java 1.8.0_282
mysql-connector-java 8.0.13
引入mysql-connector-java
xxxxxxxxxx
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
</dependencies>
test.java
(一个利用com.mysql.cj.jdbc.Driver
连接并操作MySQL数据库的例子)
x
import java.sql.*;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String Driver = "com.mysql.cj.jdbc.Driver";
String DB_URL = "jdbc:mysql://127.0.0.1:3306/test";
Class.forName(Driver);
// 连接数据库
Connection conn = DriverManager.getConnection(DB_URL, "test", "test");
// 操作数据库
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println(rs.getString("username") + " : " + rs.getString("password"));
}
}
}
(PS. getConnection
连接数据库阶段就存在MySQL任意文件读取漏洞,因为在connect阶段会读取MySQL Server的一些变量信息,产生了查询。利用工具:Rogue-MySql-Server)
如上面代码中的String DB_URL
参数可控时,可能造成反序列化漏洞
其漏洞触发在getConnection()
时,也就是说后续操作数据库啥的不需要都能触发反序列化漏洞。
JDBC一般指Java数据库连接。Java数据库连接,(Java Data Base Connectivity,简称JDBC)
是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
看看为什么在连接过程中会出现反序列化漏洞。
反序列化点(readObject()
)在com.mysql.cj.jdbc.result.ResultSetImpl
的public Object getObject(int columnIndex)
的switch
条件语句中存在两处,一处是case BIT
,另一处是case BLOB
可以看到都存在readObject()
但是getObject()
的参数是int
,它是怎么反序列化的?
首先需要知道getObject()
的作用(参见文档https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-type-conversions.html):
ResultSet.getObject()
方法使用MySQL和Java类型之间的类型转换
BIT
和BLOB
都是MySQL里的一种数据格式,查看其他case样例,也都是一些数据类型,判断后处理的作用也正是转化成java中的类型
BLOB
:
BLOB
为二进制形式的长文本数据,大小是0-65535 bytes
BIT
:
Bit数据类型用来存储bit值 BIT(M)代表可以存储M个bit,M的取值范围为1到64 如果手工指定bit值,则可以使用b'value'格式,比如b'111'和 b'10000000'分别代表7和128
知道了getObject()
是做啥的,再来看一下函数逻辑,
xxxxxxxxxx
public Object getObject(int columnIndex) throws SQLException {
try {
this.checkRowPos();
this.checkColumnBounds(columnIndex);
int columnIndexMinusOne = columnIndex - 1;
if (this.thisRow.getNull(columnIndexMinusOne)) {
return null;
} else {
Field field = this.columnDefinition.getFields()[columnIndexMinusOne];
switch(field.getMysqlType()) {
/*
case ... :
...
*/
}
代码中是通过field.getMysqlType()
来判断不同的类型,field
来自this.columnDefinition.getFields()[columnIndexMinusOne];
,差不多意思就是获取某一列的定义
如果为BIT
或者BLOB
:
判断field
是否为Binary
或Blob
后再用getBytes()
获取其值,接着判断PropertyKey.autoDeserialize
的值是否为真,为真则进入else
分支
data
满足第一位为-84
第二位为-19
就可以执行data
的反序列化
-84 -19
其实就是AC ED
(0xAC == 256 - 84
, 0xED == 256 - 19
),就是Java的序列化内容的魔术头
触发getObject()
的入口在com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues()
中的ResultSetUtil.resultSetToMap(toPopulate, rs);
执行SHOW SESSION STATUS
后并处理其结果时
处理返回结果时调用到getObject()
而populateMapWithSessionStatusValues()
在com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
的postProcess()
和preProcess()
方法中调用
问题又来了,怎么调用?上面代码中可以看到
x
public class ServerStatusDiffInterceptor implements QueryInterceptor
实现了QueryInterceptor
查阅官方文档得知
其可以通过配置属性
来实现调用,也就是通过传入JDBC url
时候的参数调用
值得一提的是,上文中提到的进入某else语句的条件是autoDeserialize
必须为真,这个值也可以通过配置属性
来实现
所以目前可以得知payload中不可或缺的一段为:
xautoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
先利用mysql8
调试发现确实能进入到这个分支,只不过暂时mysqlType
是VARCHAR
类型:
可以到wireshark
中查看流量信息(需配置useSSL=false
属性,不然都是TLS流量)
跳过身份认证阶段,前面的3个SELECT
和SET
语句是getConnection()
时就会触发的操作,然后通过我们配置的autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
属性成功触发populateMapWithSessionStatusValues()
中的SHOW SESSION STATUS
其实用脚本也挺好构造的,直接把wireshark
中的流量复制一下,修改一下SHOW SESSION STATUS
语句后的返回结果就好
有一种简单构造语句的方法,直接在mysql中创建一个table
,并添加
x
create table a(VARIABLE_NAME blob,VARIABLE_VALUE blob);
/* 0xaced1111 改为反序列化payload即可 */
insert into a(VARIABLE_NAME,VARIABLE_VALUE) values (0xaced1111,0xaced1111);
然后抓包获取查询的此数据库的流量数据
替换上面SHOW
语句的返回结果就可以触发反序列化了。
(理论上直接修改SHOW SESSION STATUS
所用到的表performance_schema.session_status
就行)
可以看到最新版(8.0.23)中com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor#populateMapWithSessionStatusValues()
代码已修改,SHOW SESSION STATUsS
后没有了ResultSetUtil.resultSetToMap(toPopulate, rs);
的操作: