CD's second night

深入剖析 uevent_helper 容器逃逸向量

type
status
date
slug
summary
tags
category
icon
password

深入剖析 uevent_helper 容器逃逸向量

引言::最近在做WIZ-CTF题的时候遇到了uevent_helper逃逸,但一直没有了解它的详细原理,故写下此篇文章。

Q&A

Q:常规检测工具比如linpeas是怎么检测出存在uevent_helper 容器逃逸的?
A:
它尝试写入 /sys/kernel/uevent_helper 文件:
  • 如果能成功写入,变量 uevent_helper_breakout 的值就是 Yes,代表系统有漏洞 😱。
  • 如果写入失败,值就是 No,代表当前路径安全 👍。
 
Q:针对这一漏洞现象有没有自动化内外扫描和自动化安全补丁方案 或者设备告警规则 监督方案?
A:接下来我们就引入重点,通过分析它产生的原理,利用思路进而探讨防护方案。
 
 
 

第一部分:Linux Uevent 子系统与 uevent_helper 的历史遗留

1.1 内核到用户空间的通信:Kobjects、Netlink 和 Uevents

Linux 内核采用 uevents(用户事件)作为内核与用户空间通信的机制,通常用于设备状态的变更(如添加、移除、改变。这些事件与内核设备模型中的基本数据结构 kobjects 的生命周期紧密相连。现代且推荐的事件传递方式是通过 netlink 套接字(NETLINK_KOBJECT_UEVENT),用户空间的守护进程(如 udevd)会监听此套接字以接收事件。一个典型的 uevent 消息包含键值对,例如 ACTIONDEVPATHSUBSYSTEMSEQNUM
理解这一架构对于认识到 uevent_helper 为何是一种异常存在至关重要。Netlink 套接字是一个高效、异步、多对多的通信渠道。它允许多个用户空间监听者(如 udevd)接收事件,而内核无需关心谁在监听。这种解耦的设计是现代 Linux 设备管理的核心

1.2 uevent_helper 的原始角色:从热插拔脚本到潜在的安全风险

uevent_helper 是一个用户模式辅助程序(user-mode helper),内核会为每一个 uevent 事件派生(fork)一个该程序的进程。在历史上,该路径通常指向 /sbin/hotplug,是基于 netlink 的系统成为标准之前处理设备事件的主要机制。此辅助程序的路径可以在运行时通过向 /sys/kernel/uevent_helper 文件写入来进行配置。
这种“每个事件派生一个进程”的模型被证明效率极低,在系统启动或设备发现风暴期间,可能导致极高的系统负载,甚至引发内存不足(out-of-memory)的情况。正是这一性能瓶颈,成为其被 netlink 机制取代并逐渐废弃的主要原因。

1.3 现代 udev 守护进程与 uevent_helper 的废弃

udev(userspace /dev)是现代 Linux 内核的设备管理器,现已成为 systemd 的一部分。systemd-udevd.service 服务会监听来自 netlink 套接字的内核 uevents,并根据 /etc/udev/rules.d/ 目录中定义的规则进行处理。在大多数现代发行版中,内核配置选项 UEVENT_HELPER_PATH 的默认值已变为空字符串(""),从而在默认情况下禁用了此功能。内核文档明确指出,该机制“今天不应再被使用”。
尽管默认被禁用,但其底层的内核代码(CONFIG_UEVENT_HELPER)通常仍被编译进内核(default y),并且如果系统未进行适当的加固,/sys/kernel/uevent_helper 接口仍然是可写的。这就产生了一个危险的缺口:一个被认为过时且默认禁用的功能,可以在运行时被一个特权进程重新启用。这种现象可以被视为一种“架构残留”。内核的演进过程——从一个简单的、同步的进程派生模型,发展到一个复杂的、异步的事件驱动模型——遗留下了一个强大的、高权限的执行原语。这个原语为了保持向后兼容性而被保留下来,但它并不属于现代 udev 威胁模型的一部分,因此在容器安全配置中常常被忽视。攻击者利用的并非代码逻辑中的漏洞,而是这一架构决策——即允许一个废弃的、高权限的“后门”在运行时保持活跃且可配置。
 

第二部分:容器逃逸剖析

2.1 攻击面分析:

在没有容器内高权限的情况下,这种攻击基本上是不可能实现的。主要的触发条件是以 --privileged 标志运行容器,或者更具体地说,是赋予容器 CAP_SYS_ADMIN 能力。-privileged 标志会授予所有能力并移除许多安全限制,使其成为最危险的配置。CAP_SYS_ADMIN 通常被称为“新的 root”,因为它授予了广泛的管理权限,包括执行mount 系统调用以及对 /sys/proc 下许多路径的写权限。攻击的核心是向/sys/kernel/uevent_helper 写入数据。/sys 伪文件系统是内核数据结构的接口。默认情况下,在具有CAP_SYS_ADMIN 的容器中,此文件系统是以读写模式挂载的,从而允许修改。此外,容器内的攻击进程必须以 root(UID 0)用户身份运行,因为即使有CAP_SYS_ADMIN,非 root 用户也可能面临额外的自主访问控制(DAC)限制。容器还必须与宿主机共享同一个用户命名空间,因为被执行的辅助程序将在宿主机的上下文中运行 。

2.2 攻击链:分步演练

  1. 第一步:制作恶意载荷
    1. 攻击者在容器的文件系统内创建一个脚本。该脚本包含将在宿主机上执行的命令。例如:
  1. 第二步:解析宿主机文件系统路径(OverlayFS)
    1. 内核将从宿主机的根文件系统上下文中执行辅助程序。因此,攻击者不能简单地将 /payload.sh 写入 uevent_helper。他们必须提供该文件在宿主机上的完整绝对路径。对于使用 OverlayFS 的 Docker 容器,此路径通常可以通过解析 /etc/mtab 文件中的 upperdir 或 perdir 挂载选项来获取。示例命令:
      这一步骤揭示了容器运行时与内核特性之间的一个关键交互。逃逸不仅仅关乎内核机制,还关乎攻击者理解和滥用容器运行时创建的存储抽象(如 OverlayFS)的能力。容器内的攻击者看到的文件系统根目录是 /,而宿主机看到的同一个文件系统根目录则是一个复杂的路径,如 /var/lib/docker/overlay2/LONG_HASH/diff/uevent_helper 机制由宿主机内核执行,它在宿主机的挂载命名空间中运行,对容器的文件系统视图一无所知。因此,为了攻击成功,攻击者必须通过审视容器自身的挂载信息(/etc/mtab)来弥合容器和宿主机对文件系统路径视图之间的“语义鸿沟”,而这些信息恰好泄露了底层宿主机存储配置的细节。这意味着成功的攻击不仅需要内核级别的权限,还需要了解特定容器运行时的存储实现。
       
  1. 第三步:覆写 uevent_helper 路径
    1. 攻击者将解析出的载荷脚本在宿主机上的路径写入目标文件。示例:
  1. 第四步:从用户空间触发内核执行
    1. 最后一步是促使内核生成一个 uevent。这可以通过向某个 sysfs 设备的 uevent 文件写入数据来可靠地从用户空间完成。/sys/class/mem/null/uevent 文件是一个常用且可靠的目标。示例命令:
      随后,内核会从uevent_helper 读取路径,并以宿主机上的 root 权限执行它。
       

第三部分:用户模式辅助程序逃逸的比较分析

3.1 cgroupfs release_agent 向量:一种并行技术

cgrouprelease_agent 是另一个用户模式辅助程序。可执行文件的路径可以被写入 cgroup 层次结构根目录下的 release_agent 文件中。当该层次结构中的某个 cgroup 的 notify_on_release 标志被设置并且该 cgroup 变为空(最后一个进程退出)时,内核会执行 release_agent 指定的二进制文件。这项技术已为人所知多年,并曾被用于现实世界的攻击中以部署加密货币矿工。

3.2 决定性因素:为何用户命名空间能触发 CVE-2022-0492,却无法用于 uevent_helper 逃逸

从历史上看,release_agentuevent_helper 逃逸都需要在初始用户命名空间中拥有 CAP_SYS_ADMIN。然而,CVE-2022-0492 证明,一个在容器内没有 CAP_SYS_ADMIN 的攻击者可以创建一个新的用户命名空间,这会赋予他们在该新命名空间内CAP_SYS_ADMIN 权限。这足以挂载一个可写的 cgroupfs 并设置 release_agent
同样的技术对 uevent_helper 却无效,因为尝试在新用户命名空间中挂载 sysfs 会被内核明确阻止 。内核的mount_too_revealing 检查由 sysfsprocfs 上的SB_I_USERNS_VISIBLE 标志触发,该检查会阻止这些文件系统在新用户命名空间中挂载,如果它们包含了被屏蔽的路径(这在容器环境中是标准做法)。
cgroupfs 缺少这个标志和相应的检查,这正是该 CVE 的关键所在。这一技术差异是至关重要的,它解释了为什么在现代环境中 release_agent 被认为是一个更严重的漏洞并获得了 CVE 编号,而 uevent_helper 仍然是一个依赖于错误配置的问题。

3.3 core_patternmodprobe 与共同的威胁模型

References材料列出了遵循相同模式的其他用户模式辅助程序:/proc/sys/kernel/core_pattern(在进程崩溃时执行辅助程序)和 /proc/sys/kernel/modprobe(为加载内核模块执行辅助程序)。所有这些都代表了一个共同的威胁模型:一个由伪文件系统中可写文件控制的特权内核执行机制 。
下表将对不同但相关的逃逸向量的复杂分析提炼成一个清晰、简洁的参考,供安全架构师和渗透测试人员使用。它通过比较每个向量的前提条件和可利用性,实现了快速的风险评估。
特性
/sys/kernel/uevent_helper
/sys/fs/cgroup/.../release_agent
/proc/sys/kernel/core_pattern
/proc/sys/kernel/modprobe
内核接口
/sys/kernel/uevent_helper
cgroupfs 根目录下的 release_agent 文件
/proc/sys/kernel/core_pattern
/proc/sys/kernel/modprobe
触发机制
任何 uevent (例如,写入 /sys/.../uevent)
cgroup 中的最后一个进程退出 (notify_on_release=1)
进程崩溃并生成核心转储 (如果首字符为 `
`)
先决条件 (经典)
CAP_SYS_ADMIN
CAP_SYS_ADMIN
CAP_SYS_ADMIN
CAP_SYS_ADMIN
用户命名空间利用
(被 sysfs 挂载限制阻止)
(CVE-2022-0492, cgroupfs 允许挂载)
(被 procfs 挂载限制阻止)
(被 procfs 挂载限制阻止)
主要缓解措施
移除 CAP_SYS_ADMIN, AppArmor 拒绝写入规则
移除 CAP_SYS_ADMIN, Seccomp (unshare), AppArmor
移除 CAP_SYS_ADMIN, AppArmor 拒绝写入规则
移除 CAP_SYS_ADMIN, CAP_SYS_MODULE, AppArmor 拒绝写入规则
 

第四部分:检测框架:从配置到运行时

本节将为安全团队提供可操作的指导,以检测 uevent_helper 逃逸的条件和主动利用行为。

4.1 静态分析与合规性扫描

CIS Docker Benchmark 为容器环境提供了加固指南 。尽管它没有特别点名 uevent_helper,但其控制措施直接映射到了攻击的先决条件。关键控制项包括建议不要运行特权容器以及建议移除所有非必需的能力。像 Docker Bench for Security 这样的工具可以自动化这些检查 。
Trivy 是一个多功能扫描器,可以审计 Kubernetes 资源中的错误配置 。安全团队可以为 Trivy 编写自定义的 Rego 策略,或使用其内置检查来扫描 Pod 安全策略或安全上下文,以发现允许 privileged: true 或在 securityContext.capabilities.add 数组中包含 SYS_ADMIN 能力的配置。

4.2 使用 Falco 进行运行时威胁检测

Falco 是一个运行时安全工具,通过监控内核系统调用来检测异常行为。检测此攻击的核心逻辑是监视在容器内运行的进程(container.id!= host)以写入方式打开特定文件 /sys/kernel/uevent_helper 的行为。
基于上述逻辑,可以制定一个特定的 Falco 规则。此规则应具有高优先级(例如 CRITICAL),因为任何从容器内对此文件的写入都极其可疑,几乎可以肯定是恶意的。
此规则的逻辑改编自用于检测 release_agent 逃逸的思路。对这种逃逸向量的检测完美地展示了静态“左移”安全与运行时安全之间的共生关系。对配置(IaC、清单文件)的静态分析充当策略门禁,防止易受攻击条件的创建。在这些条件万一通过策略门禁或被动态创建时,检测对其的利用。。
 

第五部分:加固原则与缓解策略

本节将详细介绍具体的、分层的防御措施,以主动阻止 uevent_helper 逃逸向量。

5.1 强制执行最小权限:

最有效、最根本的缓解措施是遵循最小权限原则。不要以 root 用户身份运行容器。不要使用 --privileged 标志。明确移除所有能力(-cap-drop=all),并且只添加应用程序功能所必需的特定能力 。对于 Kubernetes,应使用 Pod 安全标准(如Restricted),禁止特权容器并限制能力。

5.2 文件系统加固:以只读方式挂载 /sys

尽管在大多数容器运行时中并非默认设置,但技术上可以将 /sys 在容器内以只读方式挂载。这将通过使 /sys/kernel/uevent_helper 不可写来直接阻止攻击。这是一种高级加固技术,可以通过自定义安全配置文件实现。

5.3 使用强制访问控制(MAC)进行主动拦截

AppArmor 是一个 Linux 内核安全模块,可以使用每个应用程序的配置文件将程序限制在一组有限的资源内。可以创建一个自定义的 AppArmor 配置文件,明确拒绝向 /sys/kernel/uevent_helper 的写入权限,即使容器以 CAP_SYS_ADMIN 运行,也能提供精准的防御。
示例 AppArmor 配置文件
此配置文件将使用 apparmor_parser 加载到宿主机节点上 。在 Docker 中,可以通过 -security-opt apparmor=custom-container-deny-uevent-helper 应用 。在 Kubernetes 中,则通过 Pod 注解应用:container.apparmor.security.beta.kubernetes.io/<container-name>: localhost/custom-container-deny-uevent-helper
强制访问控制(MAC)系统如 AppArmor 提供了一个关键的“深度防御”层,它将文件访问权限与 Linux 能力模型解耦。这使得管理员可以创建“特权但受约束”的工作负载,通过精准地阻止 CAP_SYS_ADMIN 等过于宽泛的能力的最危险应用,来缓解其风险。尽管主要缓解措施是“不要授予 CAP_SYS_ADMIN”,但某些合法的系统级工作负载(如监控代理、网络插件)可能需要此能力才能正常工作。AppArmor 在另一个维度上运作:文件路径访问控制。它不关心进程拥有什么能力,而是根据被访问的文件执行自己的规则集。通过创建这样的 AppArmor 配置文件,我们可以允许容器保留其合法需要的 CAP_SYS_ADMIN,同时阻止这个已知的特定逃逸向量。因此,AppArmor 成为一个至关重要的补偿性控制,用基于路径规则的精确性来完善能力模型的粗粒度控制。
 
 

References

 
Loading...