Stack Clash 是多个操作系统(包括 Linux)的内存管理中的一个漏洞。攻击者可利用此漏洞破坏特权进程的内存,从而执行任意代码。
Bugs Gone Wild
零日漏洞只是疯狂的 bug。如果 Bug 不被视为安全问题,则可能不会对其进行修补;给攻击者留下一个有据可查的安全漏洞,等待被利用。这正是最近的 CVE-2017-1000253 中发生的情况,这是一个高严重性漏洞。此漏洞于 2015 年 4 月在 linux 内核中修补,但未向后移植到许多长期发行版,因为它不被视为安全威胁。因此,许多企业发行版(例如 Redhat 及 CentOS)在很长一段时间内(超过两年)都容易受到 — 有点矛盾的是 — 「已知」的零日漏洞。RedHat 仅在 8 月 1 日应用了该补丁,CentOS 在 9 月 13 日应用了该补丁。
发现和修补 bug 显然是不够的。如果错误没有被标记为安全问题,它可能不会得到修补,从而使内核、操作系统或应用程序容易受到攻击。攻击者可以跟踪内核或流行发行版中的这些错误,而不是自己找出零日漏洞,以找到已知的零日漏洞并加以利用。
快速回顾
Stack Clash 是一种允许攻击者控制另一个进程的执行流程的攻击。当另一个进程具有更高的权限时,该攻击可用于执行权限升级:该特权进程可纵以执行任意代码。
Stack Clash 并不是一种新的攻击。它于 2005 年在 Large Memory Management Vulnerabilities 和 2010 年的 Exploiting Large Memory Management Vulnerabilities in Xorg Server Running on Linux 中首次讨论。这两篇文章演示了如何使用 Stack Clash 在用户空间中执行权限提升。内核中还有堆栈冲突的示例 (a, b, c)。这些攻击相对较古老,在 linux 添加称为 guard-page 的安全机制以防止此类攻击之前已经讨论过。
直到最近,这些攻击还被认为更多的是假设性的,而不是实际的。几个月前(2017 年 6 月),Qualys 发布了一份公告,其中包括十多个 CVE 以及概念验证代码。他们演示了如何使用此攻击从 多个操作系统和架构上的非特权(但经过身份验证)用户获取 root。因此,内核中的几个漏洞以及其他常见的二进制文件(如 sudo 和 glibc)都得到了修补。较新的公告(9 月发布)引入了 CVE-2017-1000253:另一个内核漏洞和类似漏洞,针对已修补的内核(具有更大的 ~1MB 页面防护)。
这些漏洞在很大程度上依赖于内核如何映射进程的内存。此机制是概率性的,这意味着漏洞利用可能需要数千次尝试才能成功。这意味着漏洞利用需要几个小时才能成功。
虽然尚未证明可以远程利用这种攻击,但排除这种可能性是不明智的。毕竟,直到最近,人们还认为这种攻击并不是真正的“真实世界”问题,这导致 CVE-2017-1000253 中的错误没有被标记为安全问题。
那么,我“耳朵 abaht 冲突”的兔子和猪肉是怎么回事?
利用堆栈冲突的例子有很多,每个例子都包含令人筋疲力尽的微小细节。这些可能会让人筋疲力尽,使读者无法只见树木不见森林。在这篇文章中,我试图让事情尽可能地保持 “人类可读”,而不会错过整体情况。
执行进程时,内核的工作是将数据加载到内存中、初始化堆栈并执行许多其他操作。执行进程时映射虚拟内存的方式取决于操作系统、内核的配置方式以及可执行文件本身。天真地,我们可以想象一个进程的虚拟内存由堆栈、二进制(更准确地说,加载到内存中的段,如 .text)、内存映射和堆组成。堆栈位于内核保护的内存下方,向下增长,而堆向上增长。当堆栈或堆继续增长,并可能相互竞争时,我们说存在 Stack Clash。
在 32 位处理器中,进程的整个虚拟地址空间为 4GB。这进一步分为用户空间和内核,因此用户的内存限制为 3GB。在大多数 32 位体系结构中,堆栈和堆之间的距离最大为 2GB。这不是一个“需要跨越的巨大海洋”,Qualys 确实成功地展示了多个漏洞,这些漏洞允许在 32 位架构上发生堆栈冲突。
在 64 位处理器中,用户空间虚拟内存大小为 128TB(!!)。尽管如此,Qualys 还是能够展示一次成功的堆栈冲突,导致权限升级。这需要两个漏洞:CVE-2017-1000366 和 CVE-2017-1000379。
要使堆栈与堆或内存映射发生实际冲突,攻击者需要使其中一个或多个 grow 。例如,这可以通过制作一个进程来执行内存映射、使用 large (在堆栈上分配) 或通过执行递归函数调用来完成。argv[] envp[]
碰撞保护
当堆栈指针到达堆栈底部时,内核可以扩展堆栈。这是由内核通过使用 once the stack 尝试增长到底部地址以下来隐式完成的。当堆栈(和/或内存)不断增长时,这两者存在冲突的危险。当它们发生冲突时,不会有 a ,因为堆栈下面的内存已经被映射了。page-faultpage-fault
为了防止发生冲突,有 .A 位于堆栈的正下方。此页面无法写入,并且会终止进程。下图描述了虚拟内存布局,包括 .guard-pageguard-pageSIGSEGVguard-page
该图描述了包括保护页在内的虚拟内存布局。
理论上,guard-page 应该(!) 防止冲突。在实践中,有一些方法可以绕过这种保护。
Guard-Page 规避
在他们的咨询中,Qualys 讨论了一种 4 阶段攻击。冲突、奔跑、跳跃和扣杀。所有这些步骤基本上都是尝试让堆栈指针越过保护页并进入另一个 Map 内存区域的步骤。我喜欢从最终目标的角度来思考这种攻击:guard-page circumvention。一般来说,保护页规避发生在以下情况之一:
加载二进制文件后,它的一部分已经映射到属于 stack 的内存:
剩下的就是 Smash
加载二进制文件后,堆栈和映射内存是连续的
攻击者需要 Run 堆栈指针到底部并跳过 保护页。
完成后,攻击者可以 Smash away
堆栈在加载时不会与映射的内存冲突
攻击者需要执行 Clash(即分配内存或扩展堆栈),然后才能继续下一步。
CVE-2017-1000253漏洞
此漏洞是由内核在加载 PIE 二进制文件时在 64 位架构上分配内存的方式引起的。因为 kernel 没有考虑大型二进制文件,所以它导致二进制文件的某些段被加载到 Map 内存的上方。在 64 位架构中,堆栈和内存映射之间存在间隙,保证为 128MB。数据段大于 128MB 的二进制文件可能会有效地与堆栈发生冲突。但是,此漏洞不仅限于具有大型数据段的二进制文件。PIE 也可以使用大量 (>1.5GB) 的参数字符串来调用 PIE,这将导致它被映射到堆栈的正下方,从而触发相同的漏洞。
Qualys 展示了如何利用这种冲突来提升权限。对漏洞利用的过度简化的解释(或更多详细信息,请继续阅读他们的公告)是这样的:首先,您需要一个合适的 SUID 二进制文件(ping 做到了)。这个想法是粉碎二进制文件 .dynamic 部分,从而以更高的权限加载我们自己的恶意可执行文件。为此,该漏洞利用使用 LD_DEBUG 环境变量。此变量会导致 ld.so 为堆栈上 LD_DEBUG 中的每个未知选项分配内存。.dynamic 部分可以被粉碎,并带有我们想要加载的共享库的路径名的偏移量。下图说明了 触发 CVE-2017-1000253 后被利用的 ping 二进制文件的虚拟内存。
该图显示了触发 CVE-2017-1000253 后被利用的 ping 二进制文件的虚拟内存。
DIY 容器演示
要在容器内看到这种攻击的实际效果,需要几个步骤。此 demo 依赖于 在 CentOS 上执行漏洞利用的 POC 代码。
首先,你需要设置一台具有易受攻击内核的 CentOS 机器:
安装CentOS 7
安装 Docker
编辑 文件:/etc/yum.repos.d/CentOS-Vault.repo
将每个条目更改为 path 以包含 7.3.1611:
...baseurl=http://vault.centos.org/7.3.1611/os/$basearch/baseurl=http://vault.centos.org/7.3.1611/updates/$basearch/
启用所有条目:
enabled=1
更新存储库列表
yum repolist
安装易受攻击的内核
yum install kernel-3.10.0-514.26.1.el7.x86_64
检查内核 ID(在以下示例中为 0):
' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg awk -F
0 : CentOS Linux (3.10.0-514.26.1.el7.x86_64) 7 (Core)
1 : CentOS Linux (3.10.0-514.26.1.el7.x86_64) 7 (Core) with debugging
2 : CentOS Linux 7 (Core), with Linux 3.10.0-229.20.1.el7.x86_64
设置 grub 的默认条目:
grub2-set-default 0
确保它已更改:
# grub2-editenv list
saved_entry=0
重新启动
shutdown -r now
确保已加载易受攻击的内核
# uname −a
Linux devSDces71b305-vm0 3.10.0-514.26.1.el7.x86_64 #1 SMP Thu Jun 29 16:05:25 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
很酷,我们有一个易受攻击的内核。现在,我们可以以非 root 用户身份运行容器,并看到它在容器内执行特权操作。在本演示中,我对 POC 代码中给出的加载库进行了少量更改 – 我编写了 HACKED to file。/root/hacket.txt
我将漏洞利用代码以及易受攻击的 ping 二进制文件(为此我打开了 suid 标志,使其以 root 身份运行)打包到一个映像中。我们现在要做的就是以非 root 用户身份运行容器,并查看漏洞利用程序成功地将数据写入容器内的 /root 文件夹(这可能需要几个小时):
您可能已经注意到,我们的特权代码具有一组有限的功能。发生这种情况是因为 Docker 默认限制了进程可能具有的绑定功能集。如果我们解码这个能力集,我们会注意到这些确实是 Docker 为进程提供的能力。
# capsh -decode=00000000a80425fb 0x00000000a80425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,
cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
在容器中发生冲突 – 最后的思考
Stack Clash 是容器通常比虚拟机更安全的另一个迹象。正如我们都已经知道(并且喜欢)的那样,容器比虚拟机要“薄”得多。他们是为了执行特定任务而制造的。因此,攻击者的攻击面(在获得容器中非特权用户的访问权限后)要小得多:正在运行的 SUID 文件或特权进程较少。将权限提升到 root 并不一定意味着主机受到损害。为此,攻击者需要运行内核漏洞以逃逸到底层主机。
另一个优点是容器也可以在用户命名空间中运行。如果是这种情况,即使一个成功的漏洞使用户在容器内成为 root,也不会使其在主机上成为 root。
应特别注意内部威胁。开发人员或操作人员可能能够运行非特权容器,甚至访问它们(例如,为了进行调试)。这将允许此类人员利用此类漏洞并获得对容器的特权访问权限。为防止这种情况发生,以非 root 用户身份运行的容器不应包含任何 SUID/GUID 文件或正在运行的特权进程。没有这些,Stack Clash 不会导致权限提升。此外,容器不应对特权资源有任何影响的访问权限。例如,不要依赖将 root 设置为文件的所有者,以保护它免受非 root 用户在挂载了此类文件的容器内运行的影响。
最重要的是,永远不要依赖补丁。使您的容器尽可能精简,并且不要忘记在运行时监控它们:非特权用户突然更改为 root 是一个值得注意的事件,受影响的容器可能值得终止。
原文始发于微信公众号(菜鸟小新):漏洞失控:容器(堆栈)冲突和 CVE-2017-1000253
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论