前阵子在某个安全会议面基,和@jsjcw和@杨悦师傅交流的时候,他们透露最近新挖了一条仅依赖spring-aop的java原生反序列化gadget-chain,但没提到详情。
前天笔者正好在整理今年的一些笔记,有部分资料也和反序列化相关,就想起来了这个事情。于是就想挑战一下自己是否也能独立从spring-aop挖一条反序列化gadget-chain,最终运气不错,发现了一条gadget-chain,所需依赖为:spring-aop + aspectjweaver,能力是反射调用方法。
通过污点搜索和分析,注意到了org.springframework.aop.aspectj.AbstractAspectJAdvice
这个类:即使在反序列化之后,也天然拥有反射调用方法的能力(因为Method本身并不能反序列化,所以这种情况并不多见)
// invokeAdviceMethodWithGivenArgs.java
privatefinal 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
可以作为污点方法就基本定下来了。
接下来往上找调用链,多条调用链都会经过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。
到这里为止,梳理一下目前的思路:
JdkDynamicAopProxy#invoke
方法,这个简单,本身就是动态代理。JdkDynamicAopProxy#invoke
方法中,控制chain的生成,需要让List放入目标对象AspectJAroundAdvice
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 {
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
...
ReflectiveMethodInvocation#proceed
-> AspectJAroundAdvice#invoke
->AbstractAspectJAdvice#invokeAdviceMethodWithGivenArgs
,走到最后的污点函数,反射调用执行代码。接下来就是解决在JdkDynamicAopProxy#invoke
方法中,控制chain变量的生成过程。
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
目标是让getInterceptorsAndDynamicInterceptionAdvice
返回一个List,List里面有一个元素,是我们指定的任意对象。
分析org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice
方法,实际上有两个获取方式:
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(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