Nftables栈溢出漏洞(CVE-2022-1015)复现

news2024/11/24 0:42:24
背景介绍

Nftables

Nftables 是一个基于内核的包过滤框架,用于 Linux
操作系统中的网络安全和防火墙功能。nftables
的设计目标是提供一种更简单、更灵活和更高效的方式来管理网络数据包的流量。

钩子点(Hook Point)

钩子点的作用是拦截数据包,然后对数据包进行修改,比较,丢弃和放行等操作。

{width=“5.833333333333333in”
height=“3.0175185914260716in”}

// include/uapi/linux/netfilter_ipv4.h

#define NF_IP_PRE_ROUTING    0 /* After promisc drops, checksum checks. */
#define NF_IP_LOCAL_IN       1 /* If the packet is destined for this box. */
#define NF_IP_FORWARD        2 /* If the packet is destined for another interface. */
#define NF_IP_LOCAL_OUT      3 /* Packets coming from a local process. */
#define NF_IP_POST_ROUTING   4 /* Packets about to hit the wire. */
#define NF_IP_NUMHOOKS       5

Nftables的架构

Nftables由四部分组成

  • table(表):用于指定网络协议的类型,如ip,ip6,arp等

  • chains(链):用于指定流量的类型,如流入的流量或者是流出的流量并可以指定网络接口,如本地回环接口或者以太网接口等。

  • rules(规则):规则是用于过滤数据包所依据的规则,例如检查协议、来源、目的地、端口等规则。

  • express(表达式):表达式则是具体的操作。

图片来源于https://blog.dbouman.nl/2022/04/02/How-The-Tables-Have-Turned-CVE-2022-1015-1016/

使用非常形象的图描述,如下

{width=“5.833333333333333in”
height=“3.905547900262467in”}

表达式(express)

表达式是对一个数据包具体的操作,这里大致介绍后续需要用到的表达式。

nft_payload

nft_payload用于将数据包的值拷贝到寄存器中

struct nft_payload {
	enum nft_payload_bases	base:8;
	u8			offset;
	u8			len;
	u8			dreg;
};
  • base:数据包类型

  • offset:数据包起始位置的偏移

  • len:拷贝的长度

  • dreg:目的寄存器

其中base的类型由enum nft_payload_bases指定

/* include/uapi/linux/netfilter/nf_tables.h */
/**
 * enum nft_payload_bases - nf_tables payload expression offset bases
 *
 * @NFT_PAYLOAD_LL_HEADER: link layer header
 * @NFT_PAYLOAD_NETWORK_HEADER: network header
 * @NFT_PAYLOAD_TRANSPORT_HEADER: transport header
 * @NFT_PAYLOAD_INNER_HEADER: inner header / payload
 */
enum nft_payload_bases {
	NFT_PAYLOAD_LL_HEADER, //链路层
	NFT_PAYLOAD_NETWORK_HEADER, //网络层
	NFT_PAYLOAD_TRANSPORT_HEADER, //传输层
	NFT_PAYLOAD_INNER_HEADER, //数据包内部
};

下面这个例子则是将传输层的包偏移16个字节的位置,取出两个字节的内容存放到目的寄存器中,该寄存器的编号为2

base = NFT_PAYLOAD_TRANSPORT_HEADER
offset = 16 -> the checksum is 16 bytes away from the start of the TCP header
len = 2 -> the checksum is 2 bytes
dreg = NFT_REG32_02 (the small registers start frrom NFT_REG32_00)

nft_payload_set

nft_payload_set则是与nft_payload相反,该表达式是将指定寄存器的值存放到数据包里面

/* include/net/netfilter/nf_tables_core.h */
struct nft_payload_set {
	enum nft_payload_bases	base:8;
	u8			offset;
	u8			len;
	u8			sreg;
	u8			csum_type;
	u8			csum_offset;
	u8			csum_flags;
};

与nft_payload不同的是多了校验和的可选选项

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

nft_cmp_expr

nft_cmp_expr表达式则是用于比较,通常用于判断数据包的端口号是否是需要符合要求。

struct nft_cmp_expr {
	struct nft_data		data;
	u8			sreg;
	u8			len;
	enum nft_cmp_ops	op:8;
};
  • data:用于设置比较的常量值

  • sreg:源寄存器,可以认为是数据包取出的内容

  • len:比较的长度

  • op:比较的操作,具体操作类型如下所示

<!-- -->
/**
 * enum nft_cmp_ops - nf_tables relational operator
 *
 * @NFT_CMP_EQ: equal
 * @NFT_CMP_NEQ: not equal
 * @NFT_CMP_LT: less than
 * @NFT_CMP_LTE: less than or equal to
 * @NFT_CMP_GT: greater than
 * @NFT_CMP_GTE: greater than or equal to
 */
enum nft_cmp_ops {
	NFT_CMP_EQ,
	NFT_CMP_NEQ,
	NFT_CMP_LT,
	NFT_CMP_LTE,
	NFT_CMP_GT,
	NFT_CMP_GTE,
};

nft_bitwise

nft_bitwise用于对数据包进行比特级别的操作。例如移位,掩码设置等。

struct nft_bitwise {
	u8			sreg;
	u8			dreg;
	enum nft_bitwise_ops	op:8;
	u8			len;
	struct nft_data		mask;
	struct nft_data		xor;
	struct nft_data		data;
};
  • sreg:源寄存器

  • dreg:目的寄存器,用于存放最后的结果

  • op:指定具体的比特操作,具体操作如下

<!-- -->
/**
 * enum nft_bitwise_ops - nf_tables bitwise operations
 *
 * @NFT_BITWISE_BOOL: mask-and-xor operation used to implement NOT, AND, OR and
 *                    XOR boolean operations
 * @NFT_BITWISE_LSHIFT: left-shift operation
 * @NFT_BITWISE_RSHIFT: right-shift operation
 */
enum nft_bitwise_ops {
	NFT_BITWISE_BOOL,
	NFT_BITWISE_LSHIFT,
	NFT_BITWISE_RSHIFT,
};
  • mask:当op被指定为NFT_BITWISE_BOOL时,sreg的值会与mask中指定的值进行掩码设置操作。并将结果存放到dreg中

  • xor:当op被指定为NFT_BITWISE_BOOL时,sreg的值会与xor中指定的值进行掩码设置操作。并将结果存放到dreg中

  • data:当op被指定为NFT_BITWISE_LSHIFT或NFT_BITWISE_RSHIFT时,data需要被指定移位的数值。

寄存器(register)

在Nftables中是以寄存器作为存储区,用于存放一段连续的内存,现在Nftables版本每个寄存器的值存放4字节数据,而旧版的Nftables的每个寄存器是存放16个字节的数据,为了保持兼容性,4字节的寄存与16字节的寄存器都被保留。寄存器的枚举值如下所示

enum nft_registers {
	NFT_REG_VERDICT, //判定寄存器
	NFT_REG_1,
	NFT_REG_2,
	NFT_REG_3,
	NFT_REG_4,
	__NFT_REG_MAX,

	NFT_REG32_00	= 8,
	NFT_REG32_01,
	NFT_REG32_02,
	...
	NFT_REG32_13,
	NFT_REG32_14,
	NFT_REG32_15,
};

{width=“5.833333333333333in”
height=“2.037557961504812in”}

其中NFT_REG_VERDICT被称之为判断寄存器,这个寄存器比较特殊,是用于判定每个数据包需要怎么处理。判定的类型如下

  • NFT_CONTINUE:允许数据包通过防火墙

  • NFT_BREAK:跳过剩余的规则表达式

  • NF_DROP:直接丢弃数据包

  • NF_ACCEPT:接收数据包

  • NFT_GOTO:跳转到其他链执行

  • NFT_JUMP:跳转到其他链执行,若其他链将该数据包判定为NFT_CONTINUE则返回当前链

libmnl与libnftnl

由于Nftables处于内核,需要从用户层向内核发送消息去设置需要拦截数据包的属性,人工构造成本较大,因此使用现成的库libmnl与libnftnl

环境搭建

环境版本

  • ubuntu 20.04

  • qemu-system-x86_64 4.2.1

  • Linux-5.17源码

设置编译选项

cd /home/pwn/CVE/CVE-2022-1015/CVE-2022-1015/linux-5.17
sudo gedit .config
#将下列选项设置为y
CONFIG_NF_TABLES=y
CONFIG_NETFILTER_NETLINK=y
CONFIG_USER_NS=y
CONFIG_E1000=y
CONFIG_E1000E=y
make -j32 bzImage #编译

#安装依赖库
sudo apt-get install libmnl-dev 
sudo apt-get install libnftnl-dev

漏洞验证

若运行exp显示超过边界则代表没有漏洞

{width=“5.833333333333333in”
height=“2.4473501749781277in”}

若exp正常运行则代表漏洞

{width=“5.833333333333333in”
height=“4.2338702974628175in”}

漏洞分析

源码分析

nft_parse_register_load

nft_cmp_expr:op=NFT_CMP_EQ sreg=8
data=IPPROTO_TCP。该表达式是一个比较的表达式,用于比较下标为8的寄存器中的数据是否为TCP的协议。那么如何将下表为8的寄存器转化为内核中寄存器的内存位置,则需要以来下面列举的函数。

nft_parse_register_load函数就是将用户设定的寄存器的下标转化为内核寄存器的下标,然后存储在源寄存器中。

File: net\netfilter\nf_tables_api.c
9325: int nft_parse_register_load(const struct nlattr *attr, u8 *sreg, u32 len)
9326: {
9327: 	u32 reg;
9328: 	int err;
9329: 
9330: 	reg = nft_parse_register(attr); //用于提取数据包中的寄存器的下标,并转化为Nftables中寄存器的下标
9331: 	err = nft_validate_register_load(reg, len); //用于检验寄存器下表的合法性,漏洞点
9332: 	if (err < 0)
9333: 		return err;
9334: 
9335: 	*sreg = reg; //然后将寄存器的下标值存储在源寄存器中
9336: 	return 0;
9337: }

nft_parse_register

nft_parse_register函数用于将用户设置的寄存器下标转化为内核中寄存器的下标。

File: net\netfilter\nf_tables_api.c
9278: static unsigned int nft_parse_register(const struct nlattr *attr)
9279: {
9280: 	unsigned int reg;
9281: 
9282: 	reg = ntohl(nla_get_be32(attr)); //提取数据包的寄存器下标,比如上述例子为8
9283: 	switch (reg) {
        //0 - 4是16字节寄存器
9284: 	case NFT_REG_VERDICT...NFT_REG_4: 
9285: 		return reg * NFT_REG_SIZE / NFT_REG32_SIZE;  //reg * 4
9286: 	default:
        //由于4字节寄存器起始下标为8,因此要减去起始下标
9287: 		return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00; // reg  - 4
9288: 	}
9289: }

nft_validate_register_load

nft_validate_register_load函数则是用于校验下标是否有问题,但是这个检验存在整型溢出的问题。reg是枚举值,而枚举通常会被编译为int类型。len代表数据包的长度。

  • 正常情况下:reg = 100,那么套入校验则为100 * 4 + 0x10 = 0x1a0 >
    0x50,那么会检验出寄存器下标存在问题

  • 漏洞情况:reg =
    0xffffffff(int情况下的最大值),那么逃入检验则为0xffffffff * 4 +
    0x10 =
    0x40000000c,由于int最大值为0xffffffff,那么最高4个比特会被舍弃,那么最后得到的值为0x0000000c,此时0xc
    < 0x50,就可以绕过检验。那么绕过检验后就会执行* sreg =
    reg,此时reg = 0xffffffff,就会导致*sreg = 0xff

<!-- -->
File: net\netfilter\nf_tables_api.c
9313: static int nft_validate_register_load(enum nft_registers reg, unsigned int len)
9314: {
9315: 	if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE) // reg < 4则报错
9316: 		return -EINVAL;
9317: 	if (len == 0) //长度为0则报错
9318: 		return -EINVAL;
9319: 	if (reg * NFT_REG32_SIZE + len > sizeof_field(struct nft_regs, data)) //reg * 4 + len > 0x50则报错,存在整型溢出漏洞
9320: 		return -ERANGE;
9321: 
9322: 	return 0;
9323: }

nft_do_chain

每一个被拦截的数据包都需要经过链上的表达式进行处理,而链处理的函数则为nft_do_chains,这个函数会提取出相应的表达式,最后调用expr_call_ops_eval函数进行处理。

File: net\netfilter\nf_tables_core.c
197: unsigned int
198: nft_do_chain(struct nft_pktinfo *pkt, void *priv)
199: {
    	...
224: 	for (; rule < last_rule; rule = nft_rule_next(rule)) {
225: 		nft_rule_dp_for_each_expr(expr, last, rule) {
226: 			if (expr->ops == &nft_cmp_fast_ops)
227: 				nft_cmp_fast_eval(expr, &regs);
228: 			else if (expr->ops == &nft_bitwise_fast_ops)
229: 				nft_bitwise_fast_eval(expr, &regs);
230: 			else if (expr->ops != &nft_payload_fast_ops ||
231: 				 !nft_payload_fast_eval(expr, &regs, pkt))
232: 				expr_call_ops_eval(expr, &regs, pkt);
233: 
234: 			if (regs.verdict.code != NFT_CONTINUE)
235: 				break;
236: 		}
    	...

expr_call_ops_eval

expr_call_ops_eval函数则是根据不同的表达式选择不同的处理函数,例如若该数据包需要经过nft_payload的表达式处理,则会调用nft_payload_eval。

File: net\netfilter\nf_tables_core.c
161: static void expr_call_ops_eval(const struct nft_expr *expr,
162: 			       struct nft_regs *regs,
163: 			       struct nft_pktinfo *pkt)
164: {
165: #ifdef CONFIG_RETPOLINE
166: 	unsigned long e = (unsigned long)expr->ops->eval;
167: #define X(e, fun) \
168: 	do { if ((e) == (unsigned long)(fun)) \
169: 		return fun(expr, regs, pkt); } while (0)
170: 
171: 	X(e, nft_payload_eval);
172: 	X(e, nft_cmp_eval);
173: 	X(e, nft_counter_eval);
174: 	X(e, nft_meta_get_eval);
175: 	X(e, nft_lookup_eval);
176: 	X(e, nft_range_eval);
177: 	X(e, nft_immediate_eval);
178: 	X(e, nft_byteorder_eval);
179: 	X(e, nft_dynset_eval);
180: 	X(e, nft_rt_get_eval);
181: 	X(e, nft_bitwise_eval);
182: #undef  X
183: #endif /* CONFIG_RETPOLINE */
184: 	expr->ops->eval(expr, regs, pkt);
185: }

nft_payload_eval

这里可以看到regs存放在栈上面,dest这个变量值是通过&regs->data[priv->dreg]取出来的,而priv->dreg则是通过上述的nft_parse_register_load函数进行提取的,那么这里就存在一个非常明显的数组越界的漏洞。

File: net\netfilter\nft_payload.c
121: void nft_payload_eval(const struct nft_expr *expr,
122: 		      struct nft_regs *regs,
123: 		      const struct nft_pktinfo *pkt)
124: {
125: 	const struct nft_payload *priv = nft_expr_priv(expr);
126: 	const struct sk_buff *skb = pkt->skb;
127: 	u32 *dest = &regs->data[priv->dreg];
		...
165: 	if (skb_copy_bits(skb, offset, dest, priv->len) < 0) //拷贝数据
166: 		goto err;
167: 	return;
168: err:
169: 	regs->verdict.code = NFT_BREAK;
170: }

因此整型溢出结合越界就能够使我们访问到内核栈上的其他数据,如下图所示。

{width=“5.833333333333333in”
height=“6.234479440069991in”}

漏洞利用

漏洞利用分析

现在我们拥有了访问内核栈上其它地址的能力了,想要做到任意代码执行则需要考虑下列几种情况

  • 由于返回地址存在在栈上,需要判断数组越界是否能够到达返回地址的位置

  • 如何通过数组越界改写返回地址

  • 由于需要进行任意代码执行,那么需要用到内核函数,则需要得到内核的程序基地址才能够根据函数偏移地址计算出函数的实际地址

由于表达式都会对寄存器空间进行操作,因此可以使用表达式对内存空间进行读写操作。

nft_bitwise表达式可以控制源寄存器和目的寄存器,那么采用nft_bitwise可以将源寄存器的内容放置到目的寄存器中,因此可以利用nft_bitwise进行越界读,此时需要分析该数组越界读的边界的大小是多少。这里需要注意的是由于len是sreg与dreg共同拥有的,为了dreg不越界,这里的长度最大值只能为0x40而不能为0xff,因为拥有16个寄存器,每个寄存器的值为4个字节,因此16
* 4 = 64 = 0x40

  • 上界:(0xffffffff * 4) + 0x40 = 0x40000003c = 0x3c < 0x50 , 0xff
    * 4 = 0x3fc;由于可以拷贝0x40个字节的长度,因此0x3fc + 0x40 =
    0x43c。

  • 下界:(0xfffffff0 * 4 ) + 0x40 = 0x400000000 = 0x0 < 0x50, 0xf0 *
    4 = 0x3c0

内核地址泄露

接着查看regs偏移0x3c0处的地址信息,结果发现在该片区域存在一个明显的内核地址,因此若能将这个地址进行泄露,我们就能获取内核的基地址。

{width=“5.833333333333333in”
height=“3.313192257217848in”}

返回地址覆盖

由于需要构建的payload比较长,而我们如果利用nft_wise最多只能写入0x43c -
0x3c0 =
0x7c的长度,是远远不够的,因此对返回地址进行覆盖时不能使用nft_bitwise,而得改用nft_payload。nft_payload需要dreg的下标以及修改的长度len,由于我们只需要考虑一个寄存器的值,因此该寄存器的长度最大可以达到0xff。因此我们可以在地址更低的位置去搜索有无可以覆盖的返回地址。

可以发现在0x360的地址处也有一个内核的代码段地址

{width=“5.833333333333333in”
height=“3.2392869641294837in”}

并且可以发现该函数主要是处理udp包的发送

{width=“5.833333333333333in”
height=“2.555692257217848in”}

为了检验该地址是否能够修改程序的执行流程,可以使用一个方法,将该地址的值修改为非法值并观察内核是否会崩溃,这里将地址的内容修改为0x1122334455667788,接着运行程序。

{width=“5.833333333333333in”
height=“2.0175787401574805in”}

可以看到内核报错的信息显示RIP的地址为刚刚我们修改的地址,因此该地址可以作为被劫持程序执行流程的地址。

{width=“5.833333333333333in”
height=“3.0425896762904636in”}

exp分析

现在我们已经具有了两个利用条件

  • 泄露内核的程序基地址

  • 找到可以劫持程序执行流程的地址值

地址泄露

利用nft_bitwise泄露地址,这里注意的是在使用nft_bitwise泄露地址时,需要将data值设置为0,这样就不会进行移位而导致我们的内核地址被修改存储,最后将泄露的地址值放置在NFT_REG32_05下标的寄存器中

{width=“5.833333333333333in”
height=“1.047253937007874in”}

接着使用nft_set_payload将udp数据包的值修改为NFT_REG32_05寄存器的值,最后取出udp数据包的值,获取内核程序地址值

{width=“5.833333333333333in”
height=“1.6994411636045494in”}

返回地址覆盖

利用nft_payload完成返回地址的覆盖

{width=“5.833333333333333in”
height=“3.1172397200349957in”}

在数据包中将payload填充进去,这里需要说明一下如何在内核中拿到shell权限

  • 首先需要在内核中拿到root权限,需要调用commit_creds(prepare_kernel_cred
    (0))的内核函数获取新的凭证结构,而该结构的uid = 0 ,gid =
    0即为root权限

  • 其次需要切换命名空间,由于在普通用户下是无法直接调用Nftables的,因为需要管理员的权限,因此在普通用户下需要新开辟一个命名空间,使得该空间与正常的空间隔离,此时才能够正常执行Nftales。那么如果逃逸这段命名空间则需要进行命名空间的切换,则依赖于switch_task_namespace函数,可以将命名空间切换为root的命名空间

  • 最后则是实现从内核态切换到用户态,由于我们是在内核空间拿到权限,而我们需要在用户态执行,因此需要完成状态的转换,该状态转换依赖于swapgs_restore_regs函数

{width=“5.833333333333333in”
height=“3.350757874015748in”}

漏洞修复

补丁则是新增一条判断条件,属于4字节寄存器的下标单独处理,而不在16字节寄存器以及4字节寄存器的范围内的下标都进行报错处理

{width=“5.833333333333333in”
height=“4.722735126859143in”}

总结

Nftables栈溢出漏洞攻击流程

  • 首先利用nft_bitwise进行内核基地址的泄露。

  • 其次是利用nft_payload改写返回地址,并将提权代码注入进去。

  • 最后等到代码被触发。

Nftables栈溢出漏洞利用的限制

  • 不同的内核版本的内核栈布局几乎不同,因此不同版本之间的利用手法相差较大,因此漏洞的利用十分依赖于内核版本,针对不同的版本需要做出针对性的漏洞利用的exp编写。差别存在于内核栈中存在的内核代码段地址的偏移不同,例如有些内核代码段地址偏移距离regs太大,导致无法利用漏洞进行泄露或者改写。

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

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

相关文章

Linux环境下配置安装RocketMQ

1.下载 官网下载&#xff1a;下载链接 根据需要下载自己需要的版本、本文使用下载的是:4.7.0版本 2.安装 创建目录&#xff0c;使用ftp工具上传下载的包到上面创建的目录下。 cd /usr/local mkdir rocketmq-all-4.7.0注意&#xff1a;rocketmq 需要 Linux 上安装JDK&…

7、卷积神经网络:基础部件+LeNet

1、图像卷积 1. 互相关运算 严格来说&#xff0c;卷积层是个错误的叫法&#xff0c;因为它所表达的运算其实是互相关运算&#xff08;cross-correlation&#xff09;&#xff0c;而不是卷积运算。在卷积层中&#xff0c;输入张量和核张量通过(互相关运算)产生输出张量。 首先…

【运维工程师学习】安装ubuntu20.04并配置SSH

【运维工程师学习】安装ubuntu20.04 1、镜像获取2、创建虚拟机3、开始安装4、配置SSH(1) 查看本地ssh版本(2) 安装ssh(3) 查看ssh运行状态(4) 设置开机自动启动(5) 重启(6) 安装net-tools(7) 查看ip5、SSH连接 1、镜像获取 https://next.itellyou.cn/Original/#cbpProduct?ID…

pdf如何导出为图片?分享三个方法PDF转图片!

将PDF文件转换为图片是在许多场景下都非常有用的操作&#xff0c;不仅能够保留原始文档的内容&#xff0c;还方便在各种平台上共享和展示。在本文中&#xff0c;我们将介绍三种简便的方法&#xff0c;帮助您将PDF文件快速转换为图片格式。 方法一&#xff1a;使用记灵在线工具…

黑客是这样的炼成的

---黑客的态度 黑客们解决问题&#xff0c;建设事物&#xff0c;信仰自由和双向的帮助&#xff0c;人人为我, 我为人人。 要想被认为是一名黑客&#xff0c;你的行为必须显示出你已经具备了这种态度。要想做的好象你具备这种态度&#xff0c;你就不得不真的具备这种态度。但…

物理人机交互Physical human-robot interaction (pHRI)

物理人机交互是指人与机器之间通过物理接触或力传递进行交互的过程。它可以通过各种感知和操控技术实现,包括传感器、执行器、机器人和人体接口等。这种交互方式可以在多个领域和应用中发挥重要作用,例如机器人操作、虚拟现实、协作机器人和康复医疗等。 在物理人机交互中,…

Neo4J 特性CQL语句,函数,Springboot集成

Neo4J Neo4J Neo4J一、Neo4J相关介绍1.为什么需要图数据库方案1&#xff1a;Google方案2&#xff1a;Facebook 2.特性和优势3.什么是Neo4j4.Neo4j数据模型图论基础属性图模型Neo4j的构建元素 5.软件安装 二、CQL语句1.CQL简介2.CREATE 命令3.MATCH 命令4.RETURN 子句5.MATCH和R…

node版本管理工具nvm手册

文章目录 下载使用命令node.js其他版本下载下载exe版解压改名放入到指定位置 下载 github下载 使用命令 查看当前版本 nvm -v 查看被管理的node nvm list 切换node版本 nvm use 14.21.3 node.js其他版本下载 下载链接 下载exe版 解压改名放入到指定位置

Echarts柱状图数据过多设置滚动条效果

未设置前&#xff1a; 可以看出数据全部挤压在一起了 设置后&#xff1a; 下面多出一个滚动条&#xff0c;并且鼠标可以拖动数据 dataZoom: [{show: true,height:8,bottom:0,startValue: 0, //起始值endValue: 5, //结束值showDetail: false,fillerColor: "rgba(1, 132, …

10 Web全栈 组件化设计

前端架构层次设计 前端技术体系庞大&#xff0c;层级也非常分明&#xff0c;在架构设计领域中不能一概而论&#xff0c;任何应用种类都有自己独立的架构体系。比如在前端开发领域&#xff0c;在框架基础上进行应用构建的开发者锁思考的问题&#xff0c;与在组件库设计方面的开…

科大讯飞狂撒钱,400 万助力 AI 大赛

大家好&#xff0c;我是二哥呀。 6 月 15 日&#xff0c;科大讯飞的星火认知大模型 APP 上线&#xff0c;我第一时间带大家体验了一把&#xff0c;当时可以说是好评如潮。 后续有很多读者&#xff0c;比如说下面这位&#xff0c;私下找到我&#xff0c;让我帮忙审核通过一下他…

SpringCloud入门实战(十)- SpringCloud Bus消息总线

&#x1f4dd; 学技术、更要掌握学习的方法&#xff0c;一起学习&#xff0c;让进步发生 &#x1f469;&#x1f3fb; 作者&#xff1a;一只IT攻城狮 &#xff0c;关注我&#xff0c;不迷路 。 &#x1f490;学习建议&#xff1a;1、养成习惯&#xff0c;学习java的任何一个技术…

MySQL表聚合函数

前言 哈喽&#xff0c;各位小伙伴大家好&#xff0c;本篇文章为大家介绍几个MySQL中常用的聚合函数&#xff0c;什么是聚合函数&#xff0c;相信第一次看到这个名词的小伙伴是比较懵的&#xff0c;举个例子&#xff0c;比如说统计表中数据的个数&#xff0c;就可以使用MySQL中提…

软件测试技术分享丨支付测试

支付测试 引言&#xff1a;如今&#xff0c;随着非现金支付手段的不断推广和应用&#xff0c;“非现金社会”正在形成。非现金支付已成为日常生活中不可或缺的伙伴。那么&#xff0c;对于互联网产品来说&#xff0c;支付也是涉及到公司收入的一个重大环节。对于我们测试人员&am…

JavaScript 处理字符串数组数据方法

前端三件套中 JavaScript 就是充电处理业务逻辑的一个角色&#xff0c;在很多情况之下&#xff0c;或像在做项目之中去发起一些数据请求之后待服务器响应回馈给到客户端的时候&#xff0c;对于返回的数据需要进行一个格式的处理&#xff0c;比如有JSON&#xff0c;字符串&#…

Python 集合探索:解密高效数据操作和快速算法的奇妙世界

前言 在 Python 的众多数据结构中&#xff0c;集合&#xff08;Sets&#xff09;是一个引人注目且实用的概念。集合提供了一种存储无顺序、唯一元素的数据结构&#xff0c;它们不仅可以帮助我们高效处理数据&#xff0c;还能应用于各种算法和问题。 本博客将带您踏上一段关于…

python 从一个文件夹里面复制 符合要求的文件

记事本格式 import os def copy(src_file, dst_file):import shutil# 执行复制操作shutil.copy2(src_file, dst_file)def main(parent,data,dest_path):_list []for line in open(data, encoding"utf-8"):line line.strip()_list.append(line.split("\t&quo…

渠道归因(二)基于马尔可夫链的渠道归因

渠道归因&#xff08;二&#xff09;基于马尔可夫链的渠道归因 在应用当中&#xff0c;序列中的每个点通常映射为一个广告触点&#xff0c;每个触点都有一定概率变成真正的转化。通过这种建模&#xff0c;可以选择最有效&#xff0c;概率最高的触点路径。这种方法需要较多的数…

你知道怎么通过ai绘画图片描述生成图片吗

汤姆: 嘿&#xff0c;听说了吗&#xff1f;有几个特别酷的方法可以通过ai绘画图片描述生成出上面这些惊艳的图像&#xff01; 玛丽: 真的吗&#xff1f;那听起来好神奇啊&#xff01;怎么做到的&#xff1f; 汤姆: 其实很简单&#xff01;你只需要用文字描述你想要的图片&…

POI批量导入和echars图表

下载模板 userList.jsp <a href"downloadUserExcel">下载模板</a>DownloadUserServlet.java /*** 下载*/ WebServlet("/downloadUserExcel") public class DownloadUserServlet extends HttpServlet {protected void doGet(HttpServletReq…