CVE-2022-2586 nf_tables UAF 漏洞分析

渗透技巧 1年前 (2023) admin
729 0 0

‍漏洞简介

漏洞编号: CVE-2022-2586
漏洞产品: linux kernel – netfilter
利用条件: CAP_NET_ADMIN
利用效果: 本地提权

漏洞分析

成因

使用 NFTA_SET_ELEM_OBJREF 请求可以让 set 保存指向一个位于其他 table 中的 object,如果 set 和 object 不在同一张表中,并且释放 object 所在的表,会把 object 也释放掉,但是没有清理 set 中保存的 object 指针,导致后面内核通过 set 来引用 object 指针时会触发 UAF.

CVE-2022-2586 nf_tables UAF 漏洞分析

CVE-2022-2586 nf_tables UAF 漏洞分析

触发 UAF 的步骤:

  1. 首先创建两个 table (NFT_MSG_NEWTABLE)

  2. 使用 NFT_MSG_NEWOBJ 分配一个 object 并将其链接到 table_1->objects.

  3. 使用 NFT_MSG_NEWSET 分配一个 set 并将其链接到 table_2->sets

  4. 使用 NFT_MSG_NEWSETELEM 和 NFTNL_SET_ELEM_OBJREF 让 set 中保存对 obj 的指针.

  5. 使用 NFT_MSG_DELTABLE 释放 table_1 ,table_1 和 obj 都会被释放,此时 set 中依然保存着 obj 的指针.

相关代码分析

创建 table

用户态使用 NFT_MSG_NEWTABLE 请求可以创建 table

    [NFT_MSG_NEWTABLE] = {
        .call_batch = nf_tables_newtable,
        .attr_count = NFTA_TABLE_MAX,
        .policy     = nft_table_policy,
    },

nft_table_policy 函数的关键代码如下

static int nf_tables_newtable(struct net *net, struct sock *nlsk,
                  struct sk_buff *skb, const struct nlmsghdr *nlh,
                  const struct nlattr * const nla[],
                  struct netlink_ext_ack *extack)

{
    attr = nla[NFTA_TABLE_NAME];
    table = nft_table_lookup(net, attr, family, genmask);
    if (IS_ERR(table)) {
        if (PTR_ERR(table) != -ENOENT)
            return PTR_ERR(table);
    } else {
        // 表已经存在, 更新表
    }

    // 新建表
    table = kzalloc(sizeof(*table), GFP_KERNEL);
    // 初始化 table
    table->name = nla_strdup(attr, GFP_KERNEL);

    if (nla[NFTA_OBJ_USERDATA]) {
        // memdup 原语
        obj->udata = nla_memdup(nla[NFTA_OBJ_USERDATA], GFP_KERNEL);
        obj->udlen = nla_len(nla[NFTA_OBJ_USERDATA]);
    }

    err = nft_trans_table_add(&ctx, NFT_MSG_NEWTABLE);

    // 将表挂在 net->nft.tables 链表中
    list_add_tail_rcu(&table->list, &net->nft.tables);
    return 0;
}


CVE-2022-2586 nf_tables UAF 漏洞分析

创建 object

相关代码

static int nf_tables_newobj(struct net *net, struct sock *nlsk,
                struct sk_buff *skb, const struct nlmsghdr *nlh,
                const struct nlattr * const nla[],
                struct netlink_ext_ack *extack)

{

    // 找到要链接的表
    table = nft_table_lookup(net, nla[NFTA_OBJ_TABLE], family, genmask);

    // 分配 object
    obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]);

    obj->key.table = table;
    obj->handle = nf_tables_alloc_handle(table);

    // 分配 obj->key.name
    obj->key.name = nla_strdup(nla[NFTA_OBJ_NAME], GFP_KERNEL);

    if (nla[NFTA_OBJ_USERDATA]) {
        // memdup 原语
        obj->udata = nla_memdup(nla[NFTA_OBJ_USERDATA], GFP_KERNEL);
        obj->udlen = nla_len(nla[NFTA_OBJ_USERDATA]);
    }

    // 将 object 插入到 hash 表和 table->objects
    err = rhltable_insert(&nft_objname_ht, &obj->rhlhead,
                  nft_objname_ht_params);

    list_add_tail_rcu(&obj->list, &table->objects);
    return 0;

}


CVE-2022-2586 nf_tables UAF 漏洞分析

分配 set
static int nf_tables_newset(struct net *net, struct sock *nlsk,
                struct sk_buff *skb, const struct nlmsghdr *nlh,
                const struct nlattr * const nla[],
                struct netlink_ext_ack *extack)

{
    // 找表
    table = nft_table_lookup(net, nla[NFTA_SET_TABLE], family, genmask);

    ops = nft_select_set_ops(&ctx, nla, &desc, policy);
    if (IS_ERR(ops))
        return PTR_ERR(ops);

    udlen = 0;
    if (nla[NFTA_SET_USERDATA])
        udlen = nla_len(nla[NFTA_SET_USERDATA]);

    // 分配 set
    set = kvzalloc(sizeof(*set) + size + udlen, GFP_KERNEL);
    if (!set)
        return -ENOMEM;

    // 申请 set 中的 expr
    if (nla[NFTA_SET_EXPR]) {
    } else if (nla[NFTA_SET_EXPRESSIONS]) {
    }

    udata = NULL;
    if (udlen) {
        udata = set->data + size;
        nla_memcpy(udata, nla[NFTA_SET_USERDATA], udlen);
    }

    // 初始化 set

    // 将 set 链接到 table->sets
    list_add_tail_rcu(&set->list, &table->sets);
    return 0;

}
往 set 中插入元素

首先会进入 nf_tables_newsetelem 找到要操作的 set,然后一个一个往里面增加 elem

static int nf_tables_newsetelem(struct net *net, struct sock *nlsk,
                struct sk_buff *skb, const struct nlmsghdr *nlh,
                const struct nlattr * const nla[],
                struct netlink_ext_ack *extack)

{
    // 找到需要操作的 set
    set = nft_set_lookup_global(net, ctx.table, nla[NFTA_SET_ELEM_LIST_SET],
                    nla[NFTA_SET_ELEM_LIST_SET_ID], genmask);

    // 一个一个加元素
    nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
        err = nft_add_set_elem(&ctx, set, attr, nlh->nlmsg_flags);
        if (err < 0)
            return err;
    }

}

nft_add_set_elem 漏洞相关代码如下

static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                const struct nlattr *attr, u32 nlmsg_flags)

{
    if (nla[NFTA_SET_ELEM_OBJREF] != NULL) {
        // 找到 object
        obj = nft_obj_lookup(ctx->net, ctx->table,
                     nla[NFTA_SET_ELEM_OBJREF],
                     set->objtype, genmask);
        nft_set_ext_add(&tmpl, NFT_SET_EXT_OBJREF);
    }

    elem.priv = nft_set_elem_init(set, &tmpl, elem.key.val.data,
                      elem.key_end.val.data, elem.data.val.data,
                      timeout, expiration, GFP_KERNEL);
    ext = nft_set_elem_ext(set, elem.priv);

    if (obj) {
        *nft_set_ext_obj(ext) = obj;  // [0] ext 里面保存了 object 的指针
        obj->use++;
    }

    // [1] 将 elem 插入到 set 中
    err = set->ops->insert(ctx->net, set, &elem, &ext2);
    return 0;
}

如果请求中包含了 NFTA_SET_ELEM_OBJREF,在 [0] 处会将 object 指针保存到 ext (通过 set 和 elem 可以获取 )里面,然后 [1] 处会将 elem 插入到 set 中.

释放 table

nf_tables_deltable 会调用 nft_flush_table 释放 table 和 table 中的相关元素.

static int nf_tables_deltable(struct net *net, struct sock *nlsk,
                  struct sk_buff *skb, const struct nlmsghdr *nlh,
                  const struct nlattr * const nla[],
                  struct netlink_ext_ack *extack)

{
    // 找到 table
    table = nft_table_lookup(net, attr, family, genmask);

    ctx.family = family;
    ctx.table = table;

    return nft_flush_table(&ctx);
}

static int nft_flush_table(struct nft_ctx *ctx)
{
    list_for_each_entry_safe(set, ns, &ctx->table->sets, list) {
        // 删除 table 中的 set
        err = nft_delset(ctx, set);
    }

    list_for_each_entry_safe(obj, ne, &ctx->table->objects, list) {
        // 删除 table 中的 object
        err = nft_delobj(ctx, obj);
    }

    // 释放 table
    err = nft_deltable(ctx);
}
释放 object

nft_delobj 用于删除 object,函数就是通过 nft_trans_obj_add 往 net->nft.commit_list 里面插入了一个节点,消息类型为 NFT_MSG_DELOBJ.

static int nft_delobj(struct nft_ctx *ctx, struct nft_object *obj)
{
    int err;

    err = nft_trans_obj_add(ctx, NFT_MSG_DELOBJ, obj);
    return err;
}

static int nft_trans_obj_add(struct nft_ctx *ctx, int msg_type,
                 struct nft_object *obj)

{
    struct nft_trans *trans;

    trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_obj));
    nft_trans_obj(trans) = obj;
    list_add_tail(&trans->list, &ctx->net->nft.commit_list);

    return 0;
}

消息处理函数为 nf_tables_commit

nfnetlink_rcv_batch --> ss->commit --> nf_tables_commit

static int nf_tables_commit(struct net *net, struct sk_buff *skb)
{
    list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
        switch (trans->msg_type) {

        case NFT_MSG_DELOBJ:
            nft_obj_del(nft_trans_obj(trans));  // unlink object
            nf_tables_obj_notify(&trans->ctx, nft_trans_obj(trans),
                         NFT_MSG_DELOBJ);
            break;

    }

    nf_tables_commit_release(net);
    return 0;
}


static void nft_obj_del(struct nft_object *obj)
{
    rhltable_remove(&nft_objname_ht, &obj->rhlhead, nft_objname_ht_params);
    list_del_rcu(&obj->list);
}

nf_tables_commit 首先调用 nft_obj_del 把 object 从 obj->list 中摘下来,然后再通过 nf_tables_commit_release 完成具体的释放

static void nf_tables_commit_release(struct net *net)
{
    struct nft_trans *trans;

    // 将这次的 commit_list 放到 nf_tables_destroy_list
    spin_lock(&nf_tables_destroy_list_lock);
    list_splice_tail_init(&net->nft.commit_list, &nf_tables_destroy_list);
    spin_unlock(&nf_tables_destroy_list_lock);

    // 执行内核 work 完成具体的释放,处理函数 nf_tables_trans_destroy_work
    schedule_work(&trans_destroy_work);
}

在内核 work 里面会 nf_tables_trans_destroy_work –> nft_commit_release –> nft_obj_destroy 完成对 obj 的释放.

nf_tables_trans_destroy_work --> nft_commit_release

static void nft_commit_release(struct nft_trans *trans)
{
    switch (trans->msg_type) {
    case NFT_MSG_DELOBJ:
        nft_obj_destroy(&trans->ctx, nft_trans_obj(trans));
        break;
}

static void nft_obj_destroy(const struct nft_ctx *ctx, struct nft_object *obj)
{
    if (obj->ops->destroy)
        obj->ops->destroy(ctx, obj);

    module_put(obj->ops->type->owner);
    kfree(obj->key.name);
    kfree(obj->udata);
    kfree(obj);
}
获取 ext 中 obj 的 name
static int nf_tables_fill_setelem(struct sk_buff *skb,
                  const struct nft_set *set,
                  const struct nft_set_elem *elem)

{
    const struct nft_set_ext *ext = nft_set_elem_ext(setelem->priv);
    unsigned char *b = skb_tail_pointer(skb);
    struct nlattr *nest;


    if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF) &&
        nla_put_string(skb, NFTA_SET_ELEM_OBJREF,
               (*nft_set_ext_obj(ext))->key.name) < 0)  // []
        goto nla_put_failure;

利用分析

  1. 触发 UAF 后 obj 和 obj->key.name 会被释放,但是 obj 所在内存没有被清零.

  2. 因此当 obj 被释放后,用户态通过 NFT_MSG_GETSETELEM 获取 obj->key.name 时依然会拿到已经释放的 obj->key.name 指针,且 obj->key.name 的内存大小可控.(利用释放内存中的残留数据)

  3. 让 obj->key.name 大小为 0x20,触发 UAF 后堆喷 seq_operations ,占位 name 所在的内存空间,然后读取 obj->key.name 拿到其中的函数指针,泄露内核基地址.

  4. 控制 obj->key.name 大小,触发 UAF ,然后堆喷 nft_object 让其落入 UAF 的内存,然后泄露 obj->list.next ,得到 ctx->table 的地址.

  5. 触发 UAF ,然后堆喷内存,占位 object 所在的内存空间,修改 object->key.name 指针为 table 的地址,泄露 object 堆地址

  6. 释放堆喷的 object,然后利用 memdup 原语喷 nft_object_ops,就可以在泄露的地址处布置函数指针

  7. 劫持函数指针,ROP 修改 modprobe_path 提权.

补丁分析

object 不能被其他 table 对象引用.

--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -3842,6 +3842,7 @@ static struct nft_set *nft_set_lookup_byhandle(const struct nft_table *table,
}
static struct nft_set *nft_set_lookup_byid(const struct net *net,
+                                                        const struct nft_table *table,
                                                          const struct nlattr *nla, u8 genmask)

{
           struct nftables_pernet *nft_net = nft_pernet(net);
@@ -3853,6 +3854,7 @@ static struct nft_set *nft_set_lookup_byid(const struct net *net,
                                 struct nft_set *set = nft_trans_set(trans)
;
                                 if (id == nft_trans_set_id(trans) &&
+                                   set->table == table &&
                                     nft_active_genmask(set, genmask))
                                            return set;
                      }
@@ -3873,7 +3875,7 @@ struct nft_set *nft_set_lookup_global(const struct net *net,
                      if (!nla_set_id)

                                 return set
;
-                    set = nft_set_lookup_byid(net, nla_set_id, genmask);
+                    set = nft_set_lookup_byid(net, table, nla_set_id, genmask);
           }
           return set;
}

参考

‍https://www.openwall.com/lists/oss-security/2022/08/29/5

本文仅代表作者本人观点,用于技术探讨和交流,如有谬误,欢迎指正!


原文始发于微信公众号(华为安全应急响应中心):CVE-2022-2586 nf_tables UAF 漏洞分析

版权声明:admin 发表于 2023年2月15日 下午5:35。
转载请注明:CVE-2022-2586 nf_tables UAF 漏洞分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...