java反序列化原生链&脏数据
继续学习java
JDK7u21
AnnotationInvocationHandler
漏洞点出现在 sun.reflect.annotation.AnnotationInvocationHandler类的equalsImpl方法
1 | private Boolean equalsImpl(Object var1) { |
里面调用了一个getMemberMethods()方法
1 | private Method[] getMemberMethods() { |
这里获取了成员类里面的各种方法
1 | var8 = var5.invoke(var1); |
在这里执行了上面getMemberMethods的方法
可以用来触发newTransformer或者getOutputProperties来加载恶意字节码执行命令
查看这个函数的调用
在这里的invoke方法中被调用。
调用invoke方法可以利用对象代理,只要调用任意方法就可以触发invoke方法
cc1中利用过
那思路就是利用AnnotationInvocationHandler类
p神是这样解释的
在invoke方法里面想要调用equalsImpl需要一些条件
1 | if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { |
如果比较的方法用的是equal,且输入的只有一个object类型参数的时候,会调用equalsImpl方法
现在需要寻找一个在反序列化的时候能够对proxy调用equals方法的类了
equals
在集合set中,储存的对象不允许重复
在7u21中,利用了HashSet
1 | private void readObject(java.io.ObjectInputStream s) |
将塞入set的东西使用hashmap处理去重
我们查看hashMap的puts方法
1 | public V put(K key, V value) { |
会计算塞入的值的hashcode,然后就与已经存在的值进行比较
但是如果想要触发这个equals方法,就需要让比较和被比较的两个对的哈希相同
Magic Number
在put过程中调用了hash方法
1 | final int hash(Object k) { |
在调用这个方法的时候,会触发AnnotationInvocationHandler的invoke方法
,然后调用hashCodeImpl
1 | private int hashCodeImpl() { |
寻找一个hacode为0的字符串,这样使得proxy计算的hashcode的值和template本身的hashcode一样了
poc
1 | public class jdk7u21 { |
反序列化流程如下
从readobject开始,调用hashmap的put方法对传入的key去重
计算hashcode的是时候触发equals方法
调用AnnotationInvocationHandler#equalsImpl方法,触发方法遍历调用TemplatesImpl,触发任意代码执行。
条件
1 | jdk7<=7u21 |
修复
在AnnotationInvocationHandler的readobject方法中,检测到type不是AnnotationType的情况下,抛出异常,导致无法正常反序列化。
java反序列化协议
先构造一个序列化字符串
1 | package org.example; |
输出
1 | rO0ABXNyABFvcmcuZXhhbXBsZS50ZXN0MfjXNugM0eupAgACSQADYWdlTAAEbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwAAAAAXQABkFzYUwxbg== |
使用工具[zkar](phith0n/zkar: ZKar is a Java serialization protocol analysis tool implement in Go. (github.com))分析
在Java Object Serialization Specification这篇官方文档中,给出了相关的结构定义
实在是非常的长,我们只取一部分,结上面分析的结果来看看
1 | stream: |
在java的文档里面看到
1 | final static short STREAM_MAGIC = (short)0xaced; |
在我们利用工具得到的协议中
magic的值是0xaced,version的值是5
所以一个序列化协议流的开头是/xAC/xED/x00/x05
contents包括了content和contents或者只包含了content
这里是一个左递归,和编译原理有关
content包含了objects和blockdata,blockdata 是一个由数据长度加数 据本身组成的一个结构,里面可以填充任意内容。object里面可以包含了真正的java对象的结构,由以下的结构组成
1 | newObject :表示一个对象 |
分析刚刚的数据看看
1 | TC_OBJECT - 0x73 |
脏数据污染序列化数据
大多数WAF受限于性能影响,当request足够大时,WAF可能为因为性能原因作出让步,超出检查长度的内容,将不会被检查。
我们可以寻找一个可以序列化,同时能将我们的恶意数据和垃圾字符包含在一起的类
主要还是大部分的集合类可以满足我们的要求,比如
1 | 1. ArrayList |
主要思路是构建集合,添加大量的垃圾字符,加入我们恶意的对象,然后序列化生成
我们以cc1为例子
poc
1 | package org.example.CC; |
我们添加了10000个1在序列化数据前,依然能成功弹出计算器
用zkar查看我们塞入的垃圾数据(塞的太多了,我们以100个1为例子)
可以看到我们的数据在恶意数据的前面,塞入了一个超级长的字符串,同时不影响我们的反序列化
可以看到非常的长
blockdata脏字符填充
我们可以在这里填入藏字符,不影响反序列化的进行
以cc6为例子
我们先生成一个正常的bin
1 | package org.example.CC; |
然后使用添加入字符串
ps:p神用go语言写的脚本,我能力不够,只能使用大佬的了
poc
1 | package main |
可以看到可以正常rce
使用工具查看
1 | java -jar SerializationDumper.jar -r fakedata_bin.bin |
可以看到塞入了很多的垃圾字符
不过这个只是将相关的垃圾数据塞入到了readobject的后端,并没有塞入前端,塞入后也会无法正常反序列化
TC_RESET 脏字符填充
java在处理的时候会处理 contents 里面除了 TC_RESET 之外的首个结构,而且这个结构不能是 blockdata 、 exception 等。
我们可以将数据塞入这个结构里面完成rce
poc
1 | package main |
jdk8u20
多重try_catch
java在内层的trycatch出现异常的时候,内层的代码会中断,但是外层的代码会正常执行下去
1 | package org.example.CC; |
运行结果是
1 | Inner Error |
利用链分析
修复后的AnnotationInvocationHandler增加了对序列化成员type的检测
1 | private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { |
上文中提到了handle,我们看看他的定义
从0x7E0000 开始,依次指向各个结构,每指向一个结构之后,+1,就像指针
在反序列化的时候,内存里面会留下完整的AnnotationInvocationHandler和对应的handler.真正需要用到AnnotationInvocationHandler的时候,ObjectInputStream会直接引用这个handler。
8u20利用了一个类叫做
java.beans.beancontext.BeanContextSupport
1 | private synchronized void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
会调用readChildren方法
1 | public final void readChildren(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
可以看到这里有child = ois.readObject();,如果这里调用了AnnotationInvocationHandler,虽然内层的以为type的原因不能直接反序列化,但是这里并不影响外面对他的调用,实现反序列化
poc
调试的时候一直有问题,贴一下其他师傅的poc
1 | package ysoserial.test.payloads; |