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的相关回调:
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的数据结构特点:
- 开头数据大小为
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
- 直接尝试获取
关键代码分析
插入操作代码片段:
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;
// ...
}
漏洞触发流程
- 创建一个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被释放两次:
[nft_pipapo_insert] set 0xffff88800b4a5e00 e: 0xffff88800b95da00 elem: 0xffffc90004683860
[nft_pipapo_remove] set 0xffff88800b4a5e00 e: 0xffff88800b95da00
[nft_pipapo_remove] set 0xffff88800b4a5e00 e: 0xffff88800b95da00
漏洞利用技术
利用思路
- 初始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链构造:
*(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 -
触发执行:
- 触发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构造等技术。