容器不是沙箱
很多人把容器当成轻量虚拟机,觉得「跑在容器里就隔离了」。实际上容器只是命名空间加 cgroups 加 capabilities,共享的是同一个内核。一旦配置放开某个口子,容器里的进程就能摸到宿主机。
真正危险的配置不多,常见的就是下面三个。它们有一个共同点:在 compose 里写起来都只有一行,看起来人畜无害。
前提一:privileged: true
services:
app:
image: some/app
privileged: true
这一行等于把容器变成「几乎不受限的 root」:所有 capabilities 打开、可以访问宿主设备、可以改内核参数。进了这个容器,挂载宿主根分区、写 /etc/cron.d、操作 /dev,都不需要什么高深技巧。
它常被用来「解决权限问题」——比如某个程序要访问 USB 设备或需要 raw socket。正确的替代是只加需要的那一个 capability,或者只挂载对应的设备节点。
前提二:挂载 docker.sock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
把 Docker 守护进程的 socket 交给容器,等于把宿主机的 root 权限交出去:容器里执行 docker run -v /:/host --privileged ...,就能以宿主机身份为所欲为。
很多 CI、监控、面板类工具需要这个 socket。更安全的做法是用只读的 socket 代理(限制可调用的 API),或者改用专门的、权限受控的接口。
前提三:可写的宿主机目录挂载
volumes:
- /:/host
- /etc:/host-etc
挂载本身不是问题,问题是可写且范围过大。挂 / 或 /etc 且带写权限,容器里改一个 systemd 单元或 authorized_keys,宿主就易主了。挂 /var/run 同样危险,那里有 socket。
原则是:只挂业务需要的那一个子目录,能只读就只读(:ro),能不用 bind mount 就用命名卷。
加固怎么写进 compose
services:
app:
image: some/app:1.2.3
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
tmpfs:
- /tmp
user: "1000:1000"
no-new-privileges 阻止 setuid 提权,cap_drop: ALL 把能力清零再按需加,只读根文件系统让写入无处落脚,非 root 用户减少逃逸后的可利用面。这几项加起来,能挡掉大部分「配置不当导致的逃逸」。
还有一层:内核
容器共享内核,意味着内核漏洞就是逃逸通道。及时打内核补丁、开启 seccomp 与 AppArmor/SELinux,比任何应用层配置都重要。多租户场景更彻底的做法是 Kata Containers、gVisor 这类强隔离运行时。
我的判断
容器逃逸的新闻总爱讲「0day」,但实际事件里,绝大多数是配置给的权限太大。反过来也成立:把 privileged、docker.sock、可写根挂载这三样去掉,剩下的攻击面会小到需要真正的内核漏洞才能突破。
所以审一份 compose 时,先搜这三个关键词,比读一百页安全文档有用。










暂无评论内容