漏洞概述
在今年7月,我们从一位匿名研究员那里收到了FreeBSD中的一个本地权限提升漏洞的相关信息,即一个存在于FreeBSD的文件传输协议守护进程(ftpd)中的漏洞。ftpd提供了一个名为ftpchroot的功能,旨在限制经过身份验证的用户对文件系统的访问。这个特性是使用“chroot”系统调用实现的,这是一种通常称为“chroot jail”的安全技术,chroot jail可以将进程限制在文件系统的受限部分来运行。但是,在该漏洞的帮助下,攻击者实际上可以利用这种被“囚禁”的状态来进行各种非法操作,将其权限从受限制的FTP帐户提升为“root”权限,并允许攻击者能够在目标系统上执行任意代码。此漏洞在FreeBSD FTP守护程序中存在了很长时间,最早可以追溯到FreeBSD 6.3版本。目前,这个漏洞被标记为了CVE-2020-7468/ZDI-20-1431,相关漏洞补丁已于今年9月份正式发布。
漏洞分析
这个漏洞存在的根本原因是freebsd/libexec/ftpd/ftpd.c的chroot()函数在进行业务处理时存在设计缺陷。下面给出的是存在漏洞的函数简化版本:
代码语言:javascript复制void pass(char *passwd)
{
// ...
if (guest || dochroot) {
// ...
/*
* Finally, do chroot()
*/
if (chroot(chrootdir) < 0) {
reply(550, "Can't change root.");
goto bad;
}
__FreeBSD_libc_enter_restricted_mode();
} else /* real user w/o chroot */
homedir = pw->pw_dir;
// ...
if (chdir(homedir) < 0) {
if (guest || dochroot) {
reply(550, "Can't change to base directory.");
goto bad;
} else {
// ...
}
// ...
bad:
/* Forget all about it... */
#ifdef LOGIN_CAP
login_close(lc);
#endif
if (residue)
free(residue);
end_login();
}
如果 FTP 用户试图登录并被配置为限制在/etc/ftpchroot中的chroot jail,那么ftpd将调用chroot和chdir系统调用)。如果chdir系统调用失败,则代码将跳转到标签bad处。在这种情况下,ftpd仍然会等待新的登录,但连接已锁定在chroot jail内。此时,将导致连接上的下次登录尝试会引发错误行为。
漏洞利用
为了强制chdir系统调用在登录过程中失效,攻击者可使用命令 chmod 0 在主目录上更改权限。另外,攻击者将会上传和主页目录相关的文件“etc/spwd.db”。该文件是修改过的常规 FreeBSD 系统(包含root用户的已知密码)的密码数据库。chdir调用失败后,ftpd会被锁定在chroot jail中,以便所有后续的文件系统访问都会跟用户主目录相关,而不是真实的文件系统root路径。这样一来,当对后续登录进行认证时,ftpd读取攻击者的spwd.db而不是存储在文件系统真实root目录下的/etc/spwd.db。此时,攻击者就可以通过已知密码以root身份进行登录了。
下一步,攻击者需要上传/etc/pam.d/ftpd和/usr/lib/pam_opie.so.5。第一个文件可以让ftpd在登录过程中加载多个动态库,其中就包括这第二个文件。第二个文件旨在通过已获得的root权限来突破chroot jail并执行反向Shell。接下来,攻击者就能够以root权限来执行任意代码了。
漏洞利用步骤大致如下:
通过受限的 FTP 账户登录。
上传包含已知root密码的etc/spwd.db。
执行“chmod 0”。
再次以受限的 FTP 账户登录。在登录过程中,chdir执行失败,导致ftpd进程在chroot jail中被锁定。
通过已知密码以 root 身份登录。
上传/etc/pam.d/ftpd和/usr/lib/pam_opie.so.5,后者包含一个反向Shell。
再次以受限 FTP 账户身份登录。和之前一样,chdir执行失败,导致ftpd进程在chroot jail中被锁定。
通过已知密码以 root 身份登录。ftpd执行该反向Shell。
漏洞修复
为解决这个问题,FreeBSD对其功能实现代码进行了修改,如果chdir系统调用失败的话,则ftpd将会立刻断开连接:
代码语言:javascript复制void fatalerror(char *s)
{
reply(451, "Error in server: %s", s);
reply(221, "Closing connection due to server error.");
dologout(0);
/* NOTREACHED */
}
void pass(char *passwd)
{
// ...
if (chdir(homedir) < 0) {
if (guest || dochroot) {
fatalerror("Can't change to base directory.");
} else {
//...
}
总结
这个漏洞是一个逻辑提权漏洞,因此它的稳定性非常强,这个漏洞跟九月份发布的FreeBSD 内核提权漏洞(CVE-2020-7460)也是不一样的。