理解Docker容器逃逸 - Trail of Bits博客
Dominik ‘disconnect3d’ Czarnota
2019年7月19日
containers, exploits, kubernetes
Trail of Bits最近完成了对Kubernetes的安全评估,包括其与Docker的交互。Felix Wilhelm最近发布的概念验证(PoC)“容器逃逸"推文引发了我们的兴趣,因为我们进行了类似的研究,并好奇这个PoC如何影响Kubernetes。
Felix的推文展示了一个利用程序,该程序从带有--privileged
标志运行的Docker容器内部在主机上启动进程。PoC通过滥用Linux cgroup v1的"notification on release"功能实现这一点。
以下是启动主机上ps
命令的PoC版本:
|
|
--privileged
标志引入了重大的安全顾虑,该利用依赖于启用此标志启动docker容器。使用此标志时,容器具有对所有设备的完全访问权限,并且缺乏seccomp、AppArmor和Linux capabilities的限制。
–privileged …
— Ike Broflovski (@steaIth) 2019年7月18日
不要使用--privileged
运行容器。Docker包含独立控制容器权限的细粒度设置。根据我们的经验,这些关键的安全设置经常被遗忘。有必要了解这些选项的工作原理以保护容器安全。
在接下来的部分中,我们将逐步介绍这种"容器逃逸"的工作原理、它所依赖的不安全设置以及开发人员应该采取的措施。
使用此技术的需求
实际上,--privileged
提供的权限远远超过通过此方法逃逸docker容器所需的权限。实际上,“唯一"的要求是:
- 我们必须在容器内以root身份运行
- 容器必须使用SYS_ADMIN Linux capability运行
- 容器必须缺少AppArmor配置文件,或者允许mount系统调用
- cgroup v1虚拟文件系统必须在容器内以读写方式挂载
SYS_ADMIN capability允许容器执行mount系统调用(参见man 7 capabilities)。Docker默认以受限的能力集启动容器,并且由于安全风险而不启用SYS_ADMIN capability。
此外,Docker默认使用docker-default AppArmor策略启动容器,即使容器使用SYS_ADMIN运行,该策略也会阻止使用mount系统调用。
如果使用以下标志运行,容器将容易受到此技术的影响:--security-opt apparmor=unconfined --cap-add=SYS_ADMIN
使用cgroups传递利用
Linux cgroups是Docker隔离容器的机制之一。PoC滥用cgroups v1中notify_on_release功能的功能,以完全特权的root用户身份运行利用。
当cgroup中的最后一个任务离开(通过退出或附加到另一个cgroup)时,将执行release_agent文件中提供的命令。此功能的预期用途是帮助清理废弃的cgroup。调用此命令时,它将在主机上以完全特权的root身份运行。
1.4 notify_on_release做什么?
————————————
如果在cgroup中启用了notify_on_release标志(1),那么每当cgroup中的最后一个任务离开(退出或附加到其他cgroup)并且该cgroup的最后一个子cgroup被移除时,内核将运行由该层次结构根目录中"release_agent"文件内容指定的命令,提供废弃cgroup的路径名(相对于cgroup文件系统的挂载点)。这实现了废弃cgroups的自动移除。系统启动时根cgroup中notify_on_release的默认值为禁用(0)。创建时其他cgroups的默认值是其父级notify_on_release设置的当前值。cgroup层次结构的release_agent路径的默认值为空。
– Linux内核关于cgroups v1的文档
改进概念验证
有一种更简单的方法来编写此利用,使其在没有--privileged
标志的情况下工作。在这种情况下,我们将无法访问由--privileged
提供的读写cgroup挂载。适应这种情况很容易:我们将自己以读写方式挂载cgroup。这为利用添加了一行额外的代码,但需要更少的权限。
以下利用将在主机上执行ps aux
命令并将其输出保存到容器中的/output
文件。它使用与原始PoC相同的release_agent功能在主机上执行。
|
|
分解概念验证
既然我们了解了使用此技术的需求并改进了概念验证利用,让我们逐行讲解以演示其工作原理。
要触发此利用,我们需要一个cgroup,在其中可以创建release_agent文件并通过杀死cgroup中的所有进程来触发release_agent调用。最简单的方法是挂载一个cgroup控制器并创建一个子cgroup。
为此,我们创建一个/tmp/cgrp
目录,挂载RDMA cgroup控制器并创建一个子cgroup(在此示例中命名为"x”)。虽然尚未测试每个cgroup控制器,但此技术应与大多数cgroup控制器一起工作。
如果您在操作时遇到"mount: /tmp/cgrp: special device cgroup does not exist”,这是因为您的设置没有RDMA cgroup控制器。将rdma更改为memory以修复它。我们使用RDMA是因为原始PoC仅设计用于它。
请注意,cgroup控制器是全局资源,可以以不同的权限多次挂载,并且在一个挂载中所做的更改将应用于另一个挂载。
我们可以在下面看到"x"子cgroup的创建及其目录列表。
|
|
接下来,我们通过向其notify_on_release文件写入1来启用"x" cgroup释放时的cgroup通知。我们还通过将主机上的/cmd
脚本路径写入release_agent文件来设置RDMA cgroup释放代理以执行/cmd
脚本——我们稍后将在容器中创建该脚本。为此,我们将从/etc/mtab
文件中获取容器在主机上的路径。
我们在容器中添加或修改的文件存在于主机上,并且可以从两个世界修改它们:容器中的路径和它们在主机上的路径。
这些操作可以在下面看到:
|
|
注意/cmd
脚本的路径,我们将在主机上创建它:
|
|
现在,我们创建/cmd
脚本,使其执行ps aux
命令并将其输出保存到容器中的/output
,通过指定主机上输出文件的完整路径。最后,我们还打印/cmd
脚本以查看其内容:
|
|
最后,我们可以通过在"x"子cgroup内部生成一个立即结束的进程来执行攻击。通过创建/bin/sh
进程并将其PID写入"x"子cgroup目录中的cgroup.procs文件,主机上的脚本将在/bin/sh
退出后执行。然后在主机上执行的ps aux
的输出保存到容器内的/output
文件:
|
|
安全使用容器
Docker默认限制和约束容器。放宽这些限制可能会产生安全问题,即使没有--privileged
标志的全部功能。重要的是要承认每个额外权限的影响,并将整体权限限制到所需的最小限度。
为了帮助保持容器安全:
- 不要使用
--privileged
标志或在容器内挂载Docker套接字。docker套接字允许生成容器,因此它是完全控制主机的简单方法,例如,通过运行另一个带有--privileged
标志的容器。 - 不要在容器内以root身份运行。使用不同的用户或用户命名空间。容器中的root与主机上的root相同,除非使用用户命名空间重新映射。它仅受到Linux命名空间、capabilities和cgroups的轻度限制。
- 丢弃所有capabilities(
--cap-drop=all
)并仅启用那些必需的(--cap-add=...
)。许多工作负载不需要任何capabilities,添加它们会增加潜在攻击的范围。 - 使用"no-new-privileges"安全选项以防止进程获得更多权限,例如通过suid二进制文件。
- 限制容器可用的资源。资源限制可以保护机器免受拒绝服务攻击。
- 调整seccomp、AppArmor(或SELinux)配置文件,将容器可用的操作和系统调用限制到所需的最小限度。
- 使用官方的docker镜像或基于它们构建自己的镜像。不要继承或使用后门镜像。
- 定期重建镜像以应用安全补丁。这是不言而喻的。
如果您希望重新审视组织的关键基础设施,Trail of Bits很乐意提供帮助。联系我们并打个招呼!
如果您喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News
页面内容
使用此技术的需求
使用cgroups传递利用
改进概念验证
分解概念验证
安全使用容器
最近的帖子
构建安全消息传递很难:关于Bitchat安全辩论的细致看法
使用Deptective调查您的依赖项
系好安全带,Buttercup,AIxCC的评分回合正在进行中!
使您的智能合约超越私钥风险
Go解析器中意外的安全隐患
© 2025 Trail of Bits。
使用Hugo和Mainroad主题生成。