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