深入分析CVE-2022-0492漏洞
概述
2月4日,Linux披露了CVE-2022-0492漏洞,这是在内核中新发现的一个权限提升漏洞。据公告称,该漏洞是由于control groups(cgroups)中的一个逻辑错误所致;cgroups是Linux的一个模块,同时也是容器的基本构建块。它是近期发现的最容易利用的Linux权限提升漏洞之一:Linux内核错误地将特权操作暴露给非特权用户。
幸运的是,大多数容器环境中的默认安全强化措施足以防止容器逃逸。例如,使用AppArmor或SELinux运行的容器就能面免受该漏洞的威胁。也就是说,如果您在没有采取基于最佳实践的安全加固的环境下运行容器,或者使用了额外的特权的话,那就很可能会受到该漏洞的攻击。为此,我们将为读者详细介绍哪些容器配置是易受攻击的,并提供有关如何测试容器环境是否易受攻击的说明。
除了容器之外,该漏洞还允许受限的root用户主机进程或具有CAP_DAC_OVERRIDE特权的非root用户的主机进程提升权限并获得所有特权。这就使得攻击者能够绕过某些服务使用的某些安全加固措施——这些措施会降低权限,以便在受到攻击时将损失降到最低程度。
我们强烈建议用户升级到已经修复该漏洞的内核版本。对于那些正在运行的容器,可以启用Seccomp,并确保启用了AppArmor或SELinux。CVE-2022-0492是近几个月来曝光的第三个允许恶意容器逃逸的内核漏洞。对于这些漏洞,只要通过Seccomp和AppArmor或SELinux来保护容器,就能够防止容器逃逸。
关于Cgroups的背景知识
Control Groups(cgroups)是一个Linux特性,允许管理员限制、记录和隔离一组进程所使用的资源。Linux支持两种cgroup架构,分别名为cgroup v1和cgroup v2。实际上,这个新的漏洞只影响cgroup v1架构,而该架构是目前使用的最广泛的架构。在本文下面的内容中,我们将只针对cgroup v1进行介绍。
Control Groups是通过cgroupfsAPI进行管理的,这是一个作为文件系统暴露的管理API,通常挂载在/sys/fs/cgroup下。通过利用已挂载的cgroupfs创建和写入文件和目录,管理员就可以创建相应的cgroup,并控制施加在cgroup上的约束,或向某个cgroup添加进程,等等。
Cgroups被划分为各个子系统,每个子系统用于配置对不同资源的访问控制。例如,内存cgroup可以限制进程集合的内存消耗;而设备cgroup则定义了cgroup中的进程可以访问哪些设备(例如,硬盘驱动器或鼠标)。此外,还有控制其他资源其他子系统,例如块设备IO、CPU和远程直接内存访问(RDMA)。
所有的子系统通常都挂载在/sys/fs/cgroup/<subsystem>(即子系统的rootcgroup)。root cgroup下的任何子目录都表示一个新的子cgroup。例如,Docker容器通常是/docker/<ctr-id>cgroup的一部分,可以在主机的/sys/fs/cgroup/<subsystem>/docker/<ctr-id>路径上找到。图1显示了Docker容器的cgroup成员,图2显示了Kubernetespod的cgroup成员。
图1 Docker容器中进程的cgroup成员。
图2 Kubernetes pod中进程的cgroup成员。
从上面的截图中可以看出,并非所有的主机都被配置为支持相同的子系统。Docker和Kubernetes也有不同的cgroup配置,所以,如果读者的主机或集群上的容器中没有发现上面显示成员,也是非常正常的现象。
漏洞的成因
cgroups v1的特性之一便是release_agent文件。管理员可以借助该文件来配置“release agent”程序,该程序将在cgroup中的进程终止时运行。这是通过将所需的释放代理(releaseagent)路径写入release_agent文件来完成的,具体如下所示:
$ echo /bin/my-release-agent >/sys/fs/cgroup/memory/release_agent
需要注意的是,release_agent文件仅在root cgroup目录中可见,并且只影响其子cgroups。通过写入notify_on_release文件,可以将每个子组配置为(在其中一个进程终止时)触发或不触发释放代理。以下命令将为a_child_cgroup cgroup启用notify_on_release功能:
$ echo 1 >/sys/fs/cgroup/memory/a_child_cgroup/notify_on_release
当进程结束时,内核将检查其cgroups是否启用了notify_on_release:如果启用了,则生成配置的release_agent二进制文件。这时,发布代理将以尽可能高的权限运行:一个具有初始命名空间中所有权限的root进程。因此,配置发布代理被认为是一个特权操作,因为它允许用户决定哪个二进制文件将以完整的root权限运行。
CVE-2022-0492漏洞的成因,在于缺失相应的验证:Linux没有检查设置release_agent文件的进程是否具有管理权限(即CAP_SYS_ADMIN权限)。此外,我们还可以通过CVE-2022-0492的补丁代码(以下第2-8行)来了解该漏洞:
@@ -549,6 +549,14 @@ static ssize_tcgroup_release_agent_write(struct kernfs_open_file *of,
+ /*
+ * Release agent gets called with all capabilities,
+ * require capabilities to set release agent.
+ */
+ if((of->file->f_cred->user_ns != &init_user_ns) ||
+ !capable(CAP_SYS_ADMIN))
+ return-EPERM;
利用该漏洞的先决条件
如前所述,只要攻击者能够对release_agent文件执行写操作,就能强制内核以提升后的权限执行指定的二进制文件,进而控制整个机器。那么,哪些用户可以在易受攻击的计算机上对release_agent文件执行写操作呢?虽然内核不会显式地检查写入进程的特权,但是,正常的文件所有权和权限语义仍然还是适用的。
因为Linux将release_agent文件的所有者设置为root,所以,只有root用户(或者可以通过CAP_DAC_OVERRIDE权限绕过文件权限检查的进程)才能对该文件执行写操作。因此,该漏洞仅允许root进程提升权限。
乍一看,只能由root用户才能利用的权限提升漏洞,听起来似乎很奇怪。在过去,这不会被认为是一个安全问题。但是今天,以root用户身份运行并不一定意味着对机器的完全控制:在root用户和包括功能、命名空间和容器在内的完全权限之间,实际上还存在一个灰色区域。在root进程无法完全控制计算机的情况下,CVE-2022-0492将会成为一个严重的安全漏洞。
纵深防御的胜利——容器逃逸先决条件
实际上,并不是所有的容器都能利用CVE-2022-0492漏洞实现逃逸;相反,只有具有特定的安全配置文件的容器才能执行必要的逃逸步骤。
由于在容器内cgroup的挂载是以只读方式进行的,因此,不允许对它们托管的release_agent文件执行写操作。所以,想要利用CVE-2022-0492漏洞的恶意容器,必须挂载另一个可写的CGroupFS。
图3 容器内的Cgroupfs挂载是只读的(“RO”)
另外,AppArmor和SELinux都会阻止安装操作,这意味着使用两者运行的容器都会受到相应的保护。如果没有两者的保护,容器就可以通过以下两种方式之一挂载cgroupfs:滥用用户命名空间或CAP_SYS_ADMIN功能。
基于用户命名空间的容器逃逸
要想挂载cgroupfs,必须在托管当前cgroup命名空间的用户命名空间中拥有CAP_SYS_ADMIN权限。但是在默认情况下,容器说在非CAP_SYS_ADMIM权限的情况下运行的,因此,无法在初始用户命名空间中挂载cgroupfs。但是通过unshare系统调用,容器可以创建新的user和cgroup命名空间,并且在这些空间中拥有CAP_SYS_ADMIN权限,所以,也就可以挂载cgroupfs了。
图4 容器创建一个新的用户命名空间,并且在其中拥有CAP_SYS_ADMIN权限
当然,并不是每个容器都可以创建新的用户命名空间——底层主机就必须启用非特权用户命名空间,例如,Ubuntu最新版本的默认设置就是如此。由于Seccomp会拦截unshare系统调用,因此,只有在没有启用Seccomp的环境中运行的容器才能创建新的用户命名空间。下图显示是容器在没有启用Seccomp、AppArmor或selinux的环境中的运行情况。
图5 容器在新的用户和cgroup命名空间中挂载内存cgroup
在上面的截图中,容器成功地挂载了一个内存cgroup,但是您可能已经注意到了——release_agent文件并没有出现在已挂载的目录中!
如前所述,release_agent文件仅在root cgroup中可见。注意,在cgroup命名空间中挂载cgroupfs时,挂载的是您所属的cgroup,而不是rootcgroup。如果观察图1,就会发现,容器并不是在root用户的内存cgroup中运行的,而是在子cgroup://docker/<id>中运行的。为了使release_agent文件在cgroup挂载中可见,容器必须在子系统的root cgroup中运行。
同样,在图1中,您将看到Docker是在root RDMA cgroup中运行容器的。如果我们对RDMAcgroup执行相同的命令,那么release_agent文件也会变成是可见的。
图6 容器,它在新的用户和cgroup命名空间中挂载了rootRDMA cgroup
为了利用这个安全漏洞,我们需要在release_agent文件中写入一个恶意的释放代理。如上图6所示,由于该文件的属主为root,所以,只有root容器进程才能够设置该释放代理。图7显示了容器设置释放代理的情况,而图8则显示了非root容器设置释放代理失败的情形。
图7 root容器在设置释放代理
图8 非root容器无法设置释放代理
逃逸的最后一步,就是调用配置好的release_agent,需要说明的是,这一步不需要任何权限。由于这一步总是可以做到的,它对环境是否容易受到CVE-2022-0492的攻击没有影响,所以,我们决定把它排除在外。在下图中,我们为读者展示了一个完整的exploit。
图9 利用CVE-2022-0492漏洞,通过用户命名空间实现容器逃逸
基于CAP_SYS_ADMIN 的容器逃逸
如果容器被授予CAP_SYS_ADMIN权限,则可以直接利用该漏洞,而无需创建新的用户和cgroup命名空间。使用CAP_SYS_ADMIN功能运行的容器可以挂载cgroupfs,系统不会进行任何干涉。另外,现在大多数容器都是在没有cgroup命名空间的情况下运行的,这意味着挂载的cgroup隶属于rootcgroup,并且有权读写release_agent文件。
图10 在初始的cgroup命名空间中,无论容器的cgroup是谁,只要挂载cgroupfs,就会挂载rootcgroup
即使有CAP_SYS_ADMIN权限,AppArmor和SELinux仍然拦截相应的挂载操作,因此,在启用了它们的系统中,容器是无法利用CVE-2022-0492漏洞实现逃逸的。图11显示了在没有启用AppArmor和SELinux的情况下运行的容器,同时,该容器具有CAP_SYS_ADMIN权限,最终,该容器利用CVE-2022-0492漏洞成功实现了逃逸。
图11 利用CVE-2022-0492漏洞通过CAP_SYS_ADMIN权限实现容器逃逸
如何进行自检
总而言之,如果一个容器满足下列条件,就可能发生逃逸:
- 作为root用户运行,或者没有提供no_new_privs选项;以及
- 没有启用AppArmor或SELinux;以及
- 没有启用Seccomp;以及
- 在一个启用非特权用户命名空间的主机上运行;以及
- 隶属于root v1 cgroup。
或者,如果它在运行过程中满足下列条件:
- 具有CAP_SYS_ADMIN权限;以及
- 没有启用AppArmor或SELinux;并且
- 没有创建cgroup命名空间,或隶属于rootv1 cgroup。
我们的安全研究人员创建了一个脚本,可以用来测试容器环境是否含有CVE-2022-0492的漏洞。为了测试自己的环境,您可以部署一个新的容器,然后直接运行我们的us-central1-docker.pkg.dev/twistlock-secresearch/public/can-ctr-escape-cve-2022-0492镜像即可,它已经被配置为运行该脚本,打印检测结果,并自动退出。另外,您也可以使用该工具的存储库中的Docker文件,以构建和运行自己的镜像。
需要注意的是,我们不建议在一个现有的容器中运行该脚本。这是因为该容器可能没有该脚本所依赖的实用程序,或者没有使用正确的版本,而这可能导致不准确的结果。
图12 使用脚本来测试哪些容器配置容易受到CVE-2022-0492漏洞的影响
缓解措施
目前,CVE-2022-0492已经在最新的Linux版本上得到了修复;我们鼓励所有用户升级到各自发行的最新内核版本。
为了在无法升级的情况下防范恶意容器,用户可以启用以下缓解措施之一:
- 启用AppArmor或SELinux。更多信息请参见Kubernetes指南。
- 启用Seccomp。更多信息请参见Kubernetes指南。请注意,这并足以抵御以CAP_SYS_ADMIN权限运行的容器。
为了防止恶意的主机进程提升权限,在无法升级的情况下,用户可以启用以下两种缓解措施。请注意,这个解决方案在重启后将会失效,需要重新启用相关措施:
- 使用以下命令禁用非特权用户命名空间:sudosysctl -w kernel.unprivileged_userns_clone=0。需要注意的是,系统中的某些服务,如Podman,依赖于非特权用户命名空间,因此,采用此措施可能导致它们无法按预期工作。
- 使用下面的脚本防止进程在任何cgroupmount中设置release_agent。该脚本通过只读型绑定挂载来屏蔽所有release_agent文件。如果您的系统将cgroup挂载在一个自定义的路径上(默认是/sys/fs/cgroup),需要将该路径作为一个参数提供给该脚本。
#!/bin/bash
set -e
mask_dir=/var/lib/cve_2022_0492_release_agent_mask
cgroup_dir=/sys/fs/cgroup
if [ ! -z "$1" ]; thencgroup_dir=$1 ; fi
echo "[+] Creating mask at$mask_dir/mask"
sudo mkdir -p $mask_dir
sudo mount -t tmpfs release_agent_mask$mask_dir
sudo touch $mask_dir/mask
sudo mount -o remount,ro $mask_dir
for release_agent in $(find $cgroup_dir-name 'release_agent') ;do
echo "[+] Mounting read-only mask over $release_agent"
sudo mount --bind $mask_dir/mask $release_agent
done
小结
CVE-2022-0492是一个可实现容器逃逸的Linux漏洞。幸运的是,遵循最佳实践的环境可以免受这个漏洞的影响。但是,对于安全控制措施不严、托管不受信任或公开暴露的容器的环境来说,它们所面临的风险还是挺高的。像往常一样,我们建议大家将主机升级到一个已经修复该漏洞的内核版本。
原文地址:https://unit42.paloaltonetworks.com/cve-2022-0492-cgroups/
最新评论