您可以在生产环境中运行 Docker 数月而不会出现明显问题。容器启动,应用程序响应,没有任何中断。然后,一个暴露的端口或一个配置错误的权限就会为攻击者创造一个无需获得的立足点。大多数 Docker 安全错误在出现问题之前看起来并不像错误。
本文介绍了使容器环境面临风险的特定配置、每种配置对攻击者的影响,并以您今天可以针对自己的设置运行的清单作为结尾。
为什么 Docker 安全比看起来更难
容器给人一种孤立的感觉。你启动一个,它运行自己的进程空间,从它的内部,下一个容器不存在。你确实得到了孤立,但这只是部分的。容器共享主机的内核,这意味着容器内的进程在特定条件下可以完全访问主机系统。
Docker 船舶的配置是为了方便开发人员,而不是为了生产强化。根访问权限打开。所有端口均可绑定到所有接口。没有运行时监控。大多数开发人员接受这些设置,发送容器,然后继续。这是一个合理的入门方法;这还不是一个完整的安全态势。
根据 红帽 2024 年 Kubernetes 安全状况报告,67% 的组织由于容器或 Kubernetes 安全问题而延迟或减慢了应用程序部署。这种摩擦通常不是来自攻击。这是因为团队发现他们的容器设置需要强化,但他们没有内置。
我们经常看到在生产环境中运行的容器与开发人员本地计算机上的配置相同。这就是 Docker 安全错误往往会悄悄复合的地方,在某些内容经过审核或出现故障之前不会出现明显的症状。
造成这些差距的错误是特定的、可预测的,而且大部分是可以避免的,从配置级别开始。
常见的 Docker 配置错误
大多数容器漏洞并不是从零日漏洞开始的。他们从第一天的配置开始,没有太多考虑网络暴露或权限范围。
默认 Docker 设置是为了工作而构建的。功能性和安全性之间的差距是 Docker 容器安全风险累积的地方,尤其是在部署后就不再重新访问的自托管设置中。
我们经常看到这种模式:公共 IP 服务器上的容器具有与初始部署时完全相同的端口绑定、用户设置和网络配置。
以 root 身份运行容器
当您启动 Docker 容器而不指定用户时,它将以 root 身份运行。这意味着容器内的任何进程(包括您的应用程序)都在容器的命名空间内拥有根级权限。

容器内的根与主机上的根不同,但分离并不是绝对的。针对运行时的权限提升漏洞(例如记录详尽的 runc CVE-2019-5736 和类似的运行时缺陷)通常需要根容器进程才能成功。
非根容器消除了这些漏洞利用所依赖的根进程要求,从而显着缩小了此类漏洞的攻击面,尽管它们并不能完全消除容器逃逸风险。
在 Dockerfile 中添加 USER 指令可以解决这个问题。一些官方映像附带了一个非特权用户,您可以使用 USER 指令激活,但许多仍然默认为 root,没有现成的应用程序用户。在这些情况下,您可以在切换到 Dockerfile 之前在 Dockerfile 中创建该用户。对于大多数自托管设置,这一单一更改消除了整个类别的升级风险。
将太多端口暴露给公共互联网
当您使用 Docker 发布端口时,Docker 会直接编写自己的 iptables 规则。这些规则在主机级防火墙规则之前运行。这是一个 社区报告的众所周知的行为 和 记录在 Docker 的数据包过滤指南中,不是错误配置,这意味着 UFW 和类似工具不会阻止 Docker 已经打开的内容。

Docker 直接写入 iptables,绕过许多 Linux 主机上的 UFW 和 firewalld 默认值。这意味着即使您的防火墙显示已配置,绑定到 0.0.0.0 的端口也可以公开访问。云安全组和 DOCKER-USER 链规则仍然可以阻止该流量,因此实际暴露程度取决于您的特定网络设置。
如果可能,将服务绑定到 127.0.0.1,通过反向代理路由面向公众的流量,并仅发布真正需要外部访问的流量。反向代理是控制主机外部暴露内容的最可靠方法。
忽略容器之间的网络隔离
该网络上的任何容器都可以不受限制地访问其上的任何其他容器。默认网桥在共享它的容器之间不应用流量过滤,并且大多数设置永远不会更改该配置。

如果一个容器受到损害,这种开放的通信就会成为横向移动路径。前端容器可以访问数据库、内部 API 或同一默认桥接网络上的任何其他内容,即使该访问从未有意为之。
用户定义的网络使您可以明确控制哪些容器可以通信,但所有服务共享的单个自定义网络仍然允许免费的容器间流量。真正的隔离需要将不应该相互通信的服务放在单独的网络上。关闭默认桥是起点,而不是终点线。
俯瞰 Docker 套接字
/var/run/docker.sock 处的 Docker 套接字是整个 Docker 引擎的控制接口。将其安装到容器中可以让容器直接通过 API 访问主机上运行的守护进程。

通过该访问权限,容器可以启动新容器、挂载主机目录、检查和修改正在运行的容器以及有效控制主机。攻击面相当于主机上的 root,这就是为什么任何需要套接字访问的工具都值得仔细评估。
对于大多数用例,有更安全的替代方案:作用域 API 或 Docker 管理工具 不需要套接字访问。 Docker-in-Docker 有其自身的安全性和操作权衡,并不是简单的替代品。
配置错误造成了最初的暴露。形象和依赖性的选择决定了暴露如何随着时间的推移而复合。
比容器更持久的图像和秘密错误
当您停止容器时,容器内的配置错误也会随之停止。当您从带有漏洞或硬编码凭据的映像进行重建时,容器会重新出现问题。图像级错误不会在部署之间重置。
他们将映像带到每个提取该映像的环境、每个存储该映像的注册表以及运行该映像的每个团队成员。这种持久性使图像和机密管理成为一种独特的风险类别,值得与配置分开进行审核。
我们经常看到这种模式:在项目开始时仔细选择的图像,此后从未重建过,慢慢偏离它最初代表的安全基线。
使用不受信任或过时的图像
公共登记处向任何人开放。恶意镜像已通过 Docker Hub 进行分发,其中包含嵌入在层历史记录中的加密矿工和后门,这些镜像在容器重新启动后仍然存在。提取之前的验证很重要,尤其是对于来自非官方或未知发布者的图像。

另一个问题是陈旧性。您六个月前拉取的官方镜像一直在累积未修补的 Docker 漏洞,每个 CVE 都针对其软件包进行了披露,此后从未重建过。图像没有损坏。它只是不再流行了。
Sonatype 的 2024 年软件供应链状况报告 研究发现,95% 的情况下,存在漏洞的组件已被使用,修复版本已经可用,并且 80% 的应用程序依赖项在一年多的时间里仍未升级。该模式也与 Docker 基础镜像相关,因为它们依赖于相同的开源包。
使用来自经过验证的发布商的官方图像并固定特定版本标签,而不是依赖“最新”。建立定期的重建节奏,使您的图像保持最新状态。
Dockerfile 和 Compose 文件中的硬编码秘密
当您停止容器时,写入 Dockerfile ENV 或 ARG 指令、硬编码到 Compose 环境块、作为构建参数传递或存储在提交给版本控制的 .env 文件中的凭证不会消失。它们保留在图像层历史记录或源代码控制中,任何可以访问的人都可以访问。

这是最容易被忽视的 Docker 安全错误之一,因为它在开发过程中不会造成明显的问题。 ENV 指令中的 API 密钥可以正常工作。它也位于您的存储库中,融入您的映像中,并分发到该映像所在的任何地方。
现代 Docker Compose 支持本机机密机制,该机制可以在运行时挂载凭据,而无需将其烘焙到映像中。 Docker 的秘密 API 和外部秘密管理器遵循相同的原则。这些选项可以使凭证完全脱离构建工件和提交的文件。
运行时环境变量是对硬编码凭据的改进,但它们仍然通过 Docker 检查输出、日志和故障转储公开。它们是从根深蒂固的秘密中迈出的一步,而不是最终的解决方案。
不定期更新容器镜像
运行同一个镜像几个月是一种常见的习惯。在新漏洞被披露后、但在重建之前的每一天,您的容器都带有一个暴露窗口,该窗口在没有任何明显变化的情况下不断增长。
制定一致的重建计划。尽可能自动化该过程,并定期针对当前映像运行漏洞扫描程序。目标不是完美。它缩短了补丁发布和部署之间的时间。
在快速部署中,访问控制和监控可能会被降低优先级。它们也是事件未被发现的时间最长的类别。
访问控制和可见性差距
当容器使用可靠的配置和当前映像运行后,仍然会出现两类故障。两者本质上都是不可见的:除非有人使用,否则您不会注意到薄弱的访问控制问题;除非您需要调查从未记录的活动,否则您不会注意到监视间隙。
相同 红帽 2024 研究 发现 42% 的团队缺乏足够的能力来解决容器安全和相关威胁。
我们发现,监控差距通常在事件调查期间而不是之前出现。当可见性成为优先事项时,它通常是在响应某些事情而不是阻止它。
弱身份验证和暴露的管理仪表板
无需身份验证的公共 IP 上的容器管理仪表板不需要经验丰富的攻击者。它要求他们知道地址。这个标准比大多数团队意识到的要低。

自托管监控和管理工具通常附带可在所有网络接口上访问的 Web 界面。将它们保留在公共 IP 上而无需在它们前面进行身份验证,相当于使管理面板保持解锁状态。
身份验证、反向代理和专用网络放置是基线。访问控制是您添加到任何管理界面的配置步骤,而不是出厂时启用的内容。
同样的原则也适用于 Docker CLI 和 GUI 管理;无论界面如何,对守护程序的管理员级别访问都会带来相同的风险。
不监控容器正在做什么
如果容器遭到破坏,攻击者的活动会留下痕迹:进程行为更改、异常网络连接和意外文件修改。如果没有适当的日志收集,该跟踪就不会以您可以采取行动的形式存在。
集中式日志收集、容器审核日志记录和运行时监控工具为您提供数据,以便在异常活动复合之前检测到异常活动。目标不是分析每一行。当您需要调查时,它可以提供可用的数据。
在生产环境中静默运行、没有日志管道、也没有警报的容器设置的维护成本并不低。他们未经检查。这是两种不同的操作状态。
为什么基础设施环境也很重要
容器安全性始于配置,但配置运行在基础设施之上。网络配置错误、共享资源或没有网络级过滤的主机会产生影响其上方每个容器的条件。正确设置容器和正确配置服务器是两个独立的任务。
许多 Docker 安全漏洞因容器本身继承的条件而被放大:
- 租户之间没有硬件隔离的共享租户服务器
- 未打补丁运行的主机内核
- 没有内置网络级过滤的主机
这并不能消除上述配置步骤的需要,因为无论基础设施层如何,适当的容器强化都很重要。从孤立的基础设施开始可以消除一层担忧。
在 Cloudzy,我们根据您的设置要求提供两种路径:
- Linux虚拟专用服务器:一个干净的环境,可以自行部署 Docker 并应用本文中的强化步骤
- Portainer VPS:预装 Portainer 的一键选项;服务器启动,您已经在仪表板中
两种选项都在相同的基础设施上运行:KVM 虚拟化、高达 5.7 GHz 升压时钟的 AMD Ryzen 9 CPU、DDR5 内存、NVMe SSD 存储、高达 40 Gbps 的网络以及通过 BuyVM 过滤提供的免费 DDoS 保护,覆盖全球 12 个地点,提供 99.95% 的正常运行时间 SLA。
为了更深入地了解在 VPS 上运行 Portainer,我们在专门的文章中对此进行了介绍。
Docker 部署的实用安全检查表
上述 Docker 安全错误主要来自于一次做出且从未重新访问过的单一配置决策。针对现有设置运行此清单可以发现这些差距。它的作用是审核,而不是部署指南。
这些 Docker 安全最佳实践涵盖了如何保护 Docker 容器免受上述最常见配置故障的影响。
快速参考:所有 9 个错误
| 错误 | 类别 | 一行修复 |
| 以 root 身份运行 | 配置 | 添加 用户 指令到你的 Dockerfile |
| 端口绑定到 0.0.0.0 | 配置 | 绑定到 127.0.0.1 并通过反向代理路由 |
| 无网络隔离 | 配置 | 根据访问需求将服务拆分到单独的用户定义的网络中。 |
| Docker 套接字已安装 | 配置 | 拆下安装座;使用作用域 API 或替代方案 |
| 不受信任或过时的图像 | 图像 | 使用带有固定版本标签的官方图像 |
| 硬编码的秘密 | 图像 | 将凭证移至运行时环境变量或机密管理器 |
| 没有映像重建计划 | 图像 | 设定每月的重建节奏;尽可能实现自动化 |
| 未经身份验证的仪表板 | 使用权 | 添加身份验证并将管理 UI 移动到专用网络 |
| 无容器日志收集 | 使用权 | 设置集中日志记录和运行时监控 |
我们建议首先针对现有设置运行它,因为这很可能已经存在差距。
以非 root 身份运行的容器: 检查您的 Dockerfiles 中是否有 USER 指令。如果不存在,容器将以 root 身份运行。
端口绑定仅限于本地主机或代理: 运行 docker ps 并检查端口绑定。 0.0.0.0:PORT 条目可以在没有上游安全组、外部防火墙或 DOCKER-USER 链规则阻止的主机上公开访问。
使用中的自定义桥接网络: Docker 默认桥上的容器可以自由地相互访问。同一用户定义网桥上的容器仍然可以相互通信,因此可以通过信任边界将服务拆分到不同的网络上,以实现实际隔离。
Docker 套接字未安装在容器中: 检查 Compose 文件并运行参数。如果 /var/run/docker.sock 显示为卷,请确认它是必需的且有意为之。
来自已验证发布商的基础映像(具有固定版本): FROM ubuntu:latest 提取未指定的、可能过时的版本。固定到特定版本。
Dockerfiles、Compose 文件或构建参数中没有秘密: 删除容器后,映像层历史记录会保留凭据。使用 Compose 秘密、Swarm 秘密、构建秘密坐骑或外部秘密管理器。运行时环境变量比硬编码值更好,但仍然出现在检查输出和日志中。
定义的图像重建时间表: 旧图像会积累漏洞。每月的重建节奏使大多数设置的曝光窗口保持可控。
身份验证背后的管理接口: 公共 IP 上未经身份验证的任何仪表板都是开放入口点。如果可能的话,最好选择专用网络。
正在收集的容器日志: 如果没有日志管道,事件检测取决于可见的系统影响。这是一个迟到的行动信号。
结论
Docker 的默认配置是为了方便而不是安全而构建的。本文涵盖的大多数错误都可以追溯到初始部署后从未更改的设置,而不是复杂的攻击。
这些修复大多是一次性配置决策:用户指令、端口绑定更改、自定义网络、重建计划。大多数设置都不需要新的工具。
正确配置容器是首要任务。它运行的基础设施是第二个。两者都很重要,而且都不能替代对方。