Ubuntu服务器上跑的容器镜像一旦带病上线,后续修复成本往往是构建阶段的数倍。很多团队习惯在镜像推送到仓库之后才做扫描,结果漏洞报告堆积在CI/CD流水线末端,开发、运维和安全三方互相推诿。更现实的做法是把漏洞扫描前置到镜像构建完成的那一刻,并且针对Ubuntu基础镜像的版本特性做持续监控,因为Ubuntu的CVE数据更新节奏和Debian、Alpine并不完全一致,同一个软件包在不同发行版里的修复版本号也经常对不上。
先确认基础镜像的Ubuntu版本和内核对应关系容器镜像里的Ubuntu版本决定了你后续要跟踪哪一套CVE数据源。Ubuntu 20.04 LTS、22.04 LTS、24.04 LTS的软件包版本差异很大,而且同一CVE在不同版本上的严重级别可能不同。例如某个内核漏洞在22.04上需要紧急修复,在20.04上可能因为默认内核配置不同而只是低危。扫描之前先用以下命令确认镜像的发行版信息:
docker run --rm your-image:tag cat /etc/os-release
输出结果里的VERSION_ID就是后续配置扫描策略的关键依据。不要只看镜像标签里的“latest”或者“22.04”字样,很多第三方镜像会混用基础镜像层,实际系统版本可能和标签不一致。建议在CI流水线里固定使用带具体版本号的Ubuntu基础镜像,比如ubuntu:22.04而不是ubuntu:latest,这样漏洞数据库的匹配才有稳定基准。
选择适合Ubuntu容器场景的扫描工具Trivy是目前对Ubuntu容器支持比较完整的开源扫描器,它直接解析dpkg数据库,能够准确识别Ubuntu软件包的版本和对应的CVE。安装方式很简单,在构建服务器上执行:
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
扫描单个本地镜像:
trivy image --severity HIGH,CRITICAL your-image:tag
如果镜像已经推送到私有仓库,也可以直接扫描仓库地址。Trivy对Ubuntu的CVE数据源来自Ubuntu Security Notices和NVD,但要注意它默认只显示有修复版本的漏洞。对于Ubuntu的ESM(扩展安全维护)软件包,如果服务器没有订阅Ubuntu Pro,扫描结果里可能会漏掉那些只有ESM渠道才能修复的漏洞。这种情况下需要结合ubuntu-security-status命令来判断哪些包已经不在标准安全维护范围内。
Grype是另一个选择,它由Anchore维护,对Ubuntu的识别同样基于dpkg数据库。Grype的优势在于输出格式更丰富,可以直接生成SARIF报告接入GitHub Security页面。但Grype对Ubuntu特定CVE的覆盖偶尔会滞后于Trivy,因为它的数据聚合链路更长。实际生产环境里建议两个工具并行跑,交叉验证结果,避免单一数据源误报或漏报。
构建阶段就把扫描卡在推送之前最有效的做法是在Dockerfile构建完成后、docker push之前执行扫描。很多团队用GitLab CI或Jenkins,可以在构建任务里加入扫描步骤。以GitLab CI为例:
scan_image:
stage: test
image: aquasec/trivy:latest
script:
- trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
allow_failure: false
这里的关键参数是--exit-code 1和--ignore-unfixed。--exit-code 1让扫描到高危漏洞时直接让流水线失败,阻止镜像推送到生产仓库。--ignore-unfixed表示忽略那些还没有官方修复版本的漏洞,避免因为暂时无解的CVE卡死整个发布流程。但要注意,忽略未修复漏洞不代表不关注,应该把这些结果单独导出到安全团队的跟踪系统里。
对于Ubuntu基础镜像,还有一个容易被忽略的点:Dockerfile里apt-get install的软件包如果来自第三方PPA或者手动编译安装,Trivy和Grype都扫不到。这类软件包的漏洞需要单独维护清单,建议在Dockerfile里固定版本号,并定期用脚本检查上游发布页面的安全公告。
处理Ubuntu特有漏洞数据源和误报问题Ubuntu的CVE数据有一个特殊之处:很多漏洞在Ubuntu Security Notices里标记为“needed”或者“pending”,而不是直接给出修复版本。这是因为Ubuntu安全团队对不同软件包采取不同的修复优先级。扫描工具如果只参考NVD数据,可能会把某个在Ubuntu上已经修复的漏洞仍然报为高危,或者反过来漏掉Ubuntu已经确认但NVD尚未收录的漏洞。
减少误报的一个实用方法是定期更新扫描器的漏洞数据库。Trivy的数据库默认缓存24小时,但在Ubuntu发布重要安全更新时,建议手动触发更新:
trivy image --download-db-only
另一个常见误报来源是基础镜像里的dpkg状态文件不完整。有些精简过的Ubuntu镜像会删除/var/lib/dpkg/status里的部分条目,导致扫描器无法准确判断软件包版本。如果发现扫描结果里出现大量“package not found”或者版本号显示为空,先检查镜像里的dpkg数据库是否完整:
docker run --rm your-image:tag dpkg -l | wc -l
如果输出行数异常少,说明这个镜像被过度精简,建议换用官方ubuntu基础镜像或者自己从官方镜像重新构建。
针对运行中的Ubuntu容器做持续漏洞监控镜像扫描只能覆盖构建时刻的已知漏洞,运行中的容器可能因为新的CVE披露而变成高危。对于Ubuntu服务器上的容器,建议部署轻量级的持续扫描方案。可以在宿主机上定期对所有运行中的容器执行Trivy扫描:
docker ps -q | xargs -I {} docker inspect --format='{{.Image}}' {} | sort -u | xargs -I {} trivy image --severity HIGH,CRITICAL {}
这个脚本可以放进cron任务里每天执行一次,结果输出到日志文件或者推送到内部告警系统。但要注意,运行中容器的文件系统可能已经发生变化,扫描镜像本身只能反映构建时的状态。如果容器在运行时通过apt-get安装了新软件包,这些变更不会体现在镜像扫描结果里。对于这种场景,需要进入容器内部扫描文件系统:
trivy fs --severity HIGH,CRITICAL /var/lib/docker/overlay2/容器ID/merged
不过直接扫描overlay2目录需要root权限,而且路径在不同Docker版本下可能不同。更稳妥的方式是在容器启动时挂载只读的扫描探针,或者使用专门的运行时安全工具来监控容器内的软件包变更。
Ubuntu Pro和ESM对容器漏洞修复的影响很多Ubuntu服务器容器的基础镜像默认不启用Ubuntu Pro,这意味着超过标准支持周期的软件包不再接收安全更新。例如Ubuntu 20.04的某些universe仓库软件包,标准支持只到2025年,之后就需要ESM订阅才能获得补丁。扫描工具如果报告这些软件包有漏洞,但你在标准apt源里找不到修复版本,很可能就是因为修复版本只在ESM渠道发布。
检查容器是否启用ESM:
docker run --rm your-image:tag pro status
如果输出显示ESM未启用,而你的生产环境又依赖这些软件包,有两个选择:一是升级基础镜像到更新的Ubuntu LTS版本,二是为服务器购买Ubuntu Pro订阅并在容器构建时注入ESM源。对于大多数业务场景,升级基础镜像是更经济的选择,因为Ubuntu Pro的授权成本按物理机或虚拟机计算,容器数量多的时候成本会快速上升。
把漏洞扫描结果接入修复流程扫描本身不产生价值,修复闭环才有意义。对于Ubuntu容器镜像的漏洞,修复优先级应该按照“可利用性”和“业务暴露面”来排序,而不是简单按CVSS分数从高到低。一个CVSS 9.8的漏洞如果只存在于容器内不对外暴露的内部工具里,实际风险可能低于一个CVSS 7.5但直接暴露在公网的Web服务漏洞。
建议在扫描结果里提取出可修复的漏洞清单,自动生成Dockerfile的软件包升级建议。例如Trivy输出JSON格式后,用脚本解析出有修复版本的软件包,然后生成apt-get upgrade命令:
trivy image --format json --output result.json your-image:tag
解析result.json里的Vulnerabilities数组,提取PkgName和FixedVersion字段,生成类似下面的升级命令:
apt-get update && apt-get install -y --only-upgrade libssl3=3.0.2-0ubuntu1.10
但要注意,直接升级基础镜像里的软件包可能破坏依赖关系。Ubuntu的apt包管理器在升级单个软件包时会自动处理依赖,但在容器构建场景下,更推荐的做法是直接升级基础镜像版本。比如从ubuntu:22.04的某个旧tag升级到最新的22.04 tag,然后重新构建应用层。这样能一次性获得所有已修复的安全更新,同时保持依赖关系的完整性。
构建一个最小化的Ubuntu安全基线镜像与其每次扫描后修补,不如从源头构建一个安全基线镜像。这个基线镜像应该满足几个条件:基于官方Ubuntu LTS镜像、只安装业务必需软件包、移除不必要的setuid程序、配置好apt安全源、固定所有软件包版本。下面是一个示例Dockerfile:
FROM ubuntu:22.04
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y --no-install-recommends ca-certificates tzdata && \
rm -rf /var/lib/apt/lists/*
RUN find / -perm /6000 -type f -exec chmod a-s {} \; 2>/dev/null || true
USER 10001
这个基线镜像构建完成后先扫描一次,确保零高危漏洞,然后推送到内部私有仓库作为所有业务镜像的基础。后续业务镜像的Dockerfile都从这个基线镜像开始构建,漏洞扫描的压力会小很多。当Ubuntu发布新的安全更新时,只需要重新构建基线镜像,然后触发下游业务镜像的自动重建。
对于已经在生产环境运行的Ubuntu容器,建议建立漏洞响应SLA。高危漏洞在官方修复版本发布后24小时内完成基础镜像升级和业务镜像重建,中危漏洞在一周内处理。这个SLA需要和CI/CD流水线的自动化能力匹配,否则就是空谈。很多团队卡在“知道有漏洞但没时间修”的状态,根本原因是构建和发布流程太慢。把镜像构建时间控制在5分钟以内,把发布审批改成自动化门禁,漏洞修复才能跟上节奏。
最后提醒一点,Ubuntu容器镜像的漏洞扫描不是一次性任务。CVE数据库每天都在更新,基础镜像的tag也在不断变化。建议把扫描任务固化到每日定时流水线里,对仓库中所有活跃镜像做全量扫描,并把结果推送到安全团队的看板。同时监控Ubuntu Security Notices的RSS源,当出现影响你所用软件包的新公告时,立即触发对应镜像的重新扫描和修复流程。只有把扫描、告警、修复、验证串成自动化闭环,Ubuntu服务器上的容器镜像安全才算真正落地。
