Netfilter毛病提权利用(CVE-2023-35001)

打印 上一主题 下一主题

主题 898|帖子 898|积分 2694

前言

Netfilter是一个用于Linux操纵系统的网络数据包过滤框架,它提供了一种灵活的方式来管理网络数据包的流动。Netfilter允许系统管理员和开发人员控制数据包在Linux内核中的处置惩罚方式,以实现网络安全、网络地址转换(Network Address Translation,NAT)、数据包过滤等功能。
毛病成因

在netfilter中存在这nft_byteorder_eval函数,该函数的作用是将寄存器中的数据以主机序或网络序存储。具体代码如下,若接纳的操纵是NFT_BYTEORDER_NTOH则是将数据从主机序转化为网络序,而NFT_BYTEORDER_HTON则是从网络序转换为主机序。具体转换多少个字节则是用priv->size指定的,在该操纵下可以转换二、四、八字节。该毛病也是由于在对两字节数据进行大小端序转存时出现了错误所导致的。
可以看到代码【1】中使用了团结体存储了源地址和目的地址,团结体的变量分别是u32与u16分别代表的是四字节与两字节的空间大小。然后在代码【2】与【3】处源地址是直接取出u16的变量存储到目的地址的u16变量中。
乍一看似乎很符合常理,因为在处置惩罚双字节的时候,团结体中的变量就以u16存储,若处置惩罚四字节就转化为u32存储,但是这里存在个问题,在C语言中,团结体的存储空间是以最大空间为尺度,换句话说无论团结体取出的变量是u16还是u32,团结体的大小都是占用四个字节的,而不会出现双字节的情况,因此在对s与d两个团结体进行遍历时,会以四字节为单位找到下一个位置。但是在计算长度时是以双字节进行计算的,因此就会导致拷贝时发生溢出。
  1. File: linux-5.19\net\netfilter\nft_byteorder.c
  2. 26: void nft_byteorder_eval(const struct nft_expr *expr,
  3. 27:             struct nft_regs *regs,
  4. 28:             const struct nft_pktinfo *pkt)
  5. 29: {
  6.    ...
  7. 33:     【1】union { u32 u32; u16 u16; } *s, *d; //使用联合体存储源地址与目的地址
  8.    ...
  9. 39:     switch (priv->size) {
  10.    ...
  11. 72:     case 2:
  12. 73:         switch (priv->op) {
  13. 74:         case NFT_BYTEORDER_NTOH:
  14. 75:             for (i = 0; i < priv->len / 2; i++)
  15. 76:                 【2】d[i].u16 = ntohs((__force __be16)s[i].u16);//将源地址的数据拷贝到目的地址的低16位中
  16. 77:             break;
  17. 78:         case NFT_BYTEORDER_HTON:
  18. 79:             for (i = 0; i < priv->len / 2; i++)
  19. 80:                 【3】d[i].u16 = (__force __u16)htons(s[i].u16);
  20. 81:             break;
  21. 82:         }
  22. 83:         break;
  23. 84:     }
  24. 85: }
复制代码
举个例子,我们自定义一个团结体数组dest,分别向下标0、1以及2进行赋值。
  1. union {short a;long b;} dst[10];
  2. int main()
  3. {
  4.     dst[0].a = 0x1122;
  5.     dst[1].a = 0x3344;
  6.     dst[2].a = 0x5566;
  7.    
  8.     return 0;
  9. }
复制代码
按照假想的情况,在使用双字节变量进行遍历的时候会以双字节为单位进行遍历,但是实际的情况如下图。可以发现纵然每次赋值都是对双字节的变量进行赋值,但是再遍历的时候还是按照团结体中最大的存储空间(四字节)进行遍历的。
[img=720,419.25187032418955]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018135.png[/img]

因此毛病的成因如下,因此在使用nft_byteorder函数转换双字节的大小端序时溢出。
[img=720,395.0902527075812]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018136.png[/img]

模块地址泄漏

在nft_byteorder_eval函数内部,溢出的地址是在寄存器下方。因此可以通过控制寄存器的下标值选择需要泄漏的地址。
[img=720,678.1935483870968]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018137.png[/img]

在此需要观察通过nft_byteorder_eval函数可以溢出的范围,priv->len是可以人为控制的,只要满足reg * 4 + priv->len len能设置最大的值,(0x40 / 2) * 4 = 0x80,因此(0xaf8 ~ 0xaf8 + 0x80)范围内都是可以访问到的。但是现在存在一个问题,虽然我们可以越界访问,但是每次只能获取四字节中的低两个字节。
  1. ...
  2. 75:             for (i = 0; i < priv->len / 2; i++)
  3. 76:                 【2】d[i].u16 = ntohs((__force __be16)s[i].u16);//将源地址的数据拷贝到目的地址的低16位中
  4. ...
复制代码
将下列值传参给nft_byteorder_eval函数
  1. /*
  2.     dst:18
  3.     src:8
  4.     priv->op:NFT_BYTEORDER_HTON
  5.     priv->len:24
  6.     priv->size:2
  7. */
  8. rule_add_byteorder(r, 18, 8, NFT_BYTEORDER_HTON, 24, 2);
复制代码
泄漏的值如下,可以发现高两个字节的值是无法泄漏的,因为在nft_byteorder_eval中,每次只拷贝了u16的变量。因此每次泄漏只能获取低两字节的值。因此需要寻找其他方法进行地址的泄漏。
[img=720,194.4058205335489]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018138.png[/img]

nf_trace_fill_rule_info函数用于跟踪数据包,并且会将rule->handle的值放进数据包中回传给用户。
想要正常实行nf_trace_fill_rule_info函数需要绕过条件

  • rule不能为空,并且rule->is_last需要为0,即当前rule不是最后一个
  • info->type不能是NFT_TRACETYPE_RETURN以及info->verdict->code不能NFT_CONTINUE
  1. /*函数递归
  2. nft_do_chain
  3. ->
  4. nft_trace_packet
  5. ->
  6. __nft_trace_packet
  7. ->
  8. nft_trace_notify
  9. ->
  10. nf_trace_fill_rule_info
  11. */
  12. File: linux-5.19\net\netfilter\nf_tables_trace.c
  13. 126: static int nf_trace_fill_rule_info(struct sk_buff *nlskb,
  14. 127:                   const struct nft_traceinfo *info)
  15. 128: {
  16. 129:    if (!info->rule || info->rule->is_last)
  17. 130:        return 0;
  18. 131:
  19. 132:    /* a continue verdict with ->type == RETURN means that this is
  20. 133:     * an implicit return (end of chain reached).
  21. 134:     *
  22. 135:     * Since no rule matched, the ->rule pointer is invalid.
  23. 136:     */
  24. 137:    if (info->type == NFT_TRACETYPE_RETURN &&
  25. 138:        info->verdict->code == NFT_CONTINUE)
  26. 139:        return 0;
  27. 140:
  28. 141:    return nla_put_be64(nlskb, NFTA_TRACE_RULE_HANDLE,
  29. 142:                cpu_to_be64(info->rule->handle),
  30. 143:                NFTA_TRACE_PAD);
  31. 144: }
复制代码
因此想要通过nf_trace_fill_rule_info函数获取数据的第一步是伪造rule。
【----资助网安学习,以下全部学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习发展路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC毛病分析报告
 ④ 150+网安攻防实战技能电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
在regs变量的下方存在jumpstack变量
[img=720,419.7704667176741]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018139.png[/img]

布局体nft_jumpstack的构成如下,由chain、rule、last_rule构成,并且该布局体变量在regs下方,并且通过byteorder操纵可以访问到jumpstack布局体,那么利用byteorder操纵篡改rule。
  1. struct nft_jumpstack {
  2.     const struct nft_chain *chain;
  3.     const struct nft_rule_dp *rule;
  4.     const struct nft_rule_dp *last_rule;
  5. };
复制代码
接下来看一下nft_rule_dp布局体,可以发现is_last是调用nf_trace_fill_rule_info函数的条件,handle是泄漏的值。
  1. struct nft_rule_dp {
  2.     u64             is_last:1,
  3.                     dlen:12,
  4.                     handle:42;  /* for tracing */
  5.     unsigned char           data[]
  6.         __attribute__((aligned(__alignof__(struct nft_expr))));
  7. };
复制代码
在进入nf_trace_fill_rule_info函数内部前需要经历规则与表达式的遍历。
  1. File: linux-5.19\net\netfilter\nf_tables_core.c
  2. 255:    for (; rule < last_rule; rule = nft_rule_next(rule)) { //遍历rule
  3. 256:        nft_rule_dp_for_each_expr(expr, last, rule) { //遍历expr
  4. 257:            if (expr->ops == &nft_cmp_fast_ops)
  5. 258:                nft_cmp_fast_eval(expr, &regs);
  6. 259:            else if (expr->ops == &nft_cmp16_fast_ops)
  7. 260:                nft_cmp16_fast_eval(expr, &regs);
  8. 261:            else if (expr->ops == &nft_bitwise_fast_ops)
  9. 262:                nft_bitwise_fast_eval(expr, &regs);
  10. 263:            else if (expr->ops != &nft_payload_fast_ops ||
  11. 264:                 !nft_payload_fast_eval(expr, &regs, pkt))
  12. 265:                expr_call_ops_eval(expr, &regs, pkt); //执行expr->ops
  13. 266:
  14. 267:            if (regs.verdict.code != NFT_CONTINUE)
  15. 268:                break;
  16. 269:        }
  17. 270:
  18. 271:        switch (regs.verdict.code) {
  19. 272:        case NFT_BREAK:
  20. 273:            regs.verdict.code = NFT_CONTINUE;
  21. 274:            nft_trace_copy_nftrace(&info);
  22. 275:            continue;
  23. 276:        case NFT_CONTINUE:
  24. 277:            nft_trace_packet(&info, chain, rule,
  25. 278:                     NFT_TRACETYPE_RULE); //跟踪数据包
  26. 279:            continue;
  27. 280:        }
  28. 281:        break;
  29. 282:   
复制代码
遍历规则的宏定义如下,若是rule->dlen没有进行改写,那么会根据rule->dlen找到下一个rule,但是当前的rule是伪造的,因此会导致在取出expr会报错。倘若将rule->dlen修改为0,则下个rule的位置就是当前rule + 8。
由于不定长数组unsigned char data[],在sizeof操纵中的值为0,因此sizeof(*rule)的值为8。此时将last_rule改写成rule + 8就可以直接跳出循环。
  1. #define nft_rule_next(rule)     (void *)rule + sizeof(*rule) + rule->dlen
复制代码
在完场上述流程后,就可以顺利进入nft_trace_packet函数内部,nft_trace_packet函数也比力简单,实际是调用了__nft_trace_packet函数
  1. File: linux-5.19\net\netfilter\nf_tables_core.c
  2. 37: static inline void nft_trace_packet(struct nft_traceinfo *info,
  3. 38:                     const struct nft_chain *chain,
  4. 39:                     const struct nft_rule_dp *rule,
  5. 40:                     enum nft_trace_types type)
  6. 41: {
  7. 42:     if (static_branch_unlikely(&nft_trace_enabled)) {
  8. 43:         const struct nft_pktinfo *pkt = info->pkt;
  9. 44:
  10. 45:         info->nf_trace = pkt->skb->nf_trace;
  11. 46:         info->rule = rule;
  12. 47:         __nft_trace_packet(info, chain, type);
  13. 48:     }
  14. 49: }
复制代码
可以发现想要进入nft_trace_notify函数需要满足info->trace或info->trace不为空。
  1. File: linux-5.19\net\netfilter\nf_tables_core.c
  2. 24: static noinline void __nft_trace_packet(struct nft_traceinfo *info,
  3. 25:                     const struct nft_chain *chain,
  4. 26:                     enum nft_trace_types type)
  5. 27: {
  6. 28:     if (!info->trace || !info->nf_trace)
  7. 29:         return;
  8. 30:
  9. 31:     info->chain = chain;
  10. 32:     info->type = type;
  11. 33:
  12. 34:     nft_trace_notify(info);
  13. 35: }
复制代码
使用meta表达式可以设置skb->nf_trace,将skb->nf_trace设置为非空就可以进入到nft_trace_notify函数。
  1. File: linux-5.19\net\netfilter\nft_meta.c
  2. ...
  3. 443:    case NFT_META_NFTRACE:
  4. 444:        value8 = nft_reg_load8(sreg);
  5. 445:
  6. 446:        skb->nf_trace = !!value8;
  7. 447:        break;
  8. ...
复制代码
在nft_trace_notify函数内部,还会判断是否订阅NFNLGRP_NFTRACE。没订阅则无法继续实行。
  1. File: linux-5.19\net\netfilter\nf_tables_trace.c
  2.    ...
  3. 176:    if (!nfnetlink_has_listeners(nft_net(pkt), NFNLGRP_NFTRACE))
  4. 177:        return;
  5.     ...
复制代码
在libnml库中使用mnl_socket_setsockopt函数进行netlink的组订阅,由于在使用宏NFNLGRP_NFTRACE编译时会提示找不到该值,因此这里使用实际值代替了。
  1. static int group = 9;
  2. if (mnl_socket_setsockopt(nleak, NETLINK_ADD_MEMBERSHIP, &group,
  3.                   sizeof(int)) < 0) {
  4.         perror("mnl_socket_setsockopt");
  5.         exit(EXIT_FAILURE);
  6. }
复制代码
接下来就需要具体怎样伪造rule,通过byteorder操纵可以首先可以将原先的chain、rule以及last_rule的地址泄漏,但是只能泄漏四字节。
[img=720,531.1318242343542]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018140.png[/img]

由于我们需要找到符合上述条件的rule,并且我们只有rule的最低两个字节,因此搜刮范围不大,因此需要在泄漏的rule_low附近寻找一个符合的模块地址。在存储泄漏的rule之前存储利用immediate以及meta_set操纵,我们选择此中一个进行泄漏即可。
[img=720,260.1076095311299]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018142.png[/img]

伪造的方式也比力简单,由于is_last与dlen都需要设置为0,因此我们只需要找到两个字节为0的值,作为伪造的rule即可,伪造的rule如下。
[img=720,235.2047952047952]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018143.png[/img]

修改后的结果如下
[img=720,243.16981132075472]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018144.png[/img]

由于handle实际是占用42比特,但是有3个比特被设置为0了,因此实际泄漏的值只有39比特,但是由于模块地址的高4个字节都是固定的0xffffffff,因此不影响模块地址的泄漏。通过从数据包中提取数据得到handle的值为后,简单移位操纵就可以还原。
  1. module = ((leak << 13)  >> 16);
复制代码
最后泄漏模块基地址乐成。
[img=720,224.59893048128342]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202404251018145.png[/img]

总结

总结一下模块基地址的泄漏流程
1. 构造基础链
设置NFT_JUMP表达式
通过meta设置为NFT_META_NFTRACE
2. 泄漏链
byteorder表达式触发毛病,第一次读chain、rule以及last_rule,第二次改写为chain,fake rule以及fake last_rule
dynset表达式泄漏chain、rule、last_rule
3. 订阅NFNLGRP_NFTRACE组,吸收数据包
4. 后续接着分享怎样绕过kaslr以及最终提权的利用。
原版exp使用go语言写的,我使用c语言重写了一版。
完整exp:https://github.com/h0pe-ay/Vulnerability-Reproduction/tree/master/CVE-2023-35001(nftables)(c语言)
更多网安技能的在线实练习习,请点击这里>>
  

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

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

乌市泽哥

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