Spring AOP 原生链挖掘思路分析
前言
本文详细分析Spring AOP原生链的挖掘思路,该链在Java安全攻防赛中表现出色,能够绕过多种类过滤限制。我们将从发现者视角完整剖析这条链的挖掘过程。
Sink点分析:AbstractAspectJAdvice
关键方法分析
在AbstractAspectJAdvice类中,我们发现以下关键方法:
public Object invoke(MethodInvocation mi) throws Throwable {
if (!(mi instanceof ProxyMethodInvocation)) {
throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
}
ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
return invokeAdviceMethod(pmi, null, null);
}
以及:
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterCount() == 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.getPointcutExpression() + "]", ex);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
利用条件
要实现利用,需要控制以下关键点:
- aspectJAdviceMethod:需要能够控制要调用的方法
- aspectInstanceFactory.getAspectInstance():需要能够控制方法调用的目标对象
AspectInstanceFactory分析
AspectInstanceFactory有以下实现类:
SingletonAspectInstanceFactory:直接返回aspectInstance对象SimpleAspectInstanceFactory:通过反射创建实例PrototypeAspectInstanceFactory:每次调用都创建新实例
其中SingletonAspectInstanceFactory的getAspectInstance方法直接返回对象:
public Object getAspectInstance() {
return this.aspectInstance;
}
我们可以通过反射控制这个aspectInstance,测试证明可以使用TemplatesImpl类成功实现任意代码执行。
利用链构建
ReflectiveMethodInvocation分析
我们需要向上寻找调用链,发现ReflectiveMethodInvocation类的proceed方法:
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) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
return proceed();
}
}
else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
要利用这个方法,需要控制interceptorOrInterceptionAdvice对象为AspectJAroundAdvice。
控制interceptorsAndDynamicMethodMatchers
interceptorsAndDynamicMethodMatchers是从List中获取的,但ReflectiveMethodInvocation没有实现Serializable接口,无法直接序列化控制。
解决方案是寻找动态创建ReflectiveMethodInvocation对象的地方,且该创建者类需要实现Serializable接口。
JdkDynamicAopProxy
JdkDynamicAopProxy类满足上述条件:
- 实现了
Serializable接口 - 在
invoke方法中实例化ReflectiveMethodInvocation对象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// ...
retVal = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain).proceed();
// ...
}
控制chain
chain是通过getInterceptorsAndDynamicInterceptionAdvice方法获取的,该方法有两种获取方式:
- 从
methodCache中直接获取 - 从工厂类重新创建
我们需要关注第一种方式,通过控制AdvisedSupport对象来影响chain的生成。
getInterceptors方法分析
getInterceptors方法最终从advice获取拦截器:
public static 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 : ADAPTERS) {
if (adapter.supportsAdvice(advice)) {
interceptors.add(adapter.getInterceptor(advisor));
}
}
if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);
}
要利用这个方法,需要传入的对象同时实现Advice和MethodInterceptor接口。
突破Advice限制
虽然AspectJAroundAdvice实现了MethodInterceptor接口但没有实现Advice接口,我们可以使用JdkDynamicAopProxy代理这两个接口,设置target为AspectJAroundAdvice。
Source点分析
要触发整个利用链,需要找到一个readObject方法中调用任意对象方法的点。BadAttributeValueExpException类完美满足这个需求:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
通过控制valObj触发其toString方法,进而触发代理类的invoke方法,完成整个利用链的闭环。
完整利用链
BadAttributeValueExpException.readObject()触发toString()- 代理对象
toString()触发JdkDynamicAopProxy.invoke() JdkDynamicAopProxy.invoke()创建并调用ReflectiveMethodInvocation.proceed()ReflectiveMethodInvocation.proceed()调用AspectJAroundAdvice.invoke()AspectJAroundAdvice.invoke()调用AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()反射调用任意方法
总结
这条Spring AOP原生链的挖掘过程展示了从sink点向上寻找调用链的完整思路,通过分析各个关键类的交互关系和接口实现情况,最终构建出一条完整的利用链。该链的特别之处在于:
- 利用了Spring AOP的核心机制
- 能够绕过常见的类过滤限制
- 结合了动态代理和反射机制
- 通过
BadAttributeValueExpException触发,兼容性高
这种挖掘思路可以应用于其他框架的链挖掘中,重点关注框架核心组件的交互方式和接口实现情况。