前言
随着云计算技术的日益成熟和 DevOps 概念的推广,越来越多的企业选择将其业务上云,基于云计算的容器技术在分布式计算环境中得到广泛部署。然而,容器在其生命周期内会存在多种安全威胁,如恶意镜像、运行时攻击和内核权限提升等。
目前,容器的安全防护主要依赖于 Linux 内核安全机制中的 DAC、MAC 和Namespace 隔离等技术。例如,对于 Ubuntu 系统而言,其会默认开启AppArmor 防护,该防护机制会为 docker 容器生成 docker-default 规则,以对 docker 容器内应用的行为进行限制,进而保障 docker 容器和 Linux 宿主机的安全。本文将从 docker 容器的 CVE-2019-5736 漏洞入手,浅析 AppArmor 对docker容器的实际防护效果,会从 CVE-2019-5736 漏洞概述及复现和探索 Apparmor 防御机制两个方面进行介绍。
CVE-2019-5736 概述及复现
1 漏洞概述
2019年2月,runC 维护团队公布了影响 Docker、containerd、Podman 和 CRI-O 等默认运行时容器 runC 的严重漏洞 CVE-2019-5736。该漏洞是波兰 CTF 战队Dragon Sector 在CTF赛后对 runC 进行漏洞挖掘成功发现的,其能覆盖宿主机runC 程序完成容器逃逸。该漏洞关键点是在于 runC,docker 在运行 exec 类似功能的命令时,底层实际上是 runC 在执行,即由 runC 在容器内部执行用户指定的程序。
runC 会使用一种特殊的符号链接(/proc/[runC-PID]/exe),该链接会指向进程自身对应的本地程序文件,通过它可轻松将宿主机上的 runC 二进制文件覆盖掉。当用户再次调用 runC 执行命令,此时执行的将是攻击者放置的任意恶意命令,实现容器逃逸。
2 漏洞复现概述
2.1 影响版本
docker version <=18.09.2
runC version <=1.0-rc6
2.2 漏洞验证
当使用某开源 POC 执行攻击后,发现宿主机 /tmp 目录下存在创建的“恶意”空文件夹,由此证明漏洞是真实存在的。
主要进行的是漏洞的概念性验证,通过一种无损的模式在宿主机指定目录创建了空文件夹“runc-pwn-test”。在真实攻击场景中,攻击者可以在 POC 中写入任意恶意命令执行,这将可能带来严重后果。
探索Apparmor防御机制
为深入探索 Apparmor 防御机制,在 docker 不同版本(v17.12 & v18.03)上使用不同 docker 命令进行了一系列实验。
1 预备知识
【AppArmor】
AppArmor(Application Armor)是一种 MAC 机制,在自主访问控制 (DAC)基础上提供强制访问控制(MAC)来进一步保障 Linux 内核安全。不同于其他安全工具,AppArmor 旨在将程序限制在有限的资源集中,其将访问控制属性绑定到某个具体程序而并非系统用户。具体而言,每个进程都相应的有自己的安全配置文件,AppArmor 通过一个配置文件(profile)来指定一个应用程序的相关权限,即以白名单的方式定义该应用程序可以访问的系统资源。
AppArmor 配置文件会有两种工作模式,Enforce 和 Complain:
Enforce:此模式下加载的配置文件中定义的策略将强制执行,且会报告违规尝试(syslog/auditd);
Complain:此模式下加载的配置文件中定义的策略不会强制执行,而是仅报告违反策略的尝试;
【docker exec/attach域】
-
在 docker 17.12 版本中,以 docker start/attach 方式运行的进程受 AppArmor 域的控制(docker-default 文件);而以 docker exec 方式运行的进程则不受 AppArmor 域的控制,其在 AppArmor 看来算作 unconfined 域。
-
在 docker18.03 版本中,以 docker start/attach 方式和 docker exec 方式运行的进程均受 AppArmor (docker-default 文件)域的控制。
2 实验现象&解释
2.1 显示定义AppArmor 规则
2.1.1 实验方式
通过追踪 POC 运行状态,发现其在运行期间访问了如下路径:
[ ] /proc/<pid of each process>/cmdline
[from docker exec>/exe ] /proc/<pid of the runc executed
[from docker exec>/fd/3 ] /proc/<pid of the runc executed
因此,可通过显式地在/etc/apparmor.d/abstractions/base加上下述规则,对AppArmor防御效果进行测试:
audit deny @{PROC}/@{pid}/cmdline rwklx, #[RULE-A]
audit deny @{PROC}/@{pid}/exe rwklx, #[RULE-B]
audit deny @{PROC}/@{pid}/fd/* rwklx, #[RULE-C]
2.1.2 实验现象
【某公有云主机】
增加 [RULE-A] 后,POC 攻击被拦截,日志有 DENY 信息。
增加 [RULE-B] 和 [RULE-C],无法拦截 POC 攻击。
【本地主机】
增加 [RULE-A] 后,使用 docker attach 或 docker exec 方式运行 POC:
-
POC 攻击每次都失败;
-
AppArmor 日志中每次都有 DENY,持续增加。
增加 [RULE-B] 和 [RULE-C] 后,无法拦截 POC 攻击。
2.1.3 现象解释
[RULE-A]生效:原因是 cmdline 是一个实际文件(至少不是 symlink)。
[RULE-B]不生效:原因是 PATH-B 指向宿主机 runc,而 AppArmor 只能过滤最终指向的文件的实际路径。
[RULE-C]不生效:原因是 PATH-C 也指向宿主机 runc。
2.2 docker 18.03测试分析
2.2.1 实验方式
鉴于 docker exec 和 docker attach 命令受 AppArmor 域控制不同,将采用以下两种方式进行 CVE-2019-5736 漏洞实验:1)exec 命令运行 POC 和 /bin/sh;2)attach 命令运行 POC,exec 命令运行 /bin/sh。
2.2.2 实验现象
【某公有云主机】
在 docker-default 规则防御下:i)POC攻击不被拦截;ii)AppArmor 日志中 audit 无对应 DENY 记录。
【本地主机】
-
使用 exec 方式运行 POC 和 /bin/sh:i)POC 攻击不被拦截;ii)AppArmor日志中 audit 无对应 DENY 记录。
-
使用 attach 运行POC,exec 方式运行 /bin/sh:i)POC 攻击不被拦截;ii)AppArmor 日志中 audit DENY 随机出现(重复4-5次实验),出现的话只有一条 DENY。
注:此处新建 malicious_directory 文件夹,以示与前文基础复现实验的区分。
2.2.3 实验解释
-
POC 攻击成功
使用相同 exec 命令执行 POC 和 /bin/sh,在 AppArmor 看来属于同一个域,故不会有规则进行拦截。对于 docker 18.03,由于 exec 和 attach 属于 AppArmor 同一个域控制,因此这种方式下 POC 攻击也不会被拦截。
-
exec 攻击无 DENY 出现
AppArmor 在<moby>/profiles/apparmor/template.go文件内有“file”,该规则使得任意文件访问默认不被拦截。这使得 POC 在读取 PID 和 fd 时不会被auditd 审计到,故 AppArmor 日志信息中不会出现 DENY。
-
exec/attach 攻击 DENY 随机出现1条
可能原因未知,暂时无法给出合理解释。
2.3 docker 17.12测试分析
2.3.1 实验方式
实验环境为 docker 17.12 版本,ubuntu 18.04,实验方式同前。
2.3.2 实验现象
【某公有云主机】
-
使用 exec 方式运行 POC 和 /bin/sh:i)POC 攻击不被拦截;ii)AppArmor日志中 audit 无对应DENY记录。
-
使用 attach 运行 POC,exec 运行 /bin/sh:i)POC 攻击被拦截(POC 攻击程序卡在 open /proc/<runc-pid>/exe);ii)AppArmor 日志中 audit 有对应DENY 记录。
【本地主机】
-
使用 exec 方式运行 POC 和 /bin/sh:i)POC 攻击不被拦截;ii)AppArmor日志中 audit 无对应 DENY 记录。
-
使用attach运行POC,exec运行/bin/sh:i)POC每次攻击失败;ii)AppArmor 日志中 audit 有对应 DENY 记录(重复 6-7 次,每次 DENY 有10条,都卡在获取 fd)。
2.3.3 现象解释
-
POC攻击成功/失败
成功:使用 exec 命令执行 POC 和 /bin/sh,在 AppArmor 看来属于同一个域,故不会有规则进行拦截,攻击不会被拦截。
失败:对于docker 17.12,exec 和 attach 不属于同一个域控制,AppArmor docker-default中没有对应规则(无此白名单条目),故 POC攻击会被AppArmor 规则拦截导致失败。
-
exec无DENY出现
原因如前 docker 18.03 一节中所述,不再赘述。
-
exec/attach有DENY出现,均为10条
可能原因:是在读取 fd 时出现竞态,即同时有其他进程在读取fd,导致读取一直失败,最终读取次数过多触发 AppArmor 报警机制。
验证猜想:修改 POC 代码,在读取 fd 一定次数后退出循环等待,而不是一直等待。
结果发现:当读取少量次数(小于1000次)后,未能出发 AppArmor 审计机制,未见 DENY 信息;当读取一定次数(2000)后,AppArmor 审计机制会随机被触发,DENY 信息随机出现;当读取次数达到很大规模(大于10000)后,DENY 信息一定出现。这种现象在一定程度上说明,随着读取次数的增多,与其他进程竞争碰撞的可能性越大,最终导致竞态产生,并触发 AppArmor 报警机制,产生 DENY 信息。
注意,在 docker 17.12 版本中,由于 exec 和 attach 处于不同域的原因,即使读取次数再少,POC 攻击也不会成功。
3 其他发现
除上述与 CVE-2019-5736 漏洞相关的实验现象之外,还有一些其他实验现象的发现。
-
Debian系的操作系统开启了SELinux防护机制,这会导致docker无法正常运行某公有云主机和本地主机上的实验现象基本一致,说明该平台云主机上并没有使用HIDS进行防护。
-
AppArmor docker-default 文件是 raw_data 格式,其会默认编译到 dockerd二进制里面,故无法被逆向出来进行查看和修改。这个问题目前连开发者也无法解决,对常见的云厂商 dockerd 进行逆向后发现,其使用的都是原版 default 文件。
-
AppArmor 日志的 DENY 记录与否,与 audit,deny,allow 有一定关系。值得注意的是,其与攻击成功或失败无直接关联,日志 DENY 记录和攻击成败更像是并行的两条线。
-
Seccomp 和 AppArmor 被绕过机制不同。Seccomp的绕过(攻击者使用syscall int 而非 syscall name),是解析前 syscall 匹配机制出现问题;而AppArmor 绕过(CVE-2019-5736 POC),是解析后 proc/self/exe 链接重定向出现问题。
展望
随着云计算的普及,docker 和 K8s 等云平台工具的使用越来越频繁,与此同时潜在的安全威胁也越来越多。目前 docker 的防护仍高度依赖 Linux 核防护机制,Linux安全大厦看似稳固,近年来相继曝出的一系列 CVE 漏洞却表明,容器云的这种安全防护模式似乎并非牢不可破。
作为云计算安全人员,浅尝辄止地复现 CVE 漏洞仅仅是流于表面,真正需要的是深入研究 CVE 漏洞背后原理,以及思考其给相关云安全防护机制可能带来的负面影响。本文以 CVE-2019-5736 为例,探讨了docker runC漏洞,并测试了AppArmor 的防护效果,最终得出一系列比较有价值的结论。以攻促防,攻防交互,才能更好地全面提升云计算安全,深信服创新研究院云安全研究团队将对此持续探索。
深信服千里目安全实验室
深信服科技旗下安全实验室,致力于网络安全攻防技术的研究和积累,深度洞察未知网络安全威胁,解读前沿安全技术。
● 扫码关注我们
原文始发于微信公众号(深信服千里目安全实验室):【云攻防系列】从CVE-2019-5736漏洞浅析AppArmor防御机制
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论