文章来源:嘶吼专业版
0x00 前言
近期,我在SaltStack的Salt中发现了一个命令注入漏洞,当master调用restartcheck时,该漏洞允许通过特制的进程名称对minion进行特权提升。
受影响的版本:自2016.3.0rc2到3002.5之间的所有版本
CVSS评分:7.0 高危
官方公告:
https://saltproject.io/security_announcements/active-saltstack-cve-release-2021-feb-25/
PoC:
https://github.com/stealthcopter/CVE-2020-28243
在这篇文章中,我将重点介绍以下几部分内容:
1、发现:我是如何发现这个漏洞的?
2、漏洞详情:为什么这个漏洞有效?
3、利用方式:应该如何利用这个漏洞?
4、安全问题:包括容器逃逸、未经身份验证的远程代码执行
5、时间节点:这一过程花了多长时间?
0x01 关于SaltStack Salt
SaltStack Salt是一个流行工具,用于自动化和保护基础结构,其使用过程分为两个角色。其中,一个系统设置为master,负责控制连接到该系统的其他系统。其中的一个或多个系统被设置为minion,与master连接并响应其发出的任意命令。
master和minion通常都是以root身份在其安装的系统上运行。
0x02 漏洞发现
在查看SaltStack的源代码并寻找安全漏洞的过程中,我选择使用Bandit(一个针对Python应用程序的安全扫描程序)运行源代码,并查看其发现的问题。
我以为会看到很少的漏洞,因为SaltStack的代码库较大,并且已经维护了几年的时间。但是,从下面的截图中可以看到,Bandit展示的问题数量比我预期的还要多,其中包括117个高危问题。
Bandit针对SaltStack Salt的检测报告:
当然,其中的很多问题都是误报,或者对我们来说意义不大,并且可能需要花费大量时间来逐一进行分析。为了快速找到关键漏洞,我决定将精力聚焦在几个子进程实例可能产生的命令注入漏洞。我将Popen与代码库中的shell=True结合使用。
在分析其中的一些漏洞的过程中,我尝试了多种方法,起初都无法控制这些漏洞。但后来,我发现可以利用进程名称,以一种非常巧妙的欺骗手段来控制它们。
这也就是我们正在寻找的漏洞。
0x03 漏洞详情
minion在重新启动检查的过程中存在命令注入漏洞。当某个进程打开的文件描述符以(deleted)为结尾时(需要加上前面的空格),攻击者可以通过精心设计进程名称的方式实现命令注入。这样一来,就允许任意用户在minion任何未明确禁止的目录中创建文件,实现到root的本地特权提升。
3.1 漏洞代码
存在漏洞的代码位于restartcheck.py的第615行,其中使用shell=True和被攻击者操纵的命令来调用subprocess.Popen。
cmd = cmd_pkg_query + package
paths = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
根据操作系统的不同,软件包是由进程名称组成的,cmd_pkg_query可能如下:
Debian:dpkg-query --listfiles
RedHat:repoquery -l
NILinuxRT:opkg files
如果我们能够插入bash控制字符到进程名称中(例如; | &&等),就可以在代码到达上述一行时触发注入。
但是,要到达这一行代码,进程首先需要打开文件处理程序,打开以(deleted)为结尾的文件,并且该文件必须位于未明确禁止的目录中。
被禁止的目录列表请参考这里。这个列表可能会排除掉一些我们可能会尝试的位置,例如/tmp和/dev/shm。但是,特权较低的用户可能会访问一些常用的位置,例如:
/var/crash
/var/spool/samba
/var/www
3.2 进程名称
在我们的研究过程中,事实证明,要可靠地修改进程名称是一件比较难的事情。并且,ps列出的进程名称可能会与Python psutil库返回的名称不同。
在Linux中,进程名称可以包含任何字符(除null外)。任何用户都可以在系统上启动进程,并且进程本身可以设置进程的名称。因此,它们就成为了命令注入漏洞的良好目标,因为开发人员不太可能会想到进程名称包含特殊字符或注入的场景。
我们可以使用exec -a直接设置进程名称。但是,这种方式在busybox或sh shell中是不起作用的,并且在使用psutil时似乎没有显示出相同的名称。当然,我们也可以通过直接操作procfs来修改进程名称,但是这种方法也会导致结果不一致。
因此,我们最后找到的最简单、最准确的方法是重命名正在运行的二进制文件或脚本。由于Linux的文件名中不能包含/字符,所以就限制了我们可以注入的命令。不过,使用Base64编码可以忽略此限制,如下所示:
# 如果我们想将shadow文件复制到/tmp,我们可以运行
cp /etc/shadow /tmp/shadow
# 将其转换为Base64字符串
echo cp /etc/shadow /tmp/shadow | base64 -w0
# 转换结果
Y3AgL2V0Yy9zaGFkb3cgL3RtcC9zaGFkb3cK
# 我们需要运行的新命令
echo Y3AgL2V0Yy9zaGFkb3cgL3RtcC9zaGFkb3cK|base64 -d|sh -i
0x04 漏洞利用
PoC脚本exploit.sh的用法:
为了成功利用这个漏洞,需要使用一个未被SaltStack明确禁止的可写目录。在不带参数运行PoC脚本后,将会搜索符合该限制的目录。
示例脚本输出了潜在可写入的目录:
随后,使用-w标志加上选定的可写目录,使用-c标志加上命令,将会创建一个进程,在该进程的名称中包含命令注入和一个打开的文件处理程序,当master进入该文件处理程序时,漏洞将被处罚,并调用restartcheck。
我们逐步执行漏洞利用过程以展示这一漏洞。为了尝试,我们可以以root用户身份创建一个简单的文件。首先,带上特定标志运行脚本。
运行漏洞脚本,其中标记了进程名称和文件处理程序:
既然已经确定恶意进程正在运行,并且名称中已经注入了命令,同时也打开了文件处理程序,那么我们就可以在SaltStack的master发出restartcheck.restartcheck命令。完成此操作后,可以检查根目录中是否存在被黑的文件。
上图证明漏洞利用已经成功,我们可以以root用户身份执行代码。
不错,接下来我们可以做一些更酷的事情。如何获得一个root Shell呢?在下面的视频中,我们通过复制find二进制文件,并将其设置为suid,最终成功获得了root身份的Shell。
视频:https://asciinema.org/a/382327
对于其他场景中,下图从master视角展现了漏洞利用的过程。
来自master POV的命令注入,其中标出了注入的对应行:
0x05 其他安全问题
上面讨论了在本地利用漏洞实现到root用户特权提升的场景。但是,在此过程中我们还应该考虑一些其他安全问题。
5.1 容器逃逸
我们在主机上已经列出了容器化的进程,因此可以从容器内部执行这一漏洞利用,从而在主机上以root用户身份实现命令执行。
上图展示进程在容器中启动,下图为主机上使用ps列出的容器进程:
5.2 非特权远程代码执行
尽管不太可能,但这样的漏洞还是有机会被不具备本地Shell访问权限的攻击者利用。原因在于,在某些情况下,远程用户可以影响进程的名称。
0x06 时间节点
SaltStack的响应速度非常快,我发现和披露该漏洞的完整时间节点如下。
2020年11月5日 发现漏洞
2020年11月5日 通知SaltStack
2020年11月5日 SaltStack开始调查
2020年11月6日 分配CVE ID
2020年11月7日 SaltStack确认漏洞
2020年11月18日 SaltStack通知计划在1月发布修复程序
2021年1月22日 宣布将于2月4日发布安全修复程序
2021年2月4日 安全修复程序发布日期延迟至2月25日
2021年2月25日 已发布安全修复程序(+91天)
0x07 总结
在开发和维护过程中,要保证代码的安全性已经是非常复杂的工作了。不难理解,开发人员很难想到恶意进程名称的这个维度。如果大家有任何问题或者反馈,欢迎在Twitter上与我联系。同时,这也是我的第一个CVE,还请大家温柔一些。
参考及来源:https://sec.stealthcopter.com/cve-2020-28243/