漏洞介绍
最近的国外的团队跟踪并披露了该漏洞,报告中发现,在所有sudo版本低于1.8.29 的Linux机器,均受到该漏洞的影响。
CVE编号:CVE-2019-14287
此漏洞可以使用户拥有权限运行其他用户命令。
漏洞细节
sudo允许非特权用户以root用户身份执行命令,问题在于在sudo id 在低于1.8.29 版本中以任意用户实现了运行命令的方式,虽然攻击的利用方式,需要对本地配置进行修改,利用此漏洞需要恶意用户具有以任何用户(root用户除外)身份运行命令的特权,如果sudoers文件ALL中Runas参数带有特殊值,则可以利用成功。
漏洞分析
查看Linux机器上的Sudo版本
sudo -V | grep 'Sudo version'
ok 我们可以看到版本低于1.8.29 那么就受到这个漏洞的影响
sudo程序本身是一个设置的SUID位的二进制文件
检查一下他的权限:
ls -l /usr/bin/sudo
它的所有者是root,所以每个用户都已像root那样执行该程序。设置了SUID的程序在运行时可以给使用者以所有者的EUID
sudo的配置都记录在/etc/sudoers文件中,配置文件知名哪些用户可以执行哪些命令。要使用sudo,用户只须提供sudo用户的密码。
那么本次漏洞的命令就是
sudo -username#uidUSer
因为需要用户执行此命令,那么需要用户的sudoers中的runas说明具有特殊值ALL
查看一下配置文件/etc/sudoers
配置文件分析
%开头,代表”将要授权组“,例如其中的%sudo
%不开头的,代表”将要授权的用户“,例如其中的root
root ALL=(ALL:ALL) ALL
进行分析
第一个ALL的意思是root用户在那些服务器上登录本服务器来执行sudo命令。
第二个和第三个ALL则表示可以切换到任何(用户:组)
第四个为ALL,则表示可以执行任意命令
修改后那么在恶意用户下,则可以运行一下命令将自己升级为root
sudo -u#-1 id -u
或者
sudo -u#4294967295 id -u
在32位或者64位机器中,C语言中整数存储占用4个字节,一个字节8位,共计32位
整数在计算机中以补码形式存储,-1的补码为32个1组成的二进制数,按无符号数输出这个二进制数
就是2^32-1=4294967295
由于采用补码表示整数,计算机本身不关心整数是正数还是负数,统一按无符号数对待。具体输出时,显示为什么数,计算机按编程者的格式要求进行处理输出。如32个1组成的二进制数,按%d输出就是-1,按无符号输出就是 4294967295。
这也就是在-1和4294967295的时候 sudo 对其ID值无效,实际上他们返回的值为0
具体实践
创建的实验账号
- 添加一个系统帐号 test 作为实验所用:
useradd test
- 添加test账号密码
passwd test
用 root 身份在 /etc/sudoers 中增加:
test ALL=(ALL,!root) /usr/bin/id
表示允许 test_sudo 帐号以非 root 外的身份执行 /usr/bin/id,如果试图以 root 帐号运行 id 命令则会被拒绝
开始
Sudo的源码分析
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,从而执行任意命令
修复方法
更新sudo版本大于1.8.29