CVE-2022-34918 nftables堆溢出
- 影响版本:原作者在Ubuntu 22.04(5.15.0)上成功提权,本篇使用5.17.15
- 利用条件:CAP_NET_ADMIN
参考:【kernel exploit】CVE-2022-34918 nftable堆溢出漏洞利用(list_head任意写)
背疼o(╥﹏╥)o
漏洞分析
set,map,vmap
set
- 操作行为
- add
- delete
- destroy
- list
- flush
- reset
- 集合规格
- type(string):ipv4_addr,ipv6_addr,ether_addr,inet_proto,inet_service,mark
- typeof(expression)
- flags(string):constant,dynamic,interval,timeout
- timeout: string, decimal followed by unit (d, h, m, s)
- gc-interval:string, decimal followed by unit (d, h, m, s)
- elements:depends on ‘type’
- size:unsigned integer(64 bit)
- policy(string):performance(default),memory
- auto-merge:boolean or specific parameters
1
2
3
4
5$ add set [family] table set { type type | typeof expression ; [flags flags ;] [timeout timeout ;] [gc-interval gc-interval ;] [elements = { element[, ...] } ;] [size size ;] [comment comment ;] [policy policy ;] [auto-merge ;] }
$ {delete | destroy | list | flush | reset } set [family] table set
$ list sets [family]
$ delete set [family] table handle handle
$ {add | delete | destroy } element [family] table set { element[, ...] }- 操作行为
map
1
2
3
4
5
6
7$ add map [family] table map { type type | typeof expression [flags flags ;] [elements = { element[, ...] } ;] [size size ;] [comment comment ;] [policy policy ;] }
$ {delete | destroy | list | flush | reset } map [family] table map
$ list maps [family]
$ {add | create | delete | destroy | get | reset } element [family] table set { ELEMENT[, ...] }
ELEMENT := key_expression OPTIONS [: value_expression]
OPTIONS := [timeout TIMESPEC] [expires TIMESPEC] [comment string]
TIMESPEC := [numd][numh][numm][num[s]]vmaps:将元素直接映射到裁决(verdict)语句上,裁决语句决定了当匹配到特定规则时应该采取的动作,比如accept、reject或drop
1
$ nft add map table-name map-name { type inet_proto : verdict \; }
其实就是一种特殊的map
相关源码分析
以上三个都是用set实现的
nftables创建集合
1 |
|
向集合中添加元素
1 |
|
引用集合
1 |
|
详情见另一篇博客
创建集合元素的回调函数
1 |
|
分析使用的命令
1 |
|
nla
nla相关数据处理
nf_tables_newsetelem
进入时的参数nla,一个nlattr指针的数组,nlattr的结构如下
1
2
3
4
5
6
7
8
9
10
11
12
13/*
* <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
* +---------------------+- - -+- - - - - - - - - -+- - -+
* | Header | Pad | Payload | Pad |
* | (struct nlattr) | ing | | ing |
* +---------------------+- - -+- - - - - - - - - -+- - -+
* <-------------- nlattr->nla_len -------------->
*/
struct nlattr {
__u16 nla_len;
__u16 nla_type;
};nla数组的idx和内容对应
1
2
3
4
5
6
7
8
9enum nft_set_elem_list_attributes {
NFTA_SET_ELEM_LIST_UNSPEC,
NFTA_SET_ELEM_LIST_TABLE,
NFTA_SET_ELEM_LIST_SET,
NFTA_SET_ELEM_LIST_ELEMENTS,
NFTA_SET_ELEM_LIST_SET_ID,
__NFTA_SET_ELEM_LIST_MAX
};
#define NFTA_SET_ELEM_LIST_MAX (__NFTA_SET_ELEM_LIST_MAX - 1)nla
table
set
elements,后面会送解析
set_id
接下来就是惯例查找table查找set,调用nft_add_set_elem添加set元素
nft_add_set_elem
进入时的参数attr就是上面的elements,先送解析为nla
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16enum nft_set_elem_attributes {
NFTA_SET_ELEM_UNSPEC,
NFTA_SET_ELEM_KEY,
NFTA_SET_ELEM_DATA,
NFTA_SET_ELEM_FLAGS,
NFTA_SET_ELEM_TIMEOUT,
NFTA_SET_ELEM_EXPIRATION,
NFTA_SET_ELEM_USERDATA,
NFTA_SET_ELEM_EXPR,
NFTA_SET_ELEM_PAD,
NFTA_SET_ELEM_OBJREF,
NFTA_SET_ELEM_KEY_END,
NFTA_SET_ELEM_EXPRESSIONS,
__NFTA_SET_ELEM_MAX
};
#define NFTA_SET_ELEM_MAX (__NFTA_SET_ELEM_MAX - 1)只有key有数据,ip地址
后面就是nla解析,填入nft_set_elem,这里只有key需要解析,elem.key内容见下
elem的结构
- key
- key_end
- data
- priv
前三个成员是一个buf或一个熟悉的nft_data,寄存器也是用这个表示的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15struct nft_set_elem {
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} key;
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} key_end;
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} data;
void *priv;
};1
2
3
4
5
6struct nft_data {
union {
u32 data[4];
struct nft_verdict verdict;
};
} __attribute__((aligned(__alignof__(u64))));然后key,key_end和data传递给nft_set_elem_init,返回一个priv
tmpl
tmpl相关数据处理
nft_add_set_elem
nft_set_ext_tmpl结构及相关enum,一个用于准备nft_set_ext结构体的模板结构体,用于记录各个数据的偏移
1 |
|
1 |
|
先prepare一个tmpl,就是进行一个结构体的初始化,尤其是初始化len
1 |
|
之后的数据解析都会更新tmpl的长度和对应偏移
调用nft_set_ext_add_length更新
1
2
3
4
5
6nft_set_ext_add_length(&tmpl, NFT_SET_EXT_KEY, set->klen);
nft_set_ext_add_length(&tmpl, NFT_SET_EXT_KEY_END, set->klen);
nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, desc.len);
nft_set_ext_add_length(&tmpl, NFT_SET_EXT_EXPRESSIONS,
sizeof(struct nft_set_elem_expr) +
size);1
2
3
4
5
6
7
8static inline void nft_set_ext_add_length(struct nft_set_ext_tmpl *tmpl, u8 id,
unsigned int len)
{
tmpl->len = ALIGN(tmpl->len, nft_set_ext_types[id].align);
BUG_ON(tmpl->len > U8_MAX);
tmpl->offset[id] = tmpl->len;
tmpl->len += nft_set_ext_types[id].len + len;
}调用nft_set_ext_add更新
1
2
3
4nft_set_ext_add(&tmpl, NFT_SET_EXT_FLAGS);
nft_set_ext_add(&tmpl, NFT_SET_EXT_EXPIRATION);
nft_set_ext_add(&tmpl, NFT_SET_EXT_TIMEOUT);
nft_set_ext_add(&tmpl, NFT_SET_EXT_OBJREF);1
2
3
4static inline void nft_set_ext_add(struct nft_set_ext_tmpl *tmpl, u8 id)
{
nft_set_ext_add_length(tmpl, id, 0);
}
然后就进入到了nft_set_elem_init
1 |
|
nft_set_elem_init
开始填充nft_set_ext
1 |
|
先申请空间,加的这个elemsize不知道是什么,这时候tmpl->len包括nft_set_ext结构体头的大小+数据长度
1 |
|
nft_set_ext_init就是在复制offset
1 |
|
然后就是复制数据
1 |
|
漏洞分析
可以看到在处理data的时候
tmpl更新用的是desc.len
1
nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, desc.len);
memcpy使用的是set->dlen
1
memcpy(nft_set_ext_data(ext), data, set->dlen);
desc.len的赋值
desc的赋值
1 |
|
nft_setelem_parse_data中会检查desc->len==set->dlen,但如果是verdict则不会进行这个检查
1 |
|
set->dlen的赋值
nf_table_newset如果dtype不是verdict可以自定义dlen,只要不超过64,如果是verdict应该是16
1 |
|
漏洞点
如果往一个NFT_DATA_VALUE的set里添加一个NFT_DATA_VERDICT就可以进行溢出,最多48字节,似乎没有进行type一致的检查
src的data是nft_add_set_elem的栈上的数据,也没有进行初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15struct nft_set_elem {
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} key;
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} key_end;
union {
u32 buf[NFT_DATA_VALUE_MAXLEN / sizeof(u32)];
struct nft_data val;
} data;
void *priv;
};dest是kzalloc得到的堆块
1
2
3
4elem = kzalloc(set->ops->elemsize + tmpl->len, gfp);
// ……
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
memcpy(nft_set_ext_data(ext), data, set->dlen);
可以通过先add一个NFT_DATA_VALUE布置栈上数据,再add一个NFT_DATA_VERDICT就可以保证溢出的数据是可控的
漏洞利用
漏洞对象的大小取决于用户选项
- 20字节的head,elemsize(8)+ nft_set_ext_tmpl(2+9)+ 对齐(1)
- NFT_SET_ELEM_KEY填充28字节
- NFT_DATA_VERDICT填充16字节
这样elem位于kmalloc-64,可以溢出48字节
地址泄露
泄露内核基址
内核中有一个用于密钥管理的子系统,提供了add_key系统调用进行密钥创建,keyctl系统调用进行密钥的读取、更新、销毁等功能
1 |
|
sys_add_key中会先申请两块临时内存存储从用户空间复制过来的description和payload
description:size = 4096
1
description = strndup_user(_description, KEY_MAX_DESC_SIZE);
payload:size = 用户传来的plen
1
payload = kvmalloc(plen, GFP_KERNEL);
调用key_create_or_update
调用user_preparse其中为payload申请user_key_payload结构体
1
ret = index_key.type->preparse(&prep); // user_preparse
调用key_alloc
申请key结构体(从独立的key_jar中分配),这是密钥的主结构体
为description申请空间
1
key->index_key.description = kmemdup(desc, desclen + 1, GFP_KERNEL);
最后释放description和payload的临时空间
1
2
3
4error3:
kvfree_sensitive(payload, plen);
error2:
kfree(description);
user_key_payload结构体
1 |
|
可以通过改大datalen越界读
泄露对象
percpu_ref_data也位于kmalloc-64
1 |
|
io_uring使用这个结构体,此时
- confirm_switch和release可以泄露内核基址
- ref可以泄露physmap地址
percpu_ref_data由percpu_ref_init申请,io_ring_ctx_alloc函数申请了这个结构体
1 |
|
任意写
simple_xattr
用于存储in-memory filesystems(tmpfs)的扩展属性(xattrs - extended attribute),通过simple_xattr_alloc申请
1 |
|
1 |
|
- kmalloc-32以上
- 无法修改,修改会将旧的simple_xattr unlink,再link一个新的上去
- 所以不能通过修改next或size进行越界写
- 非特权用户无法设置simple_xattr,但系统支持user namespace也可以
unlinking attack
和堆的unsafe unlink差不多,且内核的double link没有任何检查,只要求next和prev都是合法地址
unlink的流程无非就是
1 |
|
我们能够修改p->next和p->prev,令
1 |
|
则unlink过程
1 |
|
prev指针的合法地址由physmap提供,及0xffffxxxx2f706d74是一个合法的physmap地址
识别被覆盖对象
unlink一个simple_xattr时需要一个name,因此我们需要知道哪个结构体被覆盖了,我们可以通过分配0x100大小的name,这样name指针的最低字节为\x00,在伪造list的同时覆盖name指针的低字节来识别覆盖对象
1 |
|
Exp
自己搓了一个,太长了不贴了