CVE-2023-32233 NetFilter权限提升漏洞

CVE-2023-32233

一、漏洞简介

CVE-2023-32233 是 Linux 内核 Netfilter 子系统中的一个释放后重用(Use-After-Free,UAF)漏洞,存在于 nf_tables 组件中。该漏洞源于匿名集(anonymous set)处理不当,导致在处理批量请求更新 nf_tables 配置信息时,可能发生内存释放后仍被访问的情况。攻击者可以利用此漏洞对内核内存执行任意读写操作,从而将自身权限提升至 root,完全控制目标系统。

UAF漏洞:UAF 漏洞全称是use after free,free是指函数是在堆上动态分配空间后不再使用该改数据从而被回收。但是由于程序员的一些不适当的操作,会导致攻击者能够操控已经被释放的区域,从而执行一些byte codes。

Linux Netfilter:Netfilter 是 GNU/Linux 下第三代防火墙,从 Linux Kernel 2.4 开始,就把 Netfilter 框架作为默认防火墙加入到 Linux 内核当中,即 Netfilter 是 Linux Kernel 的一个子功能,专门用来增强网络安全。
详细知识可参考:什么是 Linux Netfilter

二、环境安装

环境下载地址,网传做好的镜像,并包含了编译好的exp:Ubuntu-23.04,密码123456

https://pan.baidu.com/s/1yQUrToQTiaDXcsHYVeKeSQ?pwd=8888

三、漏洞复现

EXP下载地址:https://github.com/Liuk3r/CVE-2023-32233?tab=readme-ov-file

执行以下的命令安装编译环境

sudo apt install gcc libmnl-dev libnftnl-dev

编译文件:

gcc -Wall -o exploit exploit.c -lmnl -lnftnl

当然我们也可以直接使用下载的镜像中已经编译好的exp

./exp

可以看见我们已经提权,拿到了root权限

四、漏洞分析

漏洞成因是 nf_tables_deactivate_set​ 在释放匿名 set 时没有将 set 的标记设置为 inactive,导致它还能被此次 netlink批处理中的其他任务访问,从而导致 UAF,介绍该漏洞需要先对 netlink 的源码进行分析。
(源码版本: linux-6.1.1.tar.gz)

1、源码分析

用户态进程可以一次提交多个 netlink 请求给内核,这些请求在内存中按顺序存储,请求的存储结构为 struct nlmsghdr​ ,下发请求后内核通过 nfnetlink_rcv_batch 解析每个请求并处理

用户态填充和发送请求的大致代码如下:

struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
table_seq = seq;
mnl_nlmsg_batch_next(batch);

// 在批处理中新建请求
struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
    mnl_nlmsg_batch_current(batch),
    NFT_MSG_NEWSETELEM,
    NFPROTO_INET,
    NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK,
    seq++
);
nftnl_set_elems_nlmsg_build_payload(nlh, set);
mnl_nlmsg_batch_next(batch);

// 发送请求给内核处理
if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                        mnl_nlmsg_batch_size(batch)) < 0) {
    err(1, "Cannot into mnl_socket_sendto()");
}

mnl_nlmsg_batch_stop(batch);

netlink 批处理消息的处理流程涉及两个线程,nfnetlink_rcv_batch 在进程的系统调用上下文中执行对请求处理后,将请求转换为 trans 通过 nf_tables_destroy_list 提交给 nf_tables_trans_destroy_work 内核线程做进一步处理。

nfnetlink_rcv_batch 处理流程

  1. 消息循环处理

    • 逐条解析 skb 中的 Netlink 消息,直到消息长度不足头部大小。
    • 对每条消息:通过 nfnetlink_find_client 根据 type 查找对应的客户端回调表(nf_tables_cb)。调用 nc->call 执行消息对应的处理函数(如增删表/链/规则等)。
  2. 事务提交阶段
    所有消息处理完成后,调用 ss->commit(即 nf_tables_commit)。

    • 遍历 commit_list 中的事务(trans):
    • 按事务类型处理:例如 NFT_MSG_DELSETELEM 调用 nft_setelem_remove 删除集合元素。
    • 调用 nf_tables_commit_release
    • commit_list 中的事务转移到 nf_tables_destroy_list
    • 调度工作队列 nf_tables_trans_destroy_work 异步处理销毁逻辑。

nf_tables_trans_destroy_work 销毁流程

  1. 事务销毁准备
    • nf_tables_destroy_list 合并到本地链表 head,避免并发冲突。
  • 遍历 head 中的每个事务(trans):

    • 调用 nft_commit_release(trans):根据事务类型执行最终的资源释放操作(如内存回收、引用计数更新等)。

关键机制分析

  1. 异步销毁设计

    • 通过工作队列延迟资源释放,避免在主处理路径(如规则配置)中阻塞,提升性能。
    • 分离事务提交(commit)与销毁(destroy)阶段,确保原子性和一致性。
  2. 链表操作与同步

    • 链表转移:
    • list_splice_tail_initcommit_list 原子转移到 destroy_list,防止中间状态不一致。
    • list_splice_init 在销毁线程中安全获取待处理事务。
    • 锁机制:隐含使用内核锁(如 RCU 或自旋锁)保护链表操作,需确保在并发场景下无竞态条件。
  3. 事务生命周期

  • 创建:在 nc->call 处理阶段分配 trans 并加入 commit_list

  • 提交:nf_tables_commit 执行事务相关操作(如删除元素)。

  • 释放:nf_tables_trans_destroy_work 最终释放资源,确保无内存泄漏。

NFT_MSG_DELSETELEM 请求为例跟一下请求的处理路径加深理解,首先会进入 nf_tables_delsetelem 进行处理,处理后会分配 trans 并将其放到 commit_list

    trans = nft_trans_elem_alloc(ctx, NFT_MSG_DELSETELEM, set);
    if (trans == NULL)
        goto fail_trans;

    nft_trans_elem(trans) = elem;
    nft_trans_commit_list_add_tail(ctx->net, trans);

然后 nf_tables_commit 会处理 trans

        case NFT_MSG_DELSETELEM:
            te = (struct nft_trans_elem *)trans->data;

            nf_tables_setelem_notify(&trans->ctx, te->set,
                         &te->elem,
                         NFT_MSG_DELSETELEM);
            nft_setelem_remove(net, te->set, &te->elem);
            if (!nft_setelem_is_catchall(te->set, &te->elem)) {
                atomic_dec(&te->set->nelems);
                te->set->ndeact--;
            }
            break;

最后在 nf_tables_trans_destroy_work --> nft_commit_release 完成最后的处理。

static void nft_commit_release(struct nft_trans *trans)
{
    switch (trans->msg_type) {
    case NFT_MSG_DELSETELEM:
        nf_tables_set_elem_destroy(&trans->ctx,
                       nft_trans_elem_set(trans),
                       nft_trans_elem(trans).priv);
        break;

2、漏洞触发

接下来看一下漏洞触发的代码路径和内存变化,触发 UAF 的步骤如下:

  1. 创建一个匿名 set (pwn_lookup_set)并往 set 里面插入一个 elem

  2. 创建一个 rulerule 里面新建一个 lookupexprlookup expr 会引用 pwn_lookup_set

  3. 创建一个批处理其中包含两个请求:

    • 使用 NFT_MSG_DELRULE 删除上一步创建的 rule
    • 使用 NFT_MSG_DELSETELEM 删除 pwn_lookup_setelem
  4. nft_commit_release 处理 NFT_MSG_DELRULE 时会释放 rule 里面的 expr,然后在 nft_lookup_destroy 里面会释放匿名 set

  5. nft_commit_release 处理 NFT_MSG_DELSETELEM 就会访问到已经释放的 set.

‍下面以图和代码结合的形式分析内存状态的变化,创建匿名 set 和 rule 后的内存关系如下:

请求提交给内核后,会在 nfnetlink_rcv_batch 获取相关对象的指针(rulesetelem 的指针),然后将其封装到 trans 对象中,最后在 nf_tables_trans_destroy_work --> nft_commit_release 完成具体的释放。

nft_commit_release 处理 NFT_MSG_DELRULE 命令时会同步释放 rule 里面的 expr,在释放 lookup expr 时会进入 nft_lookup_destroy 释放其关联的 set ,即 pwn_lookup_set

static void nft_lookup_destroy(const struct nft_ctx *ctx,
                   const struct nft_expr *expr)
{
    struct nft_lookup *priv = nft_expr_priv(expr);

    nf_tables_destroy_set(ctx, priv->set);
}

然后在处理 NFT_MSG_DELSETELEM时就会用到已经被释放的 set,因为内核无法知道其 trans 保存的 set 指针已经被释放

static void nf_tables_set_elem_destroy(const struct nft_ctx *ctx,
                       const struct nft_set *set, void *elem)
{
    struct nft_set_ext *ext = nft_set_elem_ext(set, elem);

    if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
        nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext));

    kfree(elem);
}

static void nft_commit_release(struct nft_trans *trans)
{
    switch (trans->msg_type) {

    case NFT_MSG_DELSETELEM:
        nf_tables_set_elem_destroy(&trans->ctx,
                       nft_trans_elem_set(trans),
                       nft_trans_elem(trans).priv);
        break;

最后总结一下:在 nfnetlink_rcv_batch 处理 NFT_MSG_DELRULENFT_MSG_DELSETELEM 会把分别需要用到的对象指针(rule 指针和 set 指针)保存到 trans,然后在 nf_tables_trans_destroy_work 处理 NFT_MSG_DELRULE 命令释放 ruleset 时,NFT_MSG_DELSETELEM 请求已经在队列中了,然后在处理 NFT_MSG_DELSETELEM 时就会拿到该 trans 里面保存的 set 指针,而此时该指针指向的对象已经被释放。

3、简单的总结

上面的分析可以进行一下简答的总结:

1)、核心机制

  • 同步处理阶段:解析请求并生成事务对象(trans),加入提交列表。
  • 异步销毁阶段:通过工作队列异步释放事务相关资源。

2)、漏洞成因

当两个存在依赖关系的请求(如 删除规则删除集合元素)被放入同一批处理时,会引发以下问题:

步骤① 创建对象

用户创建匿名集合 pwn_lookup_set 和一条规则。

规则中的查找表达式(lookup expr)会引用该集合,形成 规则 → 表达式 → 集合 的依赖链。

步骤② 提交批处理

用户提交包含两个请求的批处理:

  • DELRULE:删除规则(释放规则和表达式)
  • DELSETELEM:删除集合中的元素(需访问集合)
步骤③ 内核处理
  • 同步阶段:两个请求被封装为事务对象,加入 commit_list

  • 异步阶段:

    • 处理 DELRULE 事务时,释放规则及其表达式。由于表达式引用了集合,集合也被释放。
    • 处理 DELSETELEM 事务时,仍尝试访问已释放的集合,触发 UAF。

3)、根本原因

内核未识别事务间的依赖关系,在异步销毁阶段错误释放仍被后续事务引用的资源。

五、补丁

补丁地址:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c1592a89942e9678f7d9c8030efa777c0d57edab

官方修复补丁的关键修改位于 nf_tables_deactivate_set() 函数:

void nf_tables_deactivate_set(...) {
    if (nft_set_is_anonymous(set))
        nft_deactivate_next(ctx->net, set);  // 标记匿名集合为“未激活”
    set->use--;
}

效果:在批处理的准备阶段(NFT_TRANS_PREPARE),匿名集合会被标记为“未激活”,阻止后续操作引用其指针

六、参考文章

https://demonlee.tech/archives/2303002

https://www.cnblogs.com/qianfu/p/17530832.html

https://hack-big.tech/2019/01/24/uaf%E6%BC%8F%E6%B4%9E%E5%8E%9F%E7%90%86%E5%AE%9E%E4%BE%8B%E6%B5%85%E6%9E%90/

https://www.secrss.com/articles/54704?utm_source=chatgpt.com

欢迎指正、交流 ~ ~ ~

作者:Jaren
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0协议
转载请注明文章地址及作者哦 ~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇