0x00 绪论
2020年2月初,ZDI收到一份报告,报告中描述了Oracle VirtualBox所使用的libalias数据包别名库中的越界访问漏洞。报告人是研究者Vishnu Dev TJ,修复后分配的编号是CVE-2020-7454。分析报告时,我发现漏洞也存在于FreeBSD,本文将讨论VirtualBox和FreeBSD中的CVE-2020-7454漏洞,展示维护第三方库以及共享代码之难。
对于不熟悉libalias库的人,这里简要介绍一下。libalias是用于IP数据包别名与解别名(aliasing and de-aliasing)的一个库,此外还用于地址伪装和NAT。既然有地址伪装和NAT的功能,就不难理解为何VirtualBox要使用这个库。不过,libalias源自FreeBSD,VirtualBox方面则维护着自己的libalias分支。不幸的是,本漏洞在两个版本上都有。它会导致FreeBSD内核模式和用户模式的越界访问。漏洞在VirtualBox 6.1.6和FreeBSD-SA-20:12中修复。
0x01 考察Oracle VirtualBox
以下分析基于VirtualBox 6.1.4。漏洞的根源在AliasHandleUdpNbtNS()
函数中,该函数负责解析UDP 137端口上的NetBIOS名字服务数据包。以下是简化的相关代码:
AliasHandleUdpNbtNS(...)
{
/* ... 略 ... */
/* 计算UDP包数据长度 */
uh = (struct udphdr *)ip_next(pip);
nsh = (NbtNSHeader *)udp_next(uh);
p = (u_char *) (nsh + 1);
pmax = (char *)uh + ntohs(uh->uh_ulen); /* <--- (1) */
/* ... 略 ... */
if (ntohs(nsh->ancount) != 0) {
p = AliasHandleResource(
ntohs(nsh->ancount),
(NBTNsResource *) p,
pmax,
&nbtarg
);
}
/* ... 略 ... */
}
AliasHandleResource(..., char *pmax, ...)
{
/* ... 略 ... */
switch (ntohs(q->type)) {
case RR_TYPE_NB:
q = (NBTNsResource *) AliasHandleResourceNB(
q,
pmax,
nbtarg
);
break;
/* ... 略 ... */
}
在上面代码的(1)处,uh_ulen
是UDP首部长度字段,是从不可信的客户机发来的,其最大值为0xFFFF。如果把uh_ulen的值设得很大,攻击者就可以产生过大的pmax
值。而后,如果UDP包中含有应答资源记录(Answer Resource Records),且类型为NetBIOS通用服务(NetBIOS General Service),则执行会进入theAliasHandleResourceNB()
函数:
AliasHandleResourceNB(..., char *pmax, ...)
{
/* ... 略 ... */
while (nb != NULL && bcount != 0) {
if ((char *)(nb + 1) > pmax) { /* <--- (2) */
nb = NULL;
break;
}
if (!bcmp(&nbtarg->oldaddr, &nb->addr, sizeof(struct in_addr))) { /* <--- (3) /
/* ... 略 ... */
nb->addr = nbtarg->newaddr; /* <--- (4) */
}
/* ... 略 ... */
nb = (NBTNsRNB *) ((u_char *) nb + SizeOfNsRNB);
}
}
在上面代码的(2)处,while
循环试图寻找包中的旧地址,将其替换为新地址,直到pmax
为止。因为pmax
的值过大,所以(3)处发生了越界读。如果旧地址找到的话,在(4)处甚至可能越界写。
客户机上的攻击者可以构造不合理的UDP首部长度,以在主机上触发越界访问。UDP端口137在VirtualBox的默认配置下是打开的。为解决此问题,Oracle在上面第一份代码的(1)处加入了UDP首部长度验证。
0x02 考察FreeBSD 12.1
如上所述,libalias库源自FreeBSD。分析Oracle VirtualBox中的此漏洞时,我发现在使用ipfw
做NAT时,此漏洞还影响到FreeBSD。ipfw
包过滤器包含两种NAT方法:一种在内核,一种在用户空间。两种实现用的都是libalias提供的同一个函数。这就表示漏洞可在内核或者用户空间程序(natd
)中触发,具体在哪取决于NAT配置。
以下是触发内核中的漏洞所需的FreeBSD 12.1相关配置:
/boot/loader.conf
alias_nbt_load="YES"
/etc/rc.conf
gateway_enable="YES"
firewall_enable="YES"
firewall_nat_enable="YES"
firewall_nat_interface="em0"
firewall_type="OPEN"
越界访问发生在alias_nbt.ko
中,这是已加载进内核的模块。
如果NAT配置是在用户空间的话,越界访问发生在natd
进程的libalias_nbt.so
中。这两种情况都可以远程触发,无需身份认证。
分析中我还发现一个惊喜。FreeBSD中的libalias包含这个漏洞的另一个变体,位于对CuSeeMe协议的处理过程中,这个协议默认监听UDP端口7648。
AliasHandleCUSeeMeIn(...)
{
/* ... snip ... */
end = (char *)ud + ntohs(ud->uh_ulen); /* <--- untrusted UDP header length */
if ((char *)oc <= end) {
/* ... snip ... */
if (ntohs(cu->data_type) == 101)
/* Find and change our address */
for (i = 0; (char *)(ci + 1) <= end && i < oc->client_count; i++, ci++)
if (ci->address == (u_int32_t) alias_addr.s_addr) { /* <--- OOBR */
ci->address = (u_int32_t) original_addr.s_addr; /* <--- OOBW */
break;
}
}
}
然而,这个漏洞不存在于VirtualBox中,所以对FreeBSD的补丁和VirtualBox的补丁会有所不同。在UdpAliasIn()
和UdpAliasOut()
中都加入了验证,这里是处理UDP包的合适的层次,这样就把任何包含这种漏洞的协议都修补了。
0x03 总结
这次案例分析展示了维护第三方库以及共享代码何其之难。即使源代码打了补丁或者更新了,这些改动还必须反映到上游产品之中。就算你和第三方的代码同步了,共享代码里的一个漏洞反而会有双倍的影响,因为使用共享代码的双方都受到影响。Oracle VirtualBox在用户和安全研究者中越来越受欢迎。再次感谢Vishnu Dev TJ报告此漏洞和其他VirtualBox漏洞。我们期望在未来收到他的更多报告。