0x00 漏洞背景

2019年10月14日, sudo 官方在发布了 CVE-2019-14287 的漏洞预警。

sudo 是所有 unix操作系统(BSD, MacOS, GNU/Linux) 基本集成的一个用户权限控制/切换程序。允许管理员控制服务器下用户能够切换的用户权限。

CVE-2019-14287 是管理员在配置文件中用了 ALL 关键词后造成的。但默认的 sudo 配置文件不受影响。

360CERT判断漏洞等级为低危,影响面有限。

但由于是配置文件的开放性。还请服务管理/运维人员及时做好自查自检工作。

在你的Linux机器上运行一下命令。

sudo -V | grep 'Sudo version'

即可查看是否受到该版本(低于1.8.29)的影响。

0x01 漏洞详情

当 /etc/sudoers 文件存在如下形式的配置会导致漏洞的产生

user_name ALL=(ALL, !root) /usr/bin/vim

这种*=(ALL, *)形式的配置,简单来说就是对 可以被切换到的用户进行了 ALL(所有用户) 和其他用户的剔除操作。

例如上面的含义就是:

允许 user_name 用户以非 root 权限之外的所有用户权限运行 vim

这样的配置应该会出现在比较少的特殊情况。

一般情况下,大多数Linux发行版的Runas规范(/etc /sudoers)都如下图所示,其中定义的ALL关键字将允许admin或sudo组中的用户以目标系统中的任意用户身份来运行命令

1571229157_5da70de5d6ec1.png

alice myhost = (ALL) /usr/bin/id

除了以任意有效用户的身份运行id命令之外,我们还能够以任意用户ID来运行该命令,此时需要使用#uid语句:

sudo -u#1234 id -u

该命令将返回“1234”。但是,sudo可以使用setresuid(2)和setreuid(2)这两个系统调用来在命令运行之前修改用户ID,并将用户ID修改为-1(或未签名的等价用户ID-4294967295):

sudo -u#-1 id -usudo -u#4294967295 id -u

上述命令运行之后,将返回“0”。这是因为sudo命令本身已经在以用户ID“0”运行了,所以当sudo尝试将用户ID修改为“-1”时,不会发生任何变化。

但是,sudo日志条目中记录下的命令运行用户的ID为“4294967295”,而并非root用户(或用户ID为“0”),除此之外,因为用户ID是通过-u选项指定的,并且不会在密码数据库中存储,所以PAM会话模块也不会运行。

如果sudoer条目允许用户以任意用户身份运行命令(非root),那么攻击者就可以利用该漏洞来绕过这种限制了。比如说,我们有下列sudoer条目:

bob myhost = (ALL, !root) /usr/bin/vi

用户bob能够以除了root之外的其他任意用户身份来运行命令vi,但由于该漏洞的存在,bob实际上能够通过下列命令来以root权限运行vi命令,并绕过目标系统中的安全策略:

sudo -u#-1 vi

只有当包含了ALL关键词的sudoer条目存在于Runas规范中时,该漏洞才存在。比如说,如果规范中包含下列sudoer条目的话,目标系统是不会受到该漏洞影响的:

alice myhost = /usr/bin/id

在上述例子中,alice只能够以root权限运行id命令,任何以不同身份用户运行命令的尝试都将被拒绝。

0x02 源码分析

void exec_cmnd(struct command_details *details, struct command_status *cstat, int errfd)
{

    debug_decl(exec_cmnd, SUDO_DEBUG_EXEC)



    restore_signals();

    if (exec_setup(details, NULL, -1) == true) {

        /* headed for execve() */

        sudo_debug_execve(SUDO_DEBUG_INFO, details->command,

                          details->argv, details->envp);

        sudo_execve(details->command, details->argv, details->envp,

                    ISSET(details->flags, CD_NOEXEC));

        cstat->type = CMD_ERRNO;

        cstat->val = errno;

        sudo_debug_printf(SUDO_DEBUG_ERROR, "unable to exec %s: %s",

                          details->command, strerror(errno));

    }

    debug_return;

}

其中exec_setup,SUD0_DEBUG_EXEC,可以执行组ID,及创建掩码

bool exec_setup(struct command_details *details, const char *ptyname, int ptyfd)
{

    bool rval = false;

    debug_decl(exec_setup, SUDO_DEBUG_EXEC);

    unlimit_nproc();



#ifdef HAVE_SETRESUID

    if (setresuid(details->uid, details->euid, details->euid) != 0) {

    sudo_warn(U_("unable to change to runas uid (%u, %u)"), details->uid,

        details->euid);

    goto done;

    }

#elif defined(HAVE_SETREUID)

    if (setreuid(details->uid, details->euid) != 0) {

    sudo_warn(U_("unable to change to runas uid (%u, %u)"),

        (unsigned int)details->uid, (unsigned int)details->euid);

    goto done;

    }

#else

    if (seteuid(details->euid) != 0 || setuid(details->euid) != 0) {

    sudo_warn(U_("unable to change to runas uid (%u, %u)"), details->uid,

        details->euid);

    goto done;

    }

#endif /* !HAVE_SETRESUID && !HAVE_SETREUID */



    /* Restore previous value of RLIMIT_NPROC. */

    restore_nproc();



    rval = true;



done:

    debug_return_bool(rval);

}

通过源码分析

其中有三个函数可以设置用户权限

setresuid
setreuid
seteuid

其中的函数在root权限时参数可以改变为任何ID,

sudo程序最初会调用了setuid(root_uid)使程序的进程获得的root权限,通过前面的ls -l /usr/bin/sudo已经检验过了

所以这三个函数都能修改进程的用户所获得的权限。因为默认情况下sudo会将权限提升为root

在出现整数溢出的时候,-1或者4294967295则被判断为0返回为真,则使得权限升级为root。

调用setuid将我们的恶意用户设置为root,从而执行任意命令。

0x03 修复建议

  • 及时升级到 sudo 1.8.28 版本。
  • 检索/etc/sudoers 是否存在 ALL 关键词的复合限制逻辑

0x04 参考

  1. 360CERT
  2. CVE-2019-14287: Potential bypass of Runas user restrictions
  3. 雷神众测