导语:一个感叹号(
!)的缺失,能让整个Linux内核防线崩塌。CVE-2026-23111正是这样一个”一个字符改变一切”的经典案例——nf_tables子系统中一处反转的条件判断,让未授权用户能够触发释放后使用(UAF),再经过三层泄露与ROP链,最终彻底拿下root权限。这不是理论推演,FuzzingLabs团队已经为Pwn2Own Berlin 2026复现了完整利用链。
一、事件概述
CVE-2026-23111是Linux内核netfilter: nf_tables子系统中的一个本地提权漏洞。问题的根源出在nft_map_catchall_activate()函数里一个被反转的元素活跃度检查上。
对比一下就能看出问题所在。正确的非catchall回调是这样的:
/* nft_mapelem_activate(): 跳过活跃元素,处理非活跃元素 */
if (nft_set_elem_active(ext, iter->genmask)) return 0;
而有漏洞的catchall版本却写反了:
/* nft_map_catchall_activate(): 跳过非活跃元素,处理活跃元素 —— 反了! */
if (!nft_set_elem_active(ext, genmask)) continue;
就差一个取反符号!。官方补丁(commit 8fdb05de0e2d)做的事情,就是删掉这个多余的感叹号,让逻辑回到正轨。但正是这个微不足道的字符,把Abort阶段的激活逻辑彻底颠倒,最终演变成可被利用的释放后使用。
二、漏洞根源:Abort阶段的反转
要理解这个漏洞,得先搞清nf_tables的事务机制。当内核收到一批(batch)请求时,会经历三个阶段:
- Prepare阶段(准备):不就地修改元素,而是构建所谓的”下一代”(next generation)
- Commit阶段(提交):若Prepare无失败,下一代成为当前代
- Abort阶段(回滚):若Prepare出错,按逆序回滚之前的操作,恢复原始状态
漏洞就藏在第三条路径里。攻击者构造一个batch,包含两个命令:
- 第一条:删除一个含catchall元素的map(catchall通常用
*表示,意为”匹配一切”) - 第二条:故意删除一个不存在的对象,让batch失败,进入Abort阶段
按常理,Abort阶段应该调用nft_map_catchall_activate()把这些被撤销激活的catchall元素重新激活。但由于那个反转的判断,它跳过了非活跃元素、却去处理活跃元素,结果catchall元素根本没有被重新激活。
如果这个catchall元素里放的是一个GOTO判定(类似{ * : goto chainX }),那么nft_data_hold()就永远不会被调用,chain->use引用计数也就无法恢复。每触发一次Abort,chain->use就永久性递减一次。当它归零,DELCHAIN就能成功执行并释放这条链——可catchall判定元素仍然引用着它,释放后使用就此产生。

三、复现:一个Bash脚本就够
FuzzingLabs团队用nft命令行工具轻松复现了UAF。核心脚本逻辑如下:
nft add table inet mytable
nft add chain inet mytable mychain
nft add map inet mytable mymap { type ipv4_addr : verdict ; }
nft add element inet mytable mymap { * : goto mychain }
nft add map inet mytable triggermap { type ipv4_addr : verdict ; }
nft add element inet mytable triggermap { * : goto mychain }
# 构造一个失败的batch,触发Abort
cat > file_bash << 'EOF'
delete map inet mytable triggermap
delete map inet mytable bonjour
EOF
nft -f file_bash
# 引用计数已被错误递减,释放链
nft delete map inet mytable mymap
nft delete chain inet mytable mychain
加上内核日志可以清楚看到:进入Abort阶段后,nft_map_catchall_activate被调用却始终没有恢复chain->use,随后链被销毁(kfree chain),但triggermap仍引用着它。此时列出map,链名字段已经是一堆乱码——典型的UAF信号。
四、利用链:三步走上root
拿到UAF只是第一步,完整利用链需要三个阶段的精心构造:
第一阶段:泄露内核基址(kbase)。nft_chain结构体位于kmalloc-cg-128缓存,攻击者利用UAF后,在其name字段(分配在kmalloc-cg-32)上喷洒struct seq_operations——它的首字段是指向single_open函数的指针。通过列出map读回链名,就能泄露出内核基址。
第二阶段:泄露堆地址(msg_msg-2k)。把UAF对象重分配到nft_chain旧位置,在name字段偏移处放一个任意指针,构造任意读原语。借助全局变量init_ipc_ns(类型为ipc_namespace)遍历结构体偏移,一路追踪到消息队列的radix tree根节点,最终定位到msg_msg-2k slab地址。三次泄露,拿到控制栈的支点。
第三阶段:ROP链夺权。利用UAF把table->name放到旧nft_chain位置,让rules->next指向msg_msg-2k泄露地址+0x30(这里由攻击者完全控制数据)。伪造nft_expr结构后,触发nft_chain_validate遍历规则并调用伪造的validate函数指针,ROP链从这里开始。由于第一个gadget调用时RSI已经指向msg_msg区域,栈迁移变得顺理成章。
ROP链的最终目标:改写modprobe_path获取root,同时将selinux_state的enforcing字段清零绕过SELinux拦截,最后用msleep让内核休眠以触发modprobe_path。至此,完全root权限到手。
五、影响与修复
该漏洞影响启用CONFIG_USER_NS和CONFIG_NF_TABLES的发行版(绝大多数主流Linux均满足),未授权用户可通过用户命名空间加nftables完成本地提权。FuzzingLabs在Red Hat内核6.12.0-124.38.1.el10_1上完成了验证。
修复建议:立即升级到已包含commit 8fdb05de0e2d的内核版本;无法立即升级的,可通过限制用户命名空间(kernel.unprivileged_userns_clone=0)临时缓解。
六、总结
一个感叹号,撬动了整个内核权限边界。CVE-2026-23111再次印证:漏洞往往不在复杂的算法里,而藏在最不起眼的逻辑反转中。对红队而言,这是n-day武器化的绝佳教材;对运维而言,这是”补丁要跟上”的又一次血泪提醒。
版权声明:本文由华盟网原创发布,保留所有权利。配图由华盟网授权使用。











暂无评论内容