docker run --privileged 这一行命令,可能在你的服务器上撕开一个口子。
2026 年 5 月,Docker Engine 被爆出 CVE-2026-34040——攻击者发一个超过 1MB 的 HTTP 请求,就能绕过授权插件,直接创建特权容器,拿到宿主机 root 权限。AWS 凭证、SSH 密钥、Kubernetes 配置,全都能偷走。
更早一点,CVE-2026-17106 利用 Docker CLI 处理归档文件时的竞态条件,把符号链接替换掉,文件写到宿主机任意位置。也是容器逃逸。
Docker 用了这么多年,大部分人对安全的理解还停留在"容器之间互相隔离"。这个理解没错,但不完整。
容器隔离不是安全边界
Docker 的隔离靠的是 Linux namespace 和 cgroups。namespace 管可见性——进程、网络、文件系统各自看到自己的视图。cgroups 管资源——CPU、内存配额。
但这两样东西不等于安全隔离。
namespace 是脆弱的。容器里的 root 和宿主机上的 root 是同一个 UID 0。一旦容器逃逸,攻击者直接以 root 身份出现在宿主机上。
cgroups 更不管安全,它只防资源耗尽,不防恶意行为。
Docker 的隔离是"组织性"的,不是"安全性"的。 它让应用好部署、好管理,但不会替你挡住攻击。
所以你需要在三个层面自己加固:镜像、容器运行时、Docker 守护进程。
镜像层:你拉的东西决定了一切
别用 latest 标签
docker pull nginx:latest 拉下来的镜像,今天和下个月可能完全不一样。你无法复现,无法审计,出了问题也不知道之前跑的是什么版本。
指定具体版本号:
FROM nginx:1.27.2-alpine
这不是强迫症,是基本卫生。
用最小化基础镜像
Alpine 只有 5MB 左右,自带的东西少,攻击面就小。Debian 全家桶动辄上百 MB,里边的 apt 包管理器、shell 工具都可能被利用。
2025 年底 Docker 开源了 Hardened Images(DHI),基于 distroless,没有 shell、没有包管理器,默认非 root 用户运行,CVE 数量接近零。如果对安全要求高,值得考虑。
扫描,每次构建都扫
镜像里的漏洞不是静态的。今天没问题的 OpenSSL,明天可能就出 CVE 了。
用 Trivy 或 Docker Scout 在 CI 流水线里自动扫描:
trivy image myapp:latest
发现高危漏洞就阻断流水线,不让这个镜像进生产。
签名验证
2025 年 8 月,Docker 正式下线了 Notary v1 签名服务。DOCKER_CONTENT_TRUST=1 对官方镜像不再生效。
现在用 Cosign 或 Notation 做镜像签名,签名和镜像一起存在镜像仓库里,不需要单独的信任服务器。部署前验签,防止镜像被篡改。
运行时:最容易犯的 5 个错
1. 用 root 跑容器
Docker 默认用 root 启动容器。这意味着容器里的进程一旦逃逸,直接就是宿主机 root。
改掉:
RUN adduser -D appuser
USER appuser
或者在运行时指定:
docker run --user=1000 myapp
如果你的容器不需要 root 权限就别给。这是容器安全里投入产出比最高的一步。
2. 开了 --privileged
--privileged 给容器所有 Linux capability,解除 cgroups 限制,相当于让容器"裸奔"在宿主机上。
99% 的应用不需要它。如果有人说"加上 --privileged 就能跑了",那说明他在掩盖一个配置问题。
正确做法:先 --cap-drop=ALL,再按需 --cap-add 逐个加回来。
docker run --cap-drop=ALL --cap-add=CHOWN myapp
3. 没有资源限制
Docker 不给容器设默认的 CPU 和内存上限。一个失控的容器可以把宿主机资源吃光,导致所有服务挂掉——这不是安全漏洞,但效果跟被攻击差不多。
docker run -m=128m --cpus=2 myapp
别忘了加。
4. 挂载了 docker.sock
-v /var/run/docker.sock:/var/run/docker.sock 这个操作等于把宿主机的 root 权限给了容器。
Docker 守护进程本身以 root 运行,能管理所有容器。谁拿到了这个 socket,谁就能在宿主机上为所欲为——启动特权容器、挂载根目录、改任何文件。
Jenkins、CI/CD 工具经常需要这个挂载来构建镜像。如果你必须这么做,至少确保这个容器不暴露到公网,而且及时清理。
5. 允许权限提升
容器里的进程默认可以通过 setuid/setgid 提权。加上这个标志就堵死了:
docker run --security-opt=no-new-privileges:true myapp
一行参数,零成本,但很多人不知道。
守护进程:最危险的一层
Docker daemon 以 root 身份运行,控制着整台机器上的所有容器。它一旦被攻破,全盘皆输。
不要暴露 TCP socket
默认 Docker 只监听 Unix socket(/var/run/docker.sock),只有本地 root 能访问。
一旦你开了 TCP 监听(-H tcp://0.0.0.0:2375),而且没加密,等于把 root 权限开放给了全网。这种事故每年都在发生。
如果必须远程管理 Docker,用 SSH 隧道或 TLS 双向认证。SSH 方式更简单,复用你已有的密钥体系。
开启 rootless 模式
Rootless 模式让 Docker daemon 以普通用户身份运行,不再需要 root。即使 daemon 被攻破,攻击者也只拿到普通用户权限,影响可控。
dockerd-rootless.sh --user myuser
有些功能不兼容(比如端口 < 1024 需要 extra 配置),但生产环境值得用。
关闭容器间通信
默认情况下,同一台机器上的容器可以互相通信。一个容器被攻破,攻击者就能扫描、攻击同网段的其他容器。
dockerd --icc=false
然后按需手动创建网络,只让需要通信的容器在同一个网络里。
一张清单带走
如果你只想记住最关键的几条:
风险 | 修复 | 一行命令 |
|---|---|---|
root 运行 | 非 root 用户 |
|
权限过大 | 丢弃所有 capability |
|
特权模式 | 永远别用 | 删掉 |
可提权 | 禁止权限提升 |
|
无资源限制 | 设上限 |
|
可写文件系统 | 只读根 |
|
latest 标签 | 固定版本 |
|
TCP 暴露 | 关掉,用 SSH |
|
镜像未扫描 | CI 里加 Trivy |
|
docker.sock 挂载 | 能不挂就不挂 | 评估替代方案 |
说句实在的
Docker 安全这件事,难的不是技术,是意识。
上面列的每一条,都是几年前的老话题了。CVE 换了一批又一批,底层的安全原则没怎么变。
大多数团队的实际状态是:本地开发用 --privileged 跑得欢,生产环境用 root 容器也不觉得有问题,镜像拉下来直接 latest 标签往上一推。
容器隔离不是安全边界,它只是部署便利的抽象层。安全边界需要你自己建。
从那张清单里挑几条你还没做的,今天就开始改。不需要全做完,先从非 root 运行和去掉 --privileged 开始。
