在spring-aop中挖掘新反序列化gadget-chain

admin 2025年1月12日21:06:48评论8 views字数 8588阅读28分37秒阅读模式

目录

  • • 前言
  • • 挖掘过程
    • • 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. 1. invokeAdviceMethodWithGivenArgs方法有反射调用方法的能力
  2. 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()获取反射对象。此时目标是找到一个同时实现AspectInstanceFactorySerializable的子类,并且getAspectInstance方法可以返回指定的对象。

org.springframework.aop.aspectj.SingletonAspectInstanceFactory刚好满足。

在spring-aop中挖掘新反序列化gadget-chain

到这里,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方法如下:

在spring-aop中挖掘新反序列化gadget-chain

第一个点是interceptorOrInterceptionAdvice的获取,是从interceptorsAndDynamicMethodMatchers中拿到的,该属性本身定义就是一个List,可以序列化,而索引currentInterceptorIndex本身也只是int类型。因此可以认为interceptorOrInterceptionAdvice是可控的。

第二个点是interceptorOrInterceptionAdvice的类型,按照笔者上面的调用链,这个对象的类型是org.springframework.aop.aspectj.AspectJAroundAdviceAbstractAspectJAdvice的子类),那么proceed代码是走下面的分支,省去了一部分麻烦:)

在spring-aop中挖掘新反序列化gadget-chain

第三个点是ReflectiveMethodInvocation本身并没有实现Serializable接口,想要在反序列化过程中使用,只能依赖于动态创建。直接往上找到创建ReflectiveMethodInvocation的地方,发现正是熟悉的老朋友org.springframework.aop.framework.JdkDynamicAopProxy#invoke。并且在创建后刚好就调用proceed方法,完美符合要求。

在spring-aop中挖掘新反序列化gadget-chain

分析ReflectiveMethodInvocation的构造方法,需要控制传入的interceptorsAndDynamicMethodMatchers,也即对应了上面JdkDynamicAopProxy#invoke中的chain。

在spring-aop中挖掘新反序列化gadget-chain

到这里为止,梳理一下目前的思路:

  1. 1. 通过反序列化触发JdkDynamicAopProxy#invoke方法,这个简单,本身就是动态代理。
  2. 2. 在JdkDynamicAopProxy#invoke方法中,控制chain的生成,需要让List放入目标对象AspectJAroundAdvice
  3. 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();}...
  1. 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. 1. 从缓存的methodCache中获取
  2. 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方法中是直接新建的,没有任何元素,判断这条路是不可行的。

在spring-aop中挖掘新反序列化gadget-chain

然后再分析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()方法获取的静态单例类

在spring-aop中挖掘新反序列化gadget-chain

这个时候笔者还以为已经凉了,因为这种静态单例类一般无法通过反序列化过程控制的,要想修改这种实例的元素或属性,还需要其他执行分支甚至其他反序列化gadget chain来调用实例的方法。

"来都来了...",所以还是认真审了一下org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#getInterceptors方法。

结果一下子就看到了希望,核心逻辑:advice变量是可控的,如果这个变量同时实现AdviceMethodInterceptor接口,则可以将其添加到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来同时代理AdviceMethodInterceptor接口,并设置反射调用对象是AspectJAroundAdvice,如果后续仅被调用MethodInterceptor接口的方法,就可以直接混水摸鱼,如果还会调用Advice接口的方法,则可以再尝试使用CompositeInvocationHandlerImpl,详情可以参考上一篇文章《高版本Fastjson在Java原生反序列化中的利用》。

经过测试,这里只需要JdkDynamicAopProxy就可以了。到这里,整条gadget chain的主要障碍都基本被扫清了,剩下的就是一些边边角角的修改。

调用链

这条gadget chain的最终能力是反射调用方法,利用方式有不少的可能性,不过作为反序列化最好用的老朋友,这里演示依然使用templatesImpl#newTransformer作为最终一环的执行函数。

gadget chain如下所示:

在spring-aop中挖掘新反序列化gadget-chain

代码示例

https://github.com/Ape1ron/SpringAopInDeserializationDemo1

原文始发于微信公众号(银针安全):在spring-aop中挖掘新反序列化gadget-chain

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月12日21:06:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   在spring-aop中挖掘新反序列化gadget-chainhttps://cn-sec.com/archives/3622413.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息