一个感叹号引发的血案:Linuxnftables提权漏洞深度解析

admin 2026-06-11 04:33:07 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文章深入分析了Linux内核nftables子系统中的一个释放后使用漏洞。该漏洞源于nftmapcatchall_activate函数在事务回滚时因逻辑取反错误导致引用计数异常,通过精心构造nftables事务序列可在Debian和Ubuntu系统上实现普通用户到root权限的提升。文章详细描述了漏洞触发条件、信息泄露方法、控制流劫持技术以及在不同系统上的利用差异,最后强调了事务回滚逻辑严谨性的重要性。 综合评分: 92 文章分类: 漏洞分析,二进制安全,内核安全,漏洞POC,实战经验


cover_image

一个感叹号引发的血案:Linux nftables 提权漏洞深度解析

幻泉之洲

2026年6月10日 09:34 北京

在小说阅读器读本章

去阅读

2025年初,我们在 Linux 内核的 nftables 子系统中发现了一个释放后使用(UAF)漏洞。这个漏洞的根因,是 nft_map_catchall_activate() 函数在回滚操作时,因为一个逻辑取反的错误,跳过了本应重新激活的元素,导致链的引用计数异常清空。通过精心构造一系列的 nftables 事务,一个普通用户就能在 Debian 和 Ubuntu 上拿到 root 权限。下面我会把整个发现和利用过程拆开来讲。

从一次代码审计说起

今年早些时候,我们在看 nftables 的代码。这个子系统是 iptables 的接班人,负责 Linux 内核里的包过滤、NAT 这些网络层的杂活。它引入了一套生成代际(generation)机制来管理规则集的原子更新——先在一个“下一代”状态里把变更准备好,然后一次性提交翻转,老状态就成了当前状态。这套机制用在表、链、规则、表达式和集合这些对象上。

问题出在一个叫 catchall 的元素上。你可以把它理解成一个集合里的默认匹配项:一条规则来了,在集合里找不到精确匹配的条目,那就命中这个 catchall。它可以指向一个裁决(verdict),比如“接受这个包”或者“跳转到另一条链去处理”。而在裁决里引用链的时候,会增加那条链的引用计数。

当你要删除一个带 catchall 元素的集合时,流程是先停用元素、减少目标链的引用计数,然后整个事务提交。如果这中间出了错,就得回滚——把引用计数加回去,重新激活那些元素。可就在这里,代码写岔了。

那个该死的感叹号

来看看出问题的函数 nft_map_catchall_activate() 里的一段逻辑:

list_for_each_entry(catchall, &set->catchall_list, list) {    ext = nft_set_elem_ext(set, catchall->elem);    if (!nft_set_elem_active(ext, genmask))        continue;    nft_clear(ctx->net, ext);    nft_setelem_data_activate(ctx->net, set, catchall->elem);    break; }

注意那个 if (!nft_set_elem_active(...))。它的本意是:如果元素已经是激活状态,就别再激活一次。但回滚的场景下,元素刚被停用,正是 非激活 状态。这个条件判断加上一个“非”运算,刚好把需要激活的元素全跳过去了。该活的没活,该加的引用计数也没加回去。

结果就是:catchall 元素一直是停用状态,而它指向的那条链的引用计数却永远少了一个。如果这是那条链的最后一个引用,计数直接掉到零。

零引用计数的链,内核会认为它可以被安全删除。可实际上,可能还有别的对象(比如另一条链里的规则)正抓着它的指针不放。一旦它被释放,再用那个旧指针去访问,典型的 UAF 漏洞就出来了。

怎么触发这个漏洞

说具体操作。整个过程需要发几个精心编排的 nftables 事务批次。

先搭一个基本的环境:一个表,一条普通链,一条挂在 ingress 钩子上的基链,基链里放一条规则,这条规则用即时表达式把裁决指向普通链。然后创建一个 pipapo 类型的集合,给它加一个 catchall 元素,裁决码设为 NFT_GOTO,指向同一条普通链。这时候,普通链的引用计数是 2。

然后发四个批次:

批次一:删除那个 pipapo 集合。就在同一个批次里,故意触发一个错误。因为错误发生,系统会回滚。由于前面说的那个感叹号 bug,catchall 元素没被重新激活,普通链的引用计数从 2 掉到 1,本该加回 2 的步骤被跳过了。

批次二:发一个合法的批次,随便做点什么,比如创建一个空链。这个批次的真正目的是让内核把生成代际的光标翻转一次。翻转之后,那个半死不活的 catchall 元素对新的“下一代”状态来说,又变成激活了。

批次三:再次删除同一个 pipapo 集合。这次不会触发回滚。停用 catchall 元素,引用计数从 1 掉到 0。

批次四:删除那条普通链。内核一看,引用计数是 0,好,删了。但基链里的规则还指着它呢——UAF 达成。

▲ nftables 主要数据结构之间的依赖关系简化图

信息泄漏:先摸清内核布局

有了 UAF,接下来要搞清两件事:内核基地址在哪,堆地址在哪。不然 KASLR 开着,你啥也干不了。

第一条链的名字长度我填了 30 字节,这样它会在 kmalloc-cg-32 的 slab 里分配。删除它之后,紧接着调用 open("/proc/self/stat", 0)。这个调用在内核里会走到 single_open() 函数,分配一个 struct seq_operations 对象,大小刚好 32 字节,塞进刚才那个被释放的空位里。

seq_operations 里存的是一堆函数指针,都指向内核代码段的固定地址。由于 UAF,我还能通过 NFT_MSG_GETRULE 请求把那条旧规则导出,它会尝试读取链的名字——实际上读到的就是这些函数指针。只要没有一个指针里带空字节截断字符串,内核基地址就直接到手了。

搞堆地址的原理类似。这次把链名字长度设成 140 字节,落在 kmalloc-cg-192 里。删除之后,分配一堆 nft_rule 对象去占位。这些规则对象之间用链表连起来,prevnext 指针就是堆地址。再导一次规则,堆布局也清楚了。

劫持控制流:从 UAF 到 ROP

最核心的一步到了。再触发一次 UAF,但我这次要控制被释放的链对象里残余的数据。

当网络包进入基链,内核会一路跑到 nft_do_chain(),看到 NFT_GOTO 裁决,就跳到那条已被释放的普通链。然后它读取链的 blob_gen_0 指针,从这个指针指向的 struct nft_expr 里取出 ops->eval 函数指针去调用。这个调用点,就是我要劫持的地方。

在 Debian 上,我找到的 gadget 是 push rbx; pop rsp; pop rbp; ret。调用 ops->eval 时,rbx 恰好指向我通过堆喷布置好的假 blob_gen_0 对象。这个 gadget 直接把栈顶迁移到我的数据里,然后执行我预设的 ROP 链。

ROP 链做了什么?

  • 调用 commit_creds(&init_cred),拿到 root 权限。
  • 调用 __rcu_read_unlock(),跳出 RCU 临界区。
  • switch_task_namespaces() 把当前进程的命名空间换成 init 进程的,跳出容器隔离。

▲ 触发漏洞前的基础对象关系,注意 victim chain 的引用计数为 2

▲ 四个批次执行前/后的代际掩码和引用计数变化

Ubuntu 的情况稍微麻烦一点。找不到同样的 push rbx gadget,但发现 rdi 也指向同样的假数据区,于是换成 push rdi; pop rsp; pop r13; pop rbp; ret。另外,Ubuntu 内核里 prepare_kernel_cred(&init_task) 的返回值只留在 rax,不像 Debian 还同步到了 rdi。所以在 ROP 链里得多加一步,把 rax 搬到 rdi,再调 commit_creds()。其余的思路完全一致。

稳定性:99% 和 80%

这个漏洞需要多次触发 UAF,每次都在堆上做文章。空闲系统上跑,稳定性超过 99%。我们拿 Phoronix 的 Apache 压力测试把内核堆折腾得一团糟,稳定性掉到 80% 左右。坦白讲,这个数字在现实利用里已经够用了。整个过程用了不少堆布局的小技巧,包括从 USENIX 那篇 K(H)eaps 论文里学来的上下文保留策略。

最后说两句

一个逻辑反了的条件判断,让一个本该正常的回滚路径留下了引用计数的窟窿,最终变成从普通用户到 root 的直通车。类似的代际管理机制在内核里并不少见,这次的问题也给所有做事务回滚的代码提了个醒:处理撤销路径时的激活/停用逻辑,必须和正向操作严格对仗,差一个符号都不行。

至于那个 break 语句还引入了另一个洞(CVE-2026-23278),那就是另一个故事了。


参考资料

[1] https://blog.exodusintel.com/2026/06/08/off-by-exploiting-a-use-after-free-in-the-linux-kernel/


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:幻泉之洲 《一个感叹号引发的血案:Linux nftables 提权漏洞深度解析》

评论:0   参与:  0