目录
-
• 前言 -
• 挖掘过程 -
• AbstractAspectJAdvice -
• ReflectiveMethodInvocation -
• JdkDynamicAopProxy -
• 调用链 -
• 代码示例
前言
前阵子在某个安全会议面基,和@jsjcw和@杨悦师傅交流的时候,他们透露最近新挖了一条仅依赖spring-aop的java原生反序列化gadget-chain,但没提到详情。
前天笔者正好在整理今年的一些笔记,有部分资料也和反序列化相关,就想起来了这个事情。于是就想挑战一下自己是否也能独立从spring-aop挖一条反序列化gadget-chain,最终运气不错,发现了一条gadget-chain,所需依赖为:spring-aop + aspectjweaver,能力是反射调用方法。
挖掘过程
AbstractAspectJAdvice
通过污点搜索和分析,注意到了org.springframework.aop.aspectj.AbstractAspectJAdvice
这个类:即使在反序列化之后,也天然拥有反射调用方法的能力(因为Method本身并不能反序列化,所以这种情况并不多见)
-
1. invokeAdviceMethodWithGivenArgs方法有反射调用方法的能力 -
2. readObject之后通过反射重新实例化了aspectJAdviceMethod属性
// invokeAdviceMethodWithGivenArgs.javaprivatefinal Class<?> declaringClass;privatefinal String methodName;privatefinal Class<?>[] parameterTypes;protectedtransient Method aspectJAdviceMethod;protected Object invokeAdviceMethodWithGivenArgs(Object[] args)throws Throwable { Object[] actualArgs = args;if (this.aspectJAdviceMethod.getParameterCount() == 0) { actualArgs = null; }try { ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);returnthis.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); } ...}privatevoidreadObject(ObjectInputStream inputStream)throws IOException, ClassNotFoundException { inputStream.defaultReadObject();try {this.aspectJAdviceMethod = this.declaringClass.getMethod(this.methodName, this.parameterTypes); } ...}
反射调用方法的三要素:Method、Object、Args,虽然还不清楚invokeAdviceMethodWithGivenArgs
中传入的args是否可控,但可以先简化场景,对于无参方法肯定是可行的,而公开的无参利用方法中就有不少,可以暂时认为Method和Args都解决了。
现在还要解决Object的问题,代码中通过this.aspectInstanceFactory.getAspectInstance()
获取反射对象。此时目标是找到一个同时实现AspectInstanceFactory
和Serializable
的子类,并且getAspectInstance
方法可以返回指定的对象。
org.springframework.aop.aspectj.SingletonAspectInstanceFactory
刚好满足。
到这里,org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
可以作为污点方法就基本定下来了。
ReflectiveMethodInvocation
接下来往上找调用链,多条调用链都会经过org.springframework.aop.framework.ReflectiveMethodInvocation#proceed
走到org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
。例如:
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed->org.springframework.aop.aspectj.AspectJAroundAdvice#invoke->org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethod(org.aspectj.lang.JoinPoint, org.aspectj.weaver.tools.JoinPointMatch, java.lang.Object, java.lang.Throwable)->org.springframework.aop.aspectj.AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
ReflectiveMethodInvocation#proceed
方法如下:
第一个点是interceptorOrInterceptionAdvice
的获取,是从interceptorsAndDynamicMethodMatchers
中拿到的,该属性本身定义就是一个List,可以序列化,而索引currentInterceptorIndex本身也只是int类型。因此可以认为interceptorOrInterceptionAdvice
是可控的。
第二个点是interceptorOrInterceptionAdvice
的类型,按照笔者上面的调用链,这个对象的类型是org.springframework.aop.aspectj.AspectJAroundAdvice
(AbstractAspectJAdvice
的子类),那么proceed
代码是走下面的分支,省去了一部分麻烦:)
第三个点是ReflectiveMethodInvocation
本身并没有实现Serializable接口,想要在反序列化过程中使用,只能依赖于动态创建。直接往上找到创建ReflectiveMethodInvocation
的地方,发现正是熟悉的老朋友org.springframework.aop.framework.JdkDynamicAopProxy#invoke
。并且在创建后刚好就调用proceed方法,完美符合要求。
分析ReflectiveMethodInvocation
的构造方法,需要控制传入的interceptorsAndDynamicMethodMatchers
,也即对应了上面JdkDynamicAopProxy#invoke
中的chain。
到这里为止,梳理一下目前的思路:
-
1. 通过反序列化触发 JdkDynamicAopProxy#invoke
方法,这个简单,本身就是动态代理。 -
2. 在 JdkDynamicAopProxy#invoke
方法中,控制chain的生成,需要让List放入目标对象AspectJAroundAdvice
-
3. 通过chain创建出 ReflectiveMethodInvocation
实例,并调用其proceed方法
...List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {MethodInvocationinvocation=newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed();}...
-
4. 通过 ReflectiveMethodInvocation#proceed
->AspectJAroundAdvice#invoke
->AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
,走到最后的污点函数,反射调用执行代码。
JdkDynamicAopProxy
接下来就是解决在JdkDynamicAopProxy#invoke
方法中,控制chain变量的生成过程。
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
目标是让getInterceptorsAndDynamicInterceptionAdvice
返回一个List,List里面有一个元素,是我们指定的任意对象。
分析org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice
方法,实际上有两个获取方式:
-
1. 从缓存的methodCache中获取 -
2. 通过getInterceptorsAndDynamicInterceptionAdvice方法
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {MethodCacheKeycacheKey=newMethodCacheKey(method); List<Object> cached = this.methodCache.get(cacheKey);if (cached == null) { cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass);this.methodCache.put(cacheKey, cached); }return cached;}
先看了一下methodCache属性,本身加了transient
修饰符,并且在readObject
方法中是直接新建的,没有任何元素,判断这条路是不可行的。
然后再分析getInterceptorsAndDynamicInterceptionAdvice
是否可用,在这个方法中,三个入参都是可控的,Advised config
实际上就是AdvisedSupport
实例。
这个方法最终返回的就是interceptorList
对象,核心是分析这个对象如何添加元素,然后往上找这个元素是怎么生成的。
public List<Object> getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, @Nullable Class<?> targetClass) {AdvisorAdapterRegistryregistry= GlobalAdvisorAdapterRegistry.getInstance(); Advisor[] advisors = config.getAdvisors(); List<Object> interceptorList = newArrayList<>(advisors.length); Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());BooleanhasIntroductions=null;for (Advisor advisor : advisors) {if (advisor instanceof PointcutAdvisor) {if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {MethodMatchermm= pointcutAdvisor.getPointcut().getMethodMatcher();boolean match;if (mm instanceof IntroductionAwareMethodMatcher) {if (hasIntroductions == null) { hasIntroductions = hasMatchingIntroductions(advisors, actualClass); } match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions); }else { match = mm.matches(method, actualClass); }if (match) { MethodInterceptor[] interceptors = registry.getInterceptors(advisor);if (mm.isRuntime()) {for (MethodInterceptor interceptor : interceptors) { interceptorList.add(newInterceptorAndDynamicMethodMatcher(interceptor, mm)); } }else { interceptorList.addAll(Arrays.asList(interceptors)); } } } }elseif (advisor instanceof IntroductionAdvisor) {IntroductionAdvisoria= (IntroductionAdvisor) advisor;if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) { Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } }else { Interceptor[] interceptors = registry.getInterceptors(advisor); interceptorList.addAll(Arrays.asList(interceptors)); } }return interceptorList;}
经过分析,无论走哪个分支,这个元素最终都是通过registry.getInterceptors(advisor)
获取的,而registry
则是直接通过静态GlobalAdvisorAdapterRegistry.getInstance()
方法获取的静态单例类
这个时候笔者还以为已经凉了,因为这种静态单例类一般无法通过反序列化过程控制的,要想修改这种实例的元素或属性,还需要其他执行分支甚至其他反序列化gadget chain来调用实例的方法。
"来都来了...",所以还是认真审了一下org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#getInterceptors
方法。
结果一下子就看到了希望,核心逻辑:advice变量是可控的,如果这个变量同时实现Advice
和MethodInterceptor
接口,则可以将其添加到interceptors,这个interceptors就是我们最终返回的目标chain。
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List<MethodInterceptor> interceptors = newArrayList<>(3);// 可控,只要可序列化即可Adviceadvice= advisor.getAdvice();if (advice instanceof MethodInterceptor) {// 如果advice本身实现了MethodInterceptor接口,将advice直接添加到interceptors!!! interceptors.add((MethodInterceptor) advice); }for (AdvisorAdapter adapter : this.adapters) {if (adapter.supportsAdvice(advice)) { interceptors.add(adapter.getInterceptor(advisor)); } }if (interceptors.isEmpty()) {thrownewUnknownAdviceTypeException(advisor.getAdvice()); }return interceptors.toArray(newMethodInterceptor[0]);}
笔者的需求是interceptors中元素是一个AspectJAroundAdvice
实例,很显然,这个类满足了实现MethodInterceptor
接口的需求,但并没有实现Advice
....
看到这里,熟悉反序列化或者是看过笔者上一篇文章文章的小伙伴,应该会一下子就想到动态代理,而我们恰好又有spring-aop依赖,JdkDynamicAopProxy
本来不就是用来做这个东西的吗?
通过JdkDynamicAopProxy
来同时代理Advice
和MethodInterceptor
接口,并设置反射调用对象是AspectJAroundAdvice
,如果后续仅被调用MethodInterceptor
接口的方法,就可以直接混水摸鱼,如果还会调用Advice
接口的方法,则可以再尝试使用CompositeInvocationHandlerImpl
,详情可以参考上一篇文章《高版本Fastjson在Java原生反序列化中的利用》。
经过测试,这里只需要JdkDynamicAopProxy
就可以了。到这里,整条gadget chain的主要障碍都基本被扫清了,剩下的就是一些边边角角的修改。
调用链
这条gadget chain的最终能力是反射调用方法,利用方式有不少的可能性,不过作为反序列化最好用的老朋友,这里演示依然使用templatesImpl#newTransformer
作为最终一环的执行函数。
gadget chain如下所示:
代码示例
https://github.com/Ape1ron/SpringAopInDeserializationDemo1
原文始发于微信公众号(银针安全):在spring-aop中挖掘新反序列化gadget-chain
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论