Containerd是基于OCI规范实现的一款工业级标准的容器运行时。 Containerd在宿主机中管理容器生命周期,如容器镜像的传输和存储、容器的执行和管理、存储和网络等。 containerd-shim是用作容器运行的载体,实现容器生命周期管理, 其API以抽象命名空间Unix域套接字方式暴露,该套接字可通过根网络名称空间访问。 因此,一旦普通用户获得主机网络访问权限(通过启动主机网络模式的容器),则可以访问任一容器的API,并以此提权。
在主机网络名称空间中运行容器是不安全的:
- 请勿使用
docker run --net = host
运行Docker容器 - 请勿使用
.spec.hostNetwork:true
配置运行Kubernetes Pods
一、Containerd CVE-2020–15257
- 漏洞级别
该漏洞社区评分为5.2 分/10分(中等)的安全级别,需要具备一定的触发条件、攻击路径较长。
- 提权条件
如果不受信任的用户在平台上无法创建主机网络模式(hostnetwork)的容器,或者容器内的进程是以非root用户(UID 0)运行,则不会触发该漏洞,具体满足以下多个条件:
- 容器使用主机网络
hostnetwork
部署,此时容器和主机共享网络命名空间; - 容器使用root用户(即UID 0);
containerd
版本在 <=1.3.7
- 漏洞确认
对于在易受攻击的系统上运行容器的用户,可以通过禁止主机网络模式,或者通过确保此类容器以非零UID/GID运行来缓解此问题。用户可将containerd
版本更新到最新版本。 此外,更新前创建并运行的容器仍会受到攻击,因此用户需要确保所有容器完全停止,然后在更新后重新启动。
对于不确定CVE-2020-15257
是否会影响的用户,可以使用以下命令快速确定受影响的containerd
版本创建的容器是否仍在运行。 如果有返回结果,则说明存在。
$ cat /proc/net/unix | grep 'containerd-shim' | grep '@'
- 特别说明
即使替换了补丁版本的containerd
,使用主机网络也是不安全的。
二、 安全分析
2.1 代码定位
- containerd/containerd
- runtime/v1/shim/client/client.go: WithStart(), newCommand()
- cmd/containerd-shim/main_unix.go: serve()
- cmd/containerd-shim/shim_linux.go: newServer()
- containerd/ttrpc (via vendor/github.com/containerd/ttrpc/unixcreds_linux.go)
- unixcreds_linux.go: UnixSocketRequireSameUser()
2.2 漏洞细节
containerd是一个容器运行时的核心组件,其管理基于runc的容器,在Kubernetes中可通过Docker(dockershim)方式或CRI方式使用。Docker架构如下图所示。
Docker架构包含docker、containerd、 containerd-shim、runC等组件。
containerd
是容器运行时,作为守护进程,containerd
通过containerd-shim
调用runc
管理容器。containerd
作为守护进程,其对外暴露用于容器生命周期管理(如容器运行管理、镜像管理等)的gRPC接口。containerd
生成containerd-shim
进程对容器的生命周期进行一对一的管理。
为了提供自己的gRPC(实际上是ttrpc,一种裁剪版gRPC协议)API,containered-shim
监听Unix域套接字。 这些是Linux独有的Unix域套接字,其使用以空字节开头的长度前缀键,并且可以包含任意二进制序列。 它们在抽象Unix域套接字sun_path中嵌入了结尾的空字节,其可阻止常见的Unix工具(例如socat)与其连接。
- @/containerd-shim///shim.sock\0
- @/containerd-shim/.sock\0
containered-shim
不仅具有绑定和侦听此类套接字的能力,它还支持从其父进程接收任意套接字文件描述符。 containerd
通过此方法,先创建抽象的Unix套接字并对其进行监听,在containerd-shim
进程启动后,可以使用该句柄进行初始化,接下来containerd-shim
启动ttrpc
服务。 containerd-shim
使用标准的Unix域套接字功能来验证传入的连接是否具有与其相同的UID和EUID(通常为UID:0和EUID:0)。
containerd-shim
所使用的抽象的Unix域套接字,是绑定在主机的网络命名空间上的。当一个恶意容器同样处于主机的网络命名空间中,该容器内的root
用户,可以通过譬如netstat -xl
或者/proc/net/unix
来扫描,找到containerd-shim
的套接字,然后链接containerd-shim
的API以执行命令。
containerd-shim
暴露了许多危险的API,可用于逃避容器和执行特权命令。在使用的containerd(-shim)
的两个主要版本1.2.x和1.3.x中,暴露以下能力:
- 任意文件读取
- 任意文件追加
- 任意文件写入
- containerd-shim中的任意命令执行
- 从runc config.json文件创建容器
- 启动创建的容器
大多数用户实际上不受此CVE的影响。如果在未指定–user的情况下运行docker run --net = host
,则会受到影响。如果Kubernetes用户使用containerd
作为CRI运行时并使用.spec.hostNetwork:true
配置运行pod且未设置.spec.securityContext.runAsUser
,则受到影响。
该CVE修复了containerd
的v1.4.3/v1.3.9版本,其将抽象套接字修改为/run/containerd
下基于文件的普通UNIX套接字。
2.3 问题容器
Docker执行以下命令:
$ docker ps -a --filter 'network=host'
Kubernetes执行以下命令:
$ kubectl get pods -A -o json | jq -c '.items[] | select(.spec.hostNetwork==true) |[.metadata.namespace, .metadata.name]'
2.4 是否不使用network就一劳永逸
并不是的。 因为除了容器外,还有很多程序使用了抽象套接字。 这些程序包括:
- dbus
- ibus
- irqbalance
- iscsid
- iscsiuio
- LXD
- multipathd
- X Window System
- [historical] systemd before v212
- [historical] Unity (desktop environment)
- [historical] upstart
等等
要查看主机上是否使用了抽象套接字,可运行grep -ao '@.*' /proc/net/unix
:
1 | grep -ao '@.*' /proc/net/unix ⏎ |
实际上,其实关于containerd
的CVE-2020-15257漏洞,一些开发人员和用户早已知晓,但其一直未被视作安全漏洞,因为使用主机网络名称空间并不安全,无论是否存在containerd
套接字。 虽然containerd
项目考虑到攻击的影响范围而更改了漏洞策略,但上述的软件应该不会将抽象套接字视作漏洞。
三、安全建议
在需要使用主机网络时,需要考虑以下安全策略
- 以非root用户运行容器
- AppArmor
- SELinux等
Docker
可以使用端口映射方式: docker run -p
通信时执行以下命令:
1 | docker inspect -f '{{.NetworkSettings.IPAddress}}' nginx ⏎ |
或者修改docker proxy
1 | cat <<EOF > /etc/docker/daemon.json ⏎ |
以及其他方案,如
- AppArmor
- SELinux等
Kubernetes
对于使用Kubernetes的用户,可以使用以下方式或特性
kubectl get pods -o wide
获取IP进行访问- 内部DNS(CoreDNS)
- kubectl port-forward
- AppArmor
- SELinux等
3.1 以非root用户运行容器
对于Docker,运行docker run --net=host --user 12345 --security-opt no-new-privileges
。 确保选择与主机上现有用户帐户没有冲突的UID号。
无需指定no-new-privileges
,但是建议禁止使用sudo之类的特权。
对于Kubernetes,指定Pod相关字段.spec.[]containers.securityContext
:
1 | hostNetwork: true |
对于普通用户使用1024以内端口,需要如下配置:
1 | echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/99-user.conf ⏎ |
3.2 使用AppArmor
AppArmor是Linux安全模块,供多个发行版使用,包括Ubuntu,Debian,SUSE和Google COS。
以下AppArmor配置文件可用于禁止容器使用抽象套接字:
1 | include <tunables/global> |
可以按如下所示将此配置文件应用于Docker容器:
1 | sudo apparmor_parser -r docker-no-abstract-socket |
关于在Kubernetes中如何使用AppArmor特性
3.3 使用SELinux
RHEL/CentOS和Fedora的SELinux策略,用于保护主机上的抽象套接字:
1 | getenforce |
默认情况下,SELinux已启用Podman和OpenShift。 要为Docker启用SELinux,请按以下方式配置/etc/docker/daemon.json
:
1 | cat <<EOF > /etc/docker/daemon.json |