CVE-2023-4004 Linux 内核 UAF 漏洞分析与利用
字数 2605 2025-08-24 07:48:33

Linux内核CVE-2023-4004 UAF漏洞分析与利用教学文档

漏洞概述

CVE-2023-4004是Linux内核中的一个Use-After-Free (UAF)漏洞,存在于Netfilter子系统的pipapo类型集合(set)实现中。该漏洞由于pipapo类型的set在删除元素(elem)时的逻辑与插入时的逻辑不一致导致,没有NFT_SET_EXT_KEY_END扩展属性的elem会删除失败,最终形成UAF条件。

漏洞背景知识

Netfilter和nftables简介

Netfilter是Linux内核的网络包过滤框架,nftables是其新一代的包过滤子系统,取代了之前的iptables/ip6tables/arptables/ebtables等工具。nftables使用集合(set)来存储规则和数据。

pipapo集合类型

pipapo是一种特殊类型的nftables集合,支持以下特性:

  • NFT_SET_INTERVAL
  • NFT_SET_MAP
  • NFT_SET_OBJECT
  • NFT_SET_TIMEOUT

集合元素(elem)生命周期管理

内核中对set中的elem的生命周期管理逻辑如下:

  1. set通常使用链表、哈希表等方式存储其中的元素(elem)
  2. 用户态通过nf_tables_newsetelem向set中添加elem
  3. 用户态通过nft_setelem_remove从set中删除元素
  4. 不同类型的set通过注册回调提供elem的插入、删除功能

pipapo set的相关回调:

const struct nft_set_type nft_set_pipapo_type = {
    .ops = {
        .lookup = nft_pipapo_lookup,
        .insert = nft_pipapo_insert,
        .activate = nft_pipapo_activate,
        .deactivate = nft_pipapo_deactivate,
        .flush = nft_pipapo_flush,
        .remove = nft_pipapo_remove,
        .walk = nft_pipapo_walk,
        .get = nft_pipapo_get,
        // ... 其他操作
    },
};

集合元素(elem)数据结构

elem的数据结构特点:

  1. 开头数据大小为set->ops->elemsize
  2. 包含struct nft_set_ext结构体
  3. struct nft_set_ext后面是实际的ext数据
  4. ext->offset是一个9字节数组,每项表示该类型数据相对ext结构起始地址的偏移

漏洞详细分析

漏洞根本原因

漏洞的根本原因是nft_pipapo_insertnft_pipapo_remove在处理没有NFT_SET_EXT_KEY_END属性的元素时行为不一致:

  1. 插入逻辑(nft_pipapo_insert):

    • 如果没有NFT_SET_EXT_KEY_END属性,则将key作为key_end
    • 使用相同的startend值进行元素比较
  2. 删除逻辑(nft_pipapo_remove):

    • 直接尝试获取NFT_SET_EXT_KEY_END属性
    • 没有处理缺少该属性的情况,导致match_end不等于match_start
    • 由于比较条件不同,无法正确找到并删除对应的elem

关键代码分析

插入操作代码片段:

static int nft_pipapo_insert(const struct net *net, const struct nft_set *set,
                           const struct nft_set_elem *elem,
                           struct nft_set_ext **ext2)
{
    const struct nft_set_ext *ext = nft_set_elem_ext(set, elem->priv);
    const u8 *start = (const u8 *)elem->key.val.data, *end;
    
    // 如果没有NFT_SET_EXT_KEY_END,则end = start
    if (nft_set_ext_exists(ext, NFT_SET_EXT_KEY_END))
        end = (const u8 *)nft_set_ext_key_end(ext)->data;
    else
        end = start;
    
    // 比较逻辑使用相同的start和end
    if (!memcmp(start, dup_key->data, sizeof(*dup_key->data)) &&
        !memcmp(end, dup_end->data, sizeof(*dup_end->data))) {
        *ext2 = &dup->ext;
        return -EEXIST;
    }
    return -ENOTEMPTY;
}

删除操作代码片段:

static void nft_pipapo_remove(const struct net *net, const struct nft_set *set,
                            const struct nft_set_elem *elem)
{
    // ...
    match_start = data;
    match_end = (const u8 *)nft_set_ext_key_end(&e->ext)->data; // 直接尝试取NFT_SET_EXT_KEY_END
    
    // 比较逻辑使用不同的match_start和match_end
    if (!pipapo_match_field(f, start, rules_fx, match_start, match_end))
        break;
    // ...
}

漏洞触发流程

  1. 创建一个pipapo类型的setA
  2. 通过NFT_MSG_NEWSETELEM向set中插入一个没有NFT_SET_EXT_KEY_END属性的元素(VE)
  3. 通过NFT_MSG_DELSETELEM删除VE,由于漏洞,setA中仍保留着VE的指针
  4. 再次通过NFT_MSG_DELSETELEM删除VE,导致double free

调试验证

使用GDB设置断点可以验证同一个elem被释放两次:

[nft_pipapo_insert] set 0xffff88800b4a5e00 e: 0xffff88800b95da00 elem: 0xffffc90004683860
[nft_pipapo_remove] set 0xffff88800b4a5e00 e: 0xffff88800b95da00
[nft_pipapo_remove] set 0xffff88800b4a5e00 e: 0xffff88800b95da00

漏洞利用技术

利用思路

  1. 初始UAF获取:通过漏洞获得elem对象的UAF
  2. 对象转换:使用table->udata占位被释放的elem,将漏洞转换为table->udata的UAF
  3. 信息泄露:分配struct nft_object占位table->udata,读取nft_object内容泄露地址
  4. 控制流劫持:再次使用table->udata占位劫持nft_object->ops函数指针
  5. ROP执行:构造ROP链实现权限提升

详细利用步骤

  1. 创建pipapo set并触发漏洞:

    • 创建pipapo set
    • 插入没有NFT_SET_EXT_KEY_END属性的元素(VE)
    • 删除VE但由于漏洞set中仍保留其指针
  2. 转换UAF对象:

    • 分配table对象,利用table->udata占位VE
    • 再次通过set释放VE,导致table->udata被释放
    • 现在漏洞转换为table->udata的UAF
  3. 地址泄露:

    • 分配struct nft_object占位table->udata
    • 通过读取table->udata泄露nft_object内容获取内核地址
    • 泄露堆地址:利用obj和table->udata重叠泄露obj->udata指针
  4. 控制流劫持:

    • 释放table->udata
    • 再次堆喷table->udata占位修改obj->ops指向伪造的ops结构
    • 伪造的ops结构中设置ROP链
  5. ROP链构造:

    *(uint64_t *)&ops[0x20] = kernel_off + 0xffffffff8198954b; // push rsi; jmp qword ptr [rsi + 0x39]
    *(uint64_t *)&ops[0x30] = kernel_off + MODULE_CT_EXPECT_OBJ_TYPE_ADDR;
    *(uint64_t *)&ops[0x60] = kernel_off + 0xffffffff8112cfc0; // pop rdi; ret
    *(uint64_t *)&ops[0x68] = kernel_off + INIT_CRED; // init_cred
    *(uint64_t *)&ops[0x70] = commit_creds;
    // ... 其他ROP gadget
    
  6. 触发执行:

    • 触发obj->ops->dump执行ROP链
    • ROP链完成以下操作:
      • 调用commit_creds(init_cred)
      • 切换任务命名空间
      • 返回到用户空间执行shell

关键数据结构利用

  • table->udata:

    • 大小可控,数据可控
    • 可以随时读取
    • 用户态通过NFTA_TABLE_USERDATA控制分配
  • nft_object:

    • 通过obj->udata存储伪造的ops指针
    • 用户态通过NFTA_OBJ_USERDATA控制内容

防御与缓解措施

  1. 补丁修复:确保nft_pipapo_remove正确处理没有NFT_SET_EXT_KEY_END属性的情况
  2. 内核配置:
    • 启用CONFIG_SLAB_FREELIST_HARDENED
    • 启用CONFIG_SLAB_FREELIST_RANDOM
  3. 运行时防护:
    • 使用KASLR
    • 启用SMAP/SMEP
  4. 权限控制:限制普通用户使用nftables的能力

总结

CVE-2023-4004是一个典型的UAF漏洞,由于集合操作中插入和删除逻辑不一致导致。通过精心构造的利用链,攻击者可以将初始的elem UAF转换为更可控的table->udata UAF,最终实现权限提升。该漏洞的分析和利用展示了现代内核漏洞利用的复杂技术链,包括对象生命周期操作、类型转换、内存布局控制和ROP构造等技术。

Linux内核CVE-2023-4004 UAF漏洞分析与利用教学文档 漏洞概述 CVE-2023-4004是Linux内核中的一个Use-After-Free (UAF)漏洞,存在于Netfilter子系统的pipapo类型集合(set)实现中。该漏洞由于pipapo类型的set在删除元素(elem)时的逻辑与插入时的逻辑不一致导致,没有NFT_ SET_ EXT_ KEY_ END扩展属性的elem会删除失败,最终形成UAF条件。 漏洞背景知识 Netfilter和nftables简介 Netfilter是Linux内核的网络包过滤框架,nftables是其新一代的包过滤子系统,取代了之前的iptables/ip6tables/arptables/ebtables等工具。nftables使用集合(set)来存储规则和数据。 pipapo集合类型 pipapo是一种特殊类型的nftables集合,支持以下特性: NFT_ SET_ INTERVAL NFT_ SET_ MAP NFT_ SET_ OBJECT NFT_ SET_ TIMEOUT 集合元素(elem)生命周期管理 内核中对set中的elem的生命周期管理逻辑如下: set通常使用链表、哈希表等方式存储其中的元素(elem) 用户态通过 nf_tables_newsetelem 向set中添加elem 用户态通过 nft_setelem_remove 从set中删除元素 不同类型的set通过注册回调提供elem的插入、删除功能 pipapo set的相关回调: 集合元素(elem)数据结构 elem的数据结构特点: 开头数据大小为 set->ops->elemsize 包含 struct nft_set_ext 结构体 struct nft_set_ext 后面是实际的ext数据 ext->offset 是一个9字节数组,每项表示该类型数据相对ext结构起始地址的偏移 漏洞详细分析 漏洞根本原因 漏洞的根本原因是 nft_pipapo_insert 和 nft_pipapo_remove 在处理没有 NFT_SET_EXT_KEY_END 属性的元素时行为不一致: 插入逻辑( nft_pipapo_insert ) : 如果没有 NFT_SET_EXT_KEY_END 属性,则将 key 作为 key_end 使用相同的 start 和 end 值进行元素比较 删除逻辑( nft_pipapo_remove ) : 直接尝试获取 NFT_SET_EXT_KEY_END 属性 没有处理缺少该属性的情况,导致 match_end 不等于 match_start 由于比较条件不同,无法正确找到并删除对应的elem 关键代码分析 插入操作代码片段 : 删除操作代码片段 : 漏洞触发流程 创建一个pipapo类型的setA 通过 NFT_MSG_NEWSETELEM 向set中插入一个没有 NFT_SET_EXT_KEY_END 属性的元素(VE) 通过 NFT_MSG_DELSETELEM 删除VE,由于漏洞,setA中仍保留着VE的指针 再次通过 NFT_MSG_DELSETELEM 删除VE,导致double free 调试验证 使用GDB设置断点可以验证同一个elem被释放两次: 漏洞利用技术 利用思路 初始UAF获取:通过漏洞获得elem对象的UAF 对象转换:使用table->udata占位被释放的elem,将漏洞转换为table->udata的UAF 信息泄露:分配struct nft_ object占位table->udata,读取nft_ object内容泄露地址 控制流劫持:再次使用table->udata占位劫持nft_ object->ops函数指针 ROP执行:构造ROP链实现权限提升 详细利用步骤 创建pipapo set并触发漏洞 : 创建pipapo set 插入没有NFT_ SET_ EXT_ KEY_ END属性的元素(VE) 删除VE但由于漏洞set中仍保留其指针 转换UAF对象 : 分配table对象,利用table->udata占位VE 再次通过set释放VE,导致table->udata被释放 现在漏洞转换为table->udata的UAF 地址泄露 : 分配struct nft_ object占位table->udata 通过读取table->udata泄露nft_ object内容获取内核地址 泄露堆地址:利用obj和table->udata重叠泄露obj->udata指针 控制流劫持 : 释放table->udata 再次堆喷table->udata占位修改obj->ops指向伪造的ops结构 伪造的ops结构中设置ROP链 ROP链构造 : 触发执行 : 触发obj->ops->dump执行ROP链 ROP链完成以下操作: 调用commit_ creds(init_ cred) 切换任务命名空间 返回到用户空间执行shell 关键数据结构利用 table->udata : 大小可控,数据可控 可以随时读取 用户态通过NFTA_ TABLE_ USERDATA控制分配 nft_ object : 通过obj->udata存储伪造的ops指针 用户态通过NFTA_ OBJ_ USERDATA控制内容 防御与缓解措施 补丁修复:确保 nft_pipapo_remove 正确处理没有 NFT_SET_EXT_KEY_END 属性的情况 内核配置: 启用CONFIG_ SLAB_ FREELIST_ HARDENED 启用CONFIG_ SLAB_ FREELIST_ RANDOM 运行时防护: 使用KASLR 启用SMAP/SMEP 权限控制:限制普通用户使用nftables的能力 总结 CVE-2023-4004是一个典型的UAF漏洞,由于集合操作中插入和删除逻辑不一致导致。通过精心构造的利用链,攻击者可以将初始的elem UAF转换为更可控的table->udata UAF,最终实现权限提升。该漏洞的分析和利用展示了现代内核漏洞利用的复杂技术链,包括对象生命周期操作、类型转换、内存布局控制和ROP构造等技术。