返回博客

别再用--privileged了!Docker安全10条铁律少一条都危险

别再用--privileged了!Docker安全10条铁律少一条都危险

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 用户

--user=1000

权限过大

丢弃所有 capability

--cap-drop=ALL --cap-add=CHOWN

特权模式

永远别用

删掉 --privileged

可提权

禁止权限提升

--security-opt=no-new-privileges:true

无资源限制

设上限

-m=128m --cpus=2

可写文件系统

只读根

--read-only

latest 标签

固定版本

nginx:1.27.2-alpine

TCP 暴露

关掉,用 SSH

dockerd -H unix:///var/run/docker.sock

镜像未扫描

CI 里加 Trivy

trivy image myapp:latest

docker.sock 挂载

能不挂就不挂

评估替代方案

说句实在的

Docker 安全这件事,难的不是技术,是意识。

上面列的每一条,都是几年前的老话题了。CVE 换了一批又一批,底层的安全原则没怎么变。

大多数团队的实际状态是:本地开发用 --privileged 跑得欢,生产环境用 root 容器也不觉得有问题,镜像拉下来直接 latest 标签往上一推。

容器隔离不是安全边界,它只是部署便利的抽象层。安全边界需要你自己建。

从那张清单里挑几条你还没做的,今天就开始改。不需要全做完,先从非 root 运行和去掉 --privileged 开始。