CVE-2023-32233 的复现与根因分析:Linux 内核 nf_tables 释放后使用漏洞,可实现本地权限提升,附 PoC 与利用说明。
本仓库内容: 我对 CVE-2023-32233 的动手复现与研究。该漏洞是 Linux 内核 Netfilter nf_tables 子系统中的一个释放后使用漏洞,可导致本地权限提升,由 Patryk Sondej 和 Piotr Krysiuk 于 2023 年 5 月公开披露。
致谢: PoC(exploit.c)及原始分析文章由 @Liuk3r 编写。本仓库中我的工作是在自己的实验环境(Ubuntu 23.04,内核 6.2.0-20)中复现该漏洞利用,并在下文记录根因与利用链。所有测试均在专用实验机器上完成。
nf_tables 将配置更新作为原子批次处理。对每个操作相对于同一批次中先前操作所导致的状态变化的校验不充分。具体而言:
nft_rule,其中包含针对某个持有若干元素的匿名 nft_set 的 lookup 表达式。NFT_MSG_DELRULE — 删除该规则,这会隐式删除 lookup 表达式以及匿名 nft_set;NFT_MSG_DELSETELEM — 删除已被删除的匿名集合中的一个元素。nf_tables_commit_release() 将资源排入 nf_tables_destroy_list,稍后由 nf_tables_trans_destroy_work() 处理:
首先 nft_commit_release() → nf_tables_rule_destroy() → nft_lookup_destroy() → nft_set_destroy() → kvfree() 释放 nft_set;
然后,针对 NFT_MSG_DELSETELEM,nf_tables_set_elem_destroy() 调用 nft_set_elem_ext(),该函数解引用已释放的 nft_set:
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
void *elem)
{
return elem + set->ops->elemsize;
}
如果 set->ops->elemsize 被破坏,攻击者选定的内存位置就会被解释为一个 nft_set_ext —— 这是后续一切利用所依赖的原语。
nf_tables_trans_destroy_work() 竞争:插入一个大型集合销毁操作作为可控延迟,让其他 CPU 保持忙碌,并在同一 CPU 上用不同类型(不同 elemsize)的 nft_set 重新分配已释放的 nft_set 内存块 → 类型混淆。nft_set_ext 头部,使用越界偏移,使 nf_tables_set_elem_destroy() 将相邻内存块当作待销毁的 nft_expr 列表来遍历。NFTA_LOG_PREFIX 的 nft_log 表达式;nft_log_destroy() 释放 priv->prefix,从而在 kmalloc-{8..192} 中提供重叠分配原语(起初受 NULL 字节限制)。nft_object->udata 回收,以解除悬空读取上的 NULL 字节限制。nft_dynset 元素喷射,针对两种有状态表达式类型:
值得注意的是,所选择的各种原语都避开了 CFI 会拦截的操作 —— 任何步骤都不需要劫持间接调用。
SLAB_FREELIST_HARDENED)、INIT_ON_FREE 以及按 CPU 的 slab 重用限制都会攻击步骤 1;CFI(在存在的情况下)会限制步骤 3 及之后。nf_tables 批次 netlink 消息设置审计规则;在栈中出现 nft_set_elem_ext / nf_tables_trans_destroy_work 时进行崩溃分析。nftables 需要 CAP_NET_ADMIN,但非特权用户命名空间会授予该权限 —— 这就是 kernel.unprivileged_userns_clone=0 成为一个有意义加固开关的原因。在 Ubuntu 23.04(Lunar Lobster)、内核 6.2.0-20-generic 下测试通过。
sudo apt install gcc libmnl-dev libnftnl-dev
gcc -Wall -o exploit exploit.c -lmnl -lnftnl
内置 profile 针对 Ubuntu 23.04 二进制内核(linux-image-6.2.0-20-generic 6.2.0-20.20)。要测试其他内核,请将符号提取到 profile 文件中:
modprobe nf_tables
egrep ' (nft_counter_ops|nft_counter_destroy|free_percpu|modprobe_path)(\s|$)' /proc/kallsyms > profile
nft_counter_destroy() 的机器码布局随编译器和选项而变化;完整参数参考(nft_counter_destroy_call_offset/mask/check)和竞争调优参数(race_lead_sleep 等)见 ORIGIN.md。在空闲的裸机 Intel 系统上报告的成功率 ≥80%;某些微架构(例如 Alder Lake)需要额外调优。
成功运行后,PoC 会使内核处于内存被破坏的不稳定状态。仅在专用的、可丢弃的系统或 VM 快照上测试 —— 绝不要在任何存有你关心的数据的系统上测试。
exploit.c — PoC 源码(由 @Liuk3r 编写)ORIGIN.md — 原始漏洞与利用分析文章(由 Liuk3r 编写)README.md — 本文件:复现 + 我的分析笔记研究在隔离的实验环境中出于防御/教育目的进行。
nft_counter — 泄露 nft_counter_ops → nf_tables.ko 基址(绕过该模块的 KASLR);nft_quota — consumed 指针提供任意读(NFT_MSG_GETSETELEM → nft_quota_do_dump() → NFTA_QUOTA_CONSUMED)和任意写(nft_overquota() 在回环流量上将 skb->len 加到 *priv->consumed)。modprobe_path("/sbin/modprobe" → "//tmp/modprobe")→ 以攻击者控制的内容执行 root 进程。