Java 反序列化漏洞技术浅析
2022-3-25 20:4:26 Author: www.freebuf.com(查看原文) 阅读量:11 收藏

一、从Java原生反序列化开始

1. 简介

自JDK1.1,为方便程序之间共享数据,Java便提供了对象序列化机制。其允许将Java对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以后恢复成原来的对象。开发者可通过ObjectInputStream、ObjectOutputStream类来实践序列化或反序列化。

2. 原理

关注一下序列化后的字节序列:1648208518_623daa865c5123379925f.png!small?16482085187641648208521_623daa8923c33afb6dcda.png!small1648208529_623daa91d0cd857bde818.png!small?1648208530131

根据可读部分简单观察可得,字节序列中包含了序列化对象的类名、字段类型、字段值。不包含类的完整定义(如方法信息等...)

- 造个轮子:字节序列可视化工具

字节序列的协议官方文档文档:

https://docs.oracle.com/javase/8/docs/technotes/guides/serialization/index.html

https://docs.oracle.com/javase/8/docs/platform/serialization/spec/protocol.html

纯协议解析,无类加载解析可视化反序列化字节序列: SerializationProtocols

1648208718_623dab4ec8529fc5798bd.png!small?1648208719181

- 序列化中的其他备注点:

反序列化时, 会对字节序列中涉及的类进行实例化,若在当前classpath下加载到该类,将抛出ClassNotFoundException,这对分析反序列化payload会带来一些基础阻碍。

反序列化时, 会对字节序列中的字段类型与当前classpath下的类定义做匹配,不匹配将反序列化失败,故无法通过定制字节序列伪造字段类型。

3. 攻击

当应用对外提供服务或依赖外部服务时(如Http、RPC), 一定会涉及数据交互,而交互的数据形式无论是字节流或者文本类型,均无法方便的直接进行操作,往往需要将数据转换成一个对象方便使用,此时便涉及到了反序列化行为,如之前文章提到的RMI,就使用了JDK序列化来做数据交互支持。攻击者可以对服务提交恶意反序列化字节流来实践攻击。

- 构造恶意反序列化字节序列, 复现攻击

举个例子1648208836_623dabc486c8b4584033a.png!small?1648208836931

备注: 这里使用ysoserial来构造恶意字节序列, ysoserial是一个java序列化安全研究工具。在Github上可以找到。

查看恶意字节序列内容1648208875_623dabebf37c319aca0c4.png!small

可以看到这是一个javax.management.BadAttributeValueExpException对象,并携带了多个字段,其中“open /System/Applications/Calculator.app”这个命令字符串也在其中。同时似乎看到了Runtime、getRuntime、exec。

执行反序列化1648208950_623dac369735fcec189eb.png!small

如图,反序列化一个恶意数据,经过一系列的函数调用,实现了调用Runtime.getRuntime().exec()执行可控的系统命令。!

4. 小结

通过构造精巧的序列化数据,可以使得程序在对其反序列化时,实现一系列的函数调用,最终达到恶意攻击的效果。这精巧的调用链被称为Gadget。利用Gadget实现攻击需要目标环境classpath下有相关类的存在。

二、从Gadget学习反序列化安全

1. 熟悉一个Gadget

0x00: org.apache.commons.collections.functors.InvokerTransformer

1648209175_623dad176a9c2060d5e14.png!small

Apache Commons Collections 提供了如上一个类,提供一个转化能力(transform method), 支持将对象经过可控的反射转化成另一个对象。除了InvokerTransformer,类似的转换能力工具类还有: ConstantTransformer、ChainedTransformer。

自由组合一下:

1648209203_623dad33550ff6932bef7.png!small?1648209203823

以上代码把0转换成了1,从零到一。(顺带弹出了一个计算器)

0x01: org.apache.commons.collections.map.LazyMap

1648209274_623dad7a36f7f821d88e7.png!small?16482092744711648209220_623dad44cd97eb9260040.png!small?1648209221182

LazyMap提供了懒加载设计的Map结构,它有个习惯,每次get()懒加载时都把key Transformer下。

0x02: org.apache.commons.collections.keyvalue.TiedMapEntry。

1648209286_623dad86e529f410e1404.png!small

1648209304_623dad98846d098aa2ae0.png!small?1648209304785

1648209315_623dada3264a9391ca98e.png!small?1648209315439

TiedMapEntry,平平无奇的一个类,不过它复写了hashcode(), 里面会调用它持有的map的get方法。

0x03: InvokerTransformer & LazyMap & TiedMapEntry 组合一下。

1648209352_623dadc89cb8f4ef262a5.png!small?1648209353139

调用一个对象的hashcode(), 调用了命令执行。

0x04: java.util.HashMap#readObject()

有Java开发经验的同学都知道,HashMap在putValue的时候会通过hashcode()计算key的hash值, 来确定Value存储的hash槽。而较少人知道的是,其实它反序列化的时候也会。1648209380_623dade4e55d0738ea548.png!small?1648209381873

0x05: 构建攻击代码(假)。

1648209390_623dadee5c47377e51342.png!small?1648209390777

根据上述逻辑,我们可构建出这样的一个恶意对象,当序列化成字节序列,在反序列化时将执行命令。

但当实际运行以上代码时会发现无法达到预期效果,原因是在 map.put(tiedMapEntry, "xxx") 的时候, lazyMap已经完成懒加载(及命令执行),导致后续反序列化时不再transformer。ok, 我们可以先给tiedMapEntry一个无transformer的lazymap,待组装完对象后,重新赋值一个恶意lazymap。

0x06: 构建攻击代码1648209430_623dae1635c38c5a5a42d.png!small?1648209430672

0x07: Run1648209443_623dae230c0c9b681f360.png!small?1648209443736

备注: 该Gadget来自ysoserial: CommonsCollections6,ysoserial项目收集了一些公开的反序列化Gadget, 供Java序列化安全研究。

2. Gadget给的思考

如上,Gadget会利用一些反序列化过程中的隐式调用来作为恶意调用链的桥梁。在JDK反序列化中,当一个类自定义了readObject()、readExternal()等函数,这些函数将在反序列化时被调用。同时反序列化数据包含了对象类型,即外部提交的反序列化数据可以决定调用哪个类的readObject()。一个反序列化过程,会发生哪些调用?

1648209500_623dae5cb52d343729975.png!small?1648209501507

攻击者通过可控的反序列化数据,可一定程度上操控调用这些隐式函数,配合一些有安全风险的函数实现,带来了攻击机会。对于防护视角,也可以在反序列化流程中,通过收敛上述隐式函数,来达到收敛攻击面的效果。

3. Gadget挖掘相关

这些精巧的Gadget如何挖掘出来的呢?Gadget链有长有短,通过IDEA寻找slink调用链,投入足够的精力挖掘一些短的链多少也会有收获。效率太低?自动化?试试 GadgetInspector

三、泛反序列化框架技术及风险分析

Java世界中除了JDK反序列化还有很多第三方提供了反序列化能力,他们或性能更高,或字节最少,这里我们从安全角度做个简单评估。

1648209549_623dae8d9809c55489b26.png!small?1648209550436

几个小判断:

- 绝大部分反序列化机制都存在安全风险。

- 反序列化机制涉及的隐式函数调用 + 支持反序列化的风险类(提供了安全风险功能)构成攻击链。

- 反序列化机制是否支持数据指定类型很大程度影响安全风险。

- 限定可反序列化类的范围可明显收敛攻击面,当然使用白名单or黑名单是个选择。

黑名单存在无法枚举风险类,采用此种方案的Fastjson不得不持续升级,一线研发也...,后续其提供了NoneAutotype版本,关闭数据指定类型能力,终止了无限升级循环...

四、攻击反序列化与防护

当你的程序涉及数据传输,并使用了对象序列化/反序列化技术,就存在被攻击的风险。典型的场景如Http服务、RPC,之前关于RMI的攻击介绍,其本质便属于RPC场景的反序列化攻击。诸如此类的还有: Shrio、Dubbo、Jenkins...反序列化攻击。它真的很好用。

如何防护反序列化攻击?选择安全的反序列化框架是个相对一劳永逸的办法,但如果是已经成型的工程,无法低成本的切换反序列化方案,通过黑名单限定可反序列化类的范围,并提供动态维护能力是个思路,RASP呼之欲出。


文章来源: https://www.freebuf.com/articles/web/326356.html
如有侵权请联系:admin#unsafe.sh