Node.js 应用程序中的文件写入提升为 RCE

在这篇博文中,我们将强调代码安全基础的重要性。我们会展示一个技术案例:攻击者如何能够把 Node.js 应用中的文件写入漏洞转化为远程代码执行,即便目标系统的文件系统是以只读方式挂载的。这个技术通过利用暴露的管道文件描述符来获得代码执行能力,从而绕过了这类加固环境中的限制。
基础设施加固确实能增强应用程序抵御攻击的能力。这些安全措施提高了攻击者的门槛,使漏洞利用变得更加困难。但是,我们不能把它当作解决一切问题的银弹,因为执着的攻击者仍然可以利用源代码中的漏洞实现突破。

在这篇博文中,我们将强调代码安全基础的重要性。我们会展示一个技术案例:攻击者如何能够把 Node.js 应用中的文件写入漏洞转化为远程代码执行,即便目标系统的文件系统是以只读方式挂载的。这个技术通过利用暴露的管道文件描述符来获得代码执行能力,从而绕过了这类加固环境中的限制。

文件写入漏洞
在我们主要针对Web的漏洞研究过程中,经常会遇到各种不同类型的漏洞,比如跨站脚本(XSS)、SQL注入、不安全的反序列化、服务器端请求伪造(SSRF)等等。这些漏洞的影响程度和利用难度各不相同,但有一些类型的漏洞一旦被发现,几乎可以确定整个应用都会被攻陷。

任意文件写入就是这样一种严重的漏洞类型。虽然攻击者还需要想办法确定写入什么内容以及写入到哪里,但通常有很多方式可以把它转化为代码执行,从而完全控制应用服务器:

在网站根目录写入PHP、JSP、ASPX等类型的文件
覆盖会被服务端模板引擎处理的模板文件
写入配置文件(比如uWSG的.ini文件或Jetty的.xml文件)
添加Python的站点特定配置钩子
使用通用手法,如写入SSH密钥、添加定时任务或覆盖用户的.bashrc文件
这些例子说明,攻击者通常能找到简单的方法把任意文件写入漏洞转化为代码执行。为了减少此类漏洞的危害,应用的底层基础设施往往会进行加固。这确实增加了攻击者利用的难度,但并非完全无法利用。

加固环境中的文件写入
我们最近发现了一个Node.js应用中的任意文件写入漏洞,这个漏洞的利用并不那么容易。虽然漏洞本身比较复杂,但可以简化为以下的代码片段:

app.post(‘/upload’, (req, res) => { const { filename, content } = req.body; fs.writeFile(filename, content, () => { res.json({ message: ‘File uploaded!’ }); });});
这段代码中的fs.writeFile函数用于写入文件,其中filename和content这两个参数都可以被用户完全控制。因此,这里存在一个任意文件写入漏洞。

在评估这个漏洞的影响时,我们注意到运行该应用的用户只对特定的上传文件夹有写入权限。文件系统的其他部分都是只读的。虽然这看起来像是漏洞利用的死胡同,但它引发了我们一个有趣的研究问题:

在目标系统的文件系统以只读方式挂载的情况下,是否可能将任意文件写入漏洞转化为代码执行?

只读环境下的文件写入
在Linux这样的Unix系统中,一切皆文件。不同于ext4这样存储数据在物理硬盘上的传统文件系统,还有一些文件系统服务于不同的目的。procfs虚拟文件系统就是其中之一,它通常挂载在/proc目录下,充当了探察内核内部运作的窗口。procfs并不存储实际的文件,而是提供了对运行中进程、系统内存、硬件配置等实时信息的访问。

procfs提供的一个特别有趣的信息是运行中进程的打开文件描述符,可以通过/proc//fd/来查看。进程打开的文件不仅包括传统文件,还包括设备文件、套接字和管道。例如,可以用下面的命令列出Node.js进程打开的文件描述符:

user@host:~$ {% mark yellow %}ls -al /proc/pidof node/fd{% mark %}total 0dr-x------ 2 user user 22 Oct 8 13:37 .dr-xr-xr-x 9 user user 0 Oct 8 13:37 …lrwx------ 1 user user 64 Oct 8 13:37 0 -> /dev/pts/1lrwx------ 1 user user 64 Oct 8 13:37 1 -> /dev/pts/1lrwx------ 1 user user 64 Oct 8 13:37 2 -> /dev/pts/1lrwx------ 1 user user 64 Oct 8 13:37 3 -> 'anon_inode:[eventpoll]'lr-x------ 1 user user 64 Oct 8 13:37 4 -> 'pipe:[9173261]'l-wx------ 1 user user 64 Oct 8 13:37 5 -> 'pipe:[9173261]'lr-x------ 1 user user 64 Oct 8 13:37 6 -> 'pipe:[9173262]'l-wx------ 1 user user 64 Oct 8 13:37 7 -> 'pipe:[9173262]'lrwx------ 1 user user 64 Oct 8 13:37 8 -> 'anon_inode:[eventfd]'lrwx------ 1 user user 64 Oct 8 13:37 9 -> ‘anon_inode:[eventpoll]’…
从上面的输出可以看到,这里包含了匿名管道(比如pipe:[9173261])。与在文件系统上有具体文件名的命名管道不同,由于缺少引用,通常无法直接写入匿名管道。但是,procfs文件系统允许我们通过/proc//fd/中的条目来引用管道。与procfs下的其他文件相比,这种文件写入不需要root权限,运行Node.js应用的低权限用户就可以执行:

user@host:~$ echo hello > /proc/pidof node/fd/5
即使procfs以只读方式挂载(比如在Docker容器中),写入管道仍然是可能的,因为管道由内核内部使用的一个单独的文件系统pipefs处理。

这为能够写入任意文件的攻击者打开了新的攻击面,因为他们可以向从匿名管道读取数据的事件处理器输送数据。

Node.js与管道
Node.js构建在V8 JavaScript引擎之上,是单线程的。但Node.js提供了异步非阻塞的事件循环。为此,它使用了一个叫libuv的库。这个库使用匿名管道来发送和处理事件,正如我们在上面的输出中看到的,这些管道通过procfs暴露出来。

当一个Node.js应用存在文件写入漏洞时,攻击者可以自由地写入这些管道,因为这些管道对运行应用的用户来说是可写的。那么,写入管道的数据会发生什么呢?

在审计相关的libuv源码时,一个名为uv__signal_event的处理器引起了我们的注意。它假定从管道读取的数据是uv__signal_msg_t类型的消息:

static void {% mark yellow %}uv__signal_event{% mark %}(uv_loop_t* loop, uv__io_t* w, unsigned int events) { {% mark yellow %}uv__signal_msg_t*{% mark %} msg; // […] do { r = {% mark yellow %}read{% mark %}(loop->{% mark yellow %}signal_pipefd[0]{% mark %}, {% mark yellow %}buf{% mark %} + bytes, sizeof(buf) - bytes); // […] for (i = 0; i < end; i += sizeof(uv__signal_msg_t)) { {% mark yellow %}msg = (uv__signal_msg_t*) (buf + i);{% mark %} // […]
这个uv__signal_msg_t数据结构只包含两个成员:一个handle指针和一个名为signum的整数:

typedef struct { {% mark yellow %}uv_signal_t* handle;{% mark %} int signum;} uv__signal_msg_t;
handle指针的uv_signal_t类型是uv_signal_s数据结构的别名,其中包含了一个特别有趣的成员signal_cb:

struct uv_signal_s { UV_HANDLE_FIELDS uv_signal_cb signal_cb; int signum; // […]
signal_cb成员是一个函数指针,它指向了一个回调函数的地址。当事件处理器中两个数据结构的signum值匹配时,这个回调函数会被调用:

  // [...]      handle = msg->handle;      if (msg->signum == handle->signum) {        assert(!(handle->flags & UV_HANDLE_CLOSING));        handle->signal_cb(handle, handle->signum);      }

也就是说,如果我们能够精心构造写入管道的数据,让它包含合适的handle指针和signum值,就有机会让事件处理器执行我们指定的代码。这为漏洞利用打开了一个新的思路。

下图显示了事件处理程序所需的数据结构:
在这里插入图片描述
这对攻击者来说是一个非常有希望的情况:他们可以向管道写入任意数据,而且有一条直接通往函数指针调用的路径。事实上,我们并不是第一个注意到这一点的研究者。在8月8日,HackerOne公开了来自Lee Seunghyun的一份精彩报告,他描述了一个不同的场景,在这个场景中他能够利用Node.js程序内的开放文件描述符绕过任何模块和进程级别的权限限制 - 基本上就是一种沙箱逃逸。

即便在他描述的场景中(这不是我们最初考虑的情况),这也不被认为是一个安全漏洞,该报告被标记为信息性报告并关闭。这意味着我们接下来要描述的技术仍然适用于最新版本的Node.js,而且在近期可能也不会改变。

构建数据结构
攻击者利用文件写入漏洞来利用这个事件处理器的一般策略可能是这样的:

向管道写入一个伪造的uv_signal_s数据结构
将signal_cb函数指针设置为想要调用的任意地址
向管道写入一个伪造的uv__signal_msg_t数据结构
将handle指针指向之前写入的uv_signal_s数据结构
为两个数据结构设置相同的signum值
获得任意代码执行能力
假设攻击者只能写入文件,这一切都需要在一次写入中完成,而且无法预先读取任何内存。

事件处理器的缓冲区相当大,这让攻击者可以轻松地将两个数据结构写入管道。但是这里有一个障碍:由于写入管道的所有数据都存储在栈上,数据结构的地址是未知的:
在这里插入图片描述
因此,攻击者无法让handle指针引用伪造的uv_signal_s数据结构。这就引出了一个问题:是否还有任何数据是攻击者可以引用的?

通过ASLR(地址空间布局随机化),栈、堆和所有库的地址都是随机化的。但是,让人意外的是,Node.js二进制文件本身的段并没有启用PIE(位置无关可执行文件)。我们可以看到官方Linux版本的Node.js的安全特性:

user@host:~$ checksec /opt/node-v22.9.0-linux-x64/bin/node [*] ‘/opt/node-v22.9.0-linux-x64/bin/node’ Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
这样做的原因显然是出于性能考虑,因为PIE的间接寻址会带来一些额外开销。对攻击者来说,这意味着他们可以引用Node.js段中的数据,因为这个地址是已知的。

这一发现为构建利用链提供了重要的基础,因为攻击者可以利用这个固定的地址空间来定位和引用所需的数据结构。
在这里插入图片描述
接下来的问题是:攻击者如何能在Node.js的段中存储一个伪造的uv_signal_s数据结构?一种思路是寻找让Node.js在静态位置存储攻击者控制的数据的方法(比如从HTTP请求读取的数据),但这看起来相当具有挑战性。

一个更简单的方法是直接利用已有的数据。通过检查Node.js的内存段,攻击者可以在现有数据中找到适合用作uv_signal_s伪结构的数据。

攻击者理想中的数据结构应该是这样的:
在这里插入图片描述
攻击者需要在Node.js的二进制段中找到一个满足这些条件的数据片段,这样就可以复用这些已存在的数据,而不是试图注入新的数据。

这个数据结构以一个命令字符串(“touch /tmp/pwned”)开始,后面紧跟着system函数的地址,这个地址正好与signal_cb函数指针重叠。攻击者只需要让signum值与伪造的uv_signal_s数据结构匹配,回调函数就会被调用,从而实际执行了system(“touch /tmp/pwned”)。

这种方法需要在Node.js的段中存在system函数的地址。全局偏移表(GOT)通常是一个候选位置。但是,Node.js并不使用system函数,所以它的地址并不在GOT中。即使地址存在,生成的伪造uv_signal_s数据结构的开头可能也只是GOT中的另一个条目,而不是一个有用的命令字符串。

因此,另一个方法似乎更可行:经典的ROP链(Return-Oriented Programming,返回导向编程)。

搜索数据结构片段
每个ROP链的开始都是搜索有用的ROP片段(gadget)。用于搜索ROP片段的工具通常会解析磁盘上的ELF文件,然后确定所有可执行段。.text段通常是最大的可执行段,因为它存储了程序本身的指令:

在这里插入图片描述
这个工具会遍历这个段中的字节,寻找比如ret指令这样适合作为ROP片段末尾的指令。然后工具会从表示ret指令的字节开始,逐字节向前搜索,以找出所有可能有用的ROP片段:

位置A: pop rdi ; 设置第一个函数参数 ret ; 返回到下一个片段位置B: mov rax, [rsp] ; 从栈上读取数据 ret ; 返回到下一个片段位置C: push rax ; 保存数据到栈上 jmp [rdi] ; 跳转到目标地址 ret ; 返回到下一个片段
我们的思路和寻找ROP片段类似,但目标不是寻找指令序列,而是要在Node.js二进制中搜索可以用作我们所需数据结构的字节序列。这种数据结构的搜索方法和传统ROP片段搜索有异曲同工之妙。

在这里插入图片描述
但在本例中,这并不是攻击者所需要的。他们不需要 ROP 小工具,而需要一个引用虚假uv_signal_s数据结构的地址,该地址通过其signal_cb函数指针引用 ROP 小工具。因此,存在一种间接方式:ROP 小工具(指令序列的地址)需要存储在引用的数据本身中:
在这里插入图片描述

为了识别此类合适的数据结构,攻击者需要搜索 Node.js 镜像,类似于经典的 ROP 小工具查找工具。但不同之处在于,攻击者不仅对可执行部分(如.text部分)感兴趣。伪造数据结构所在的内存不必是可执行的。攻击者需要指向小工具的指针。因此,他们可以考虑所有至少可读的段。此外,此搜索可以在内存中完成,而不仅仅是解析磁盘上的 ELF 文件。这样,攻击者还可以找到仅在运行时在.bss部分中创建的数据结构。这可能会导致误报或特定于环境的结构,但增加了他们获得有用发现的机会,这些发现可以手动验证。

这种内存中搜索虚假数据结构的基本实现实际上非常简单:

for addr, len in nodejs_segments: for offset in range(len - 7): ptr = read_mem(addr + offset, 8) if is_mapped(ptr) and is_executable(ptr): instr = read_mem(ptr, n) if is_useful_gadet(instr): print(‘gadget at %08x’ % addr + offset) print('-> ’ + disassemble(instr))
Python 脚本遍历所有 Node.js 内存区域,每次将 8 个字节解释为一个指针,并尝试引用该指针。如果地址被映射并引用可执行段中的内存,它会确定存储在此地址的字节序列是否是有用的 ROP 小工具:

在这里插入图片描述
Python 脚本的实际运行情况如下:
在这里插入图片描述所有可能有用的 ROP 小工具都会输出,现在可以用作调用回调函数时执行的第一个初始 ROP 小工具。由于写入管道的所有数据都存储在堆栈中,因此只需为第一个小工具找到合适的旋转小工具即可。一旦攻击者将堆栈指针旋转到受控数据,就可以使用经典的 ROP 链:

在这里插入图片描述
使用此技术利用任意文件漏洞时仍需注意一点。通常,用于写入文件的函数(fs.writeFile在本例中)仅限于有效的 UTF-8 数据。因此,写入管道的所有数据都必须是有效的 UTF-8。

克服 UTF-8 限制
由于 Node.js 二进制文件非常庞大(最新的 x64 版本约为 110M),因此为经典 ROP 链找到有用的 UTF-8 兼容小工具并不困难。但是,这种限制进一步限制了uv_signal_s现有数据中可能适合伪造的数据结构。基于此,需要在脚本中添加额外的检查,以验证伪造数据结构的基地址是否为有效的 UTF-8:

for addr, len in nodejs_segments: for offset in range(len - 7): {% mark yellow %}if not is_valid_utf8(addr + offset - 0x60): continue{% mark %} ptr = read_mem(addr + offset, 8) # […]
即使添加了这个额外的检查,脚本仍然可能产生一些适合伪造的数据结构,它们可能指向一个利用pivot gadget的结构,如下所示。

… 0x4354ca1 -> 0x12d0000: pop rsi; pop r15; pop rbp; ret …

这就是相关数据结构在内存中的样子:
在这里插入图片描述
这个伪造数据结构的基地址(0x4354c41)是有效的UTF-8,因此uv__signal_msg_t数据结构中的handle指针可以正确地被填充。然而,仍然存在另一个与UTF-8相关的问题。这次与signum值有关。
在这里插入图片描述
signum 值的最后一个字节是 0xf0,它不是有效的 UTF-8 编码。如果攻击者试图通过文件写入漏洞写入这个字节,它会被替换为替换字符,而 signum 值的检查会失败。如果我们在 UTF-8 可视化工具中输入 0xf0,我们可以看到这个字节引入了一个 4 个字节的 UTF-8 序列:
在这里插入图片描述
因此,UTF-8 解析器期望在这个字节后面跟随 3 个继续字节。由于 uv__signal_msg_t 数据结构包含一个 8 字节的指针和一个 4 字节的整数,编译器添加 4 个补齐字节以使结构体对齐到 16 字节。这些字节可以用来添加 3 个继续字节,如此便可构造出一个有效的 UTF-8 序列:

在这里插入图片描述
例如,上面的软盘图标是一个以 0xf0 开头的有效的 4 字节 UTF-8 序列。通过添加这些继续字节,攻击者可以满足整个有效负载都是有效 UTF-8 的要求,并使两个.signum值相匹配:

在这里插入图片描述
解决了最后一个障碍后,攻击者便能够获得远程代码执行权限。

以下视频展示了如何在脆弱的示例应用上实施此漏洞利用。该应用运行在一个具有只读根文件系统和只读 procfs 的系统上,且用户权限较低。

学习与结论
Unix 系统中的 “一切皆文件” 哲学,在利用文件写入漏洞时,打开了不常见的攻击面。在本文中,我们展示了一种技术,可以将 Node.js 应用中的文件写入漏洞转化为远程代码执行。由于事件处理器代码来自 libuv,该技术也可以应用于使用 libuv 的其他软件,如 Julia。

这种通用方法甚至可以在没有 Node.js 和 libuv 的情况下使用。只要应用程序使用管道作为通信机制,攻击者就可能利用文件写入漏洞,通过 procfs 暴露的管道文件描述符进行攻击。正如这个例子所展示的,这种攻击方式可能在常见的威胁模型中未被考虑到,但却能让远程攻击者执行任意代码。

从防御角度看,这个例子凸显了基础设施加固只能作为额外的防御层,不能替代根本的代码安全。即使采取了加固措施,决心强烈的攻击者仍可以利用源代码中的漏洞。这再次证明了代码安全的重要性,正如《整洁代码》所强调的那样,漏洞应当从根源——源代码中被修复。

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

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

相关文章

Oracle视频基础1.4.5练习

1.4.5 看bbk的框架 ls env | grep ORA cd /u01/oradata ls ll cd bbk ll cd /u01/admin/ ll ll bbk cd cd db cd dbs ls vi initbbk.ora clear ls ll env | grep ORA执行创建数据库语句。 sqlplus /nolog conn /as sysdba create spfile from pfile ! ls ll exit startup nom…

Echats柱状图的横坐标用图片显示

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>图片作为横坐标示例 - ECharts</title><!-…

vscode php Launch built-in server and debug, PHP内置服务xdebug调试,自定义启动参数配置使用示例

在vscode中&#xff0c;当我们安装了插件 PHP Debug&#xff08;xdebug.php-debug&#xff09;或者 xdebug.php-pack 后 我们通过内置默认的 php xdebug配置启动php项目后&#xff0c;默认情况下我们在vscode中设置断点是不会生效的&#xff0c;因为我们的内置php服务默认启动时…

城镇保障性住房管理:SpringBoot系统创新点

5系统详细实现 5.1 用户信息管理 管理员可以对用户信息进行添加&#xff0c;修改&#xff0c;删除操作。具体界面的展示如图5.1所示。 图5.1 用户信息管理界面 5.2 房屋类型管理 管理员可以对房屋类型信息进行添加&#xff0c;修改&#xff0c;删除操作。具体界面如图5.2所示…

积极助力信创工作,爱加密荣获麒麟系统优秀合作伙伴

近日&#xff0c;麒麟软件安全生态联盟2024年第三次工作会议顺利举行。麒麟软件安全生态联盟由工业和信息化部网络安全产业发展中心指导成立&#xff0c;旨在联合打造原创性、引领性的自主操作系统内生安全技术体系和自主创新安全生态。会上&#xff0c;麒麟软件相关领导为爱加…

用图说明 CPU、MCU、MPU、SoC 的区别

CPU CPU 负责执行构成计算机程序的指令&#xff0c;执行这些指令所指定的算术、逻辑、控制和输入/输出&#xff08;I/O&#xff09;操作。 MCU (microcontroller unit) 不同的 MCU 架构如下&#xff0c;注意这里的 MPU 表示 memory protection unit MPU (microprocessor un…

navicat pg库安装mysql fdw 外表扩展

在Windows上手动安装mysql_fdw&#xff08;MySQL Foreign Data Wrapper&#xff09;通常涉及一系列步骤&#xff0c;包括下载源码、编译、配置和测试。以下是一个详细的指南&#xff1a; 一、下载mysql_fdw源码 访问mysql_fdw的GitHub发布页面&#xff0c;选择最新版本的源码…

LongVU :Meta AI 的解锁长视频理解模型,利用自适应时空压缩技术彻底改变视频理解方式

Meta AI在视频理解方面取得了令人瞩目的里程碑式成就&#xff0c;推出了LongVU&#xff0c;这是一种开创性的模型&#xff0c;能够理解以前对人工智能系统来说具有挑战性的长视频。 研究论文 "LongVU&#xff1a;用于长视频语言理解的时空自适应压缩 "提出了一种革命…

Oracle OCP认证考试考点详解082系列09

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 41. 第41题&#xff1a; 题目 41.Examine the description of the EMPLOYEES table NLS_DATE_FORMAT is set to DD-MON-YY Which query…

【NOIP提高组】引水入城

【NOIP提高组】引水入城 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 在一个遥远的国度&#xff0c;一侧是风景秀美的湖泊&#xff0c;另一侧则是漫无边际的沙漠。该国的行政 区划十分特殊&#xff0c;刚好构成一个N行M列的矩形&#xff…

apache poi 实现下拉框联动校验

apache poi 提供了 DataValidation​ 接口 让我们可以轻松实现 Excel 下拉框数据局校验。但是下拉框联动校验是无法直接通过 DataValidation ​实现&#xff0c;所以我们可以通过其他方式间接实现。 ‍ 步骤如下&#xff1a; 创建一个隐藏 sheet private static void create…

盘点和嗨格式一样好用的10款数据恢复!!

亲爱的朋友们&#xff0c;相信大家都知道&#xff0c;一旦不小心删除了重要文件或者遇到了硬盘故障&#xff0c;心情简直如同坐过山车一般此起彼伏&#xff0c;那么这个时候就需要一款好的数据恢复工具来解救我们的数据危机。今天就来给大家推荐嗨格式数据恢复以及另外这10款我…

【Python】怎么创建一个新的conda环境,并在其中安装所需的软件包

最近在运行前同事留下的包的时候&#xff0c;遇到了numpy包和pandas包不匹配的问题&#xff0c;见前一篇&#xff1a; 屋漏偏逢连夜雨&#xff0c;今天打开spyder的时候&#xff0c;也没法运行spyder了。 于是&#xff0c;痛定思痛&#xff0c;打算换一个conda环境&#xff0…

通讯录(C 语言)

目录 一、通讯录设计思路1. 伪代码设计思路2. 代码设计思路 二、代码实现三、程序运行演示四、整体分析 一、通讯录设计思路 1. 伪代码设计思路 通讯录可以用来存储 100 个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址。 提供方法&#x…

海明码校验和纠错

1.计算1011海明码的校验位 根据公式nk<-1 &#xff08;n是信息码位数&#xff0c;1011就是4&#xff09; 则 k3 43<-1 由上可知校验码有3个 又因为 4 2 1 可以列出下列表格 7654321d3d2d1x2d0x1x01011 x0 x1 x2 分别为3个校验码的位置 又因为 7421 642 541…

SpringMVC的执行流程以及运行原理

文章目录 SpringMVC的执行流程以及运行原理一、引言二、SpringMVC核心组件与执行流程1、SpringMVC核心组件1.1、DispatcherServlet配置 2、SpringMVC执行流程 三、SpringMVC配置文件及注解四、总结 SpringMVC的执行流程以及运行原理 一、引言 SpringMVC作为Spring框架的核心模…

Unity XR Interaction Toolkit 开发教程(4)XR Origin:追踪参考系与相机高度【3.0以上版本】

获取完整课程以及答疑&#xff0c;工程文件下载&#xff1a; https://www.spatialxr.tech/ 视频试看链接&#xff1a; 4.XR Origin&#xff1a;追踪参考系与相机高度【Unity XR Interaction Toolkit 跨平台开发教程】&#xff08;3.0以上版本&#xff09; 系列教程专栏&#…

【基于Zynq FPGA对雷龙SD NAND的测试】

一、SD NAND 特征 1.1 SD 卡简介 雷龙的 SD NAND 有很多型号&#xff0c;在测试中使用的是 CSNP4GCR01-AMW 与 CSNP32GCR01-AOW。芯片是基于 NAND FLASH 和 SD 控制器实现的 SD 卡。具有强大的坏块管理和纠错功能&#xff0c;并且在意外掉电的情况下同样能保证数据的安全。 …

小程序与服务器通信webSocket和UDPSocket

UDPSocket 官方文档链接UDPSocket webSocket 官方文档链接 WebSocket 任务 先用webSocket 测试成功后&#xff0c;由于WSS的问题最后决定用UDPSocket&#xff0c;两个都记录一下。 UDPSocket 项目里主要就使用了以下几个方法, 先用wx.createUDPSocket创建UDP Socket 实例&a…

HTMLCSS:爱上班的猫咪

这段HTML和CSS代码是一个SVG动画的示例&#xff0c;它描述了一个包含猫咪和笔记本电脑的复杂场景 HTML <div class"content"><div class"container"><svg id"bongo-cat" xmlns"http://www.w3.org/2000/svg" xmlns:x…