收到漏洞扫描结果时,我最初关注的是“应该升到哪个版本”。沿着组件关系、软件包来源和官方公告继续查,才发现升级之前还有一个更基础的问题:报告发现的组件,和我正在检查的组件,是否是同一个对象?
这篇文章围绕两条告警,整理一次测试环境中的排查与升级记录:
- CVE-2021-30465:runc 挂载路径处理中的竞争条件问题。
- CVE-2025-15467:OpenSSL 解析 CMS 消息时的栈缓冲区溢出问题。
重点是记录判断过程,以及这次排查帮助我理解的几个概念。
本文记录 Ubuntu 测试环境中的版本变化与核验过程,版本组合仅用于说明当时的测试,不作为当前安装推荐。官方资料核对日期为 2026 年 9 月 15 日;该日期不是实际整改日期。
一、先确定排查对象
同一个组件名称,可能出现在不同位置:宿主机软件包、容器镜像、应用自带依赖,甚至是不再使用的旧文件。扫描报告里的一个版本号,不能替代对这些位置的确认。
本次记录中的测试基线为 Ubuntu 22.04 LTS,测试机没有已有容器。保留的版本变化如下:
| 组件 | 测试前 | 测试后 |
|---|---|---|
| Docker Engine / CLI | 24.0.6 | 28.5.2 |
| containerd | 1.6.24 | 1.7.29 |
| runc | 1.1.9 | 1.3.3 |
| openssl / libssl3 | 3.0.2-0ubuntu1.18 | 3.0.2-0ubuntu1.29 |
以下检查命令在待核查的 Ubuntu 宿主机执行,用于记录系统、包版本和组件来源:
cat /etc/os-releasedocker versioncontainerd --versionrunc --versiondpkg -S /usr/bin/runcdpkg-query -W -f='${Package}\t${Version}\n' openssl libssl3apt-cache policy docker-ce docker-ce-cli containerd.io openssl libssl3其中 docker version 要同时看客户端与服务端信息;如果 Docker CLI 连接的是远端 daemon,本机的 runc --version 就不能用来说明远端运行时的版本。多套运行时并存时,还需核对 daemon 配置与实际使用的二进制路径。
我的排查顺序因此变成了:先定位扫描对象,再确认组件来源,最后比较修复版本。
二、runc 告警:先理清谁提供了运行时
Docker、containerd 与 runc 的关系
在本文涉及的常见 Linux Docker 配置中,可以把调用关系简化为:
Docker:提供容器管理接口 ↓containerd:管理镜像与容器生命周期 ↓runc:按 OCI 规范创建和启动容器进程 ↓Linux namespace / cgroup / mount 等内核机制 ↓容器进程这是一张帮助理解职责的简图,省略了 shim 等中间环节。
本次执行 dpkg -S /usr/bin/runc 得到:
containerd.io: /usr/bin/runc这说明该路径由 containerd.io 软件包管理。对于这套安装方式,应沿着 Docker CE 软件包体系检查和更新运行时,避免另装发行版 runc 包或手工覆盖二进制,造成包管理记录与实际文件不一致。Docker 官方也说明,containerd.io 打包提供 containerd 和 runc,并列出了与发行版软件包的冲突关系。Docker 的 Ubuntu 安装说明
这不意味着每次修复 runc 都必须同时升级全部 Docker 组件;具体更新范围要看修复所在的软件包和兼容要求。本次选择了配套升级验证。
CVE-2021-30465 是否影响原版本
这个漏洞涉及 TOCTOU(检查时与使用时不一致):挂载目标通过检查后,路径可能在实际使用前被符号链接交换改变,导致挂载越出预期的容器根文件系统边界。利用还依赖特定的容器配置与共享卷条件。
runc 官方公告列出的受影响范围为 1.0.0-rc94 及以前,修复版本为 1.0.0-rc95。runc 安全公告
据此,记录中的 runc 1.1.9 不在该公告列出的受影响范围内。这里的判断前提是:扫描对象确实使用这份正常来源的 runc,且版本与文件核验一致。
告警仍存在时,可以继续检查扫描路径、运行时配置、旧文件残留及扫描规则,不能仅凭终端输出就宣布扫描器误报。针对一个 CVE 不受影响,也不能推导出这个版本没有其他漏洞。
三、运行时升级:把安装检查与运行验证分开
为什么采用分阶段测试
当时软件源中可见的候选还包括 Docker 29.x 与 containerd 2.x。考虑到跨主要版本会扩大排查范围,本次先验证 Docker 28.5.2 与 containerd 1.7.29,再考虑后续迁移。
这个选择反映的是当时的测试拆分方式,不代表中间版本天然安全,也不保证可以简单降级回滚。真正的变更方案仍需核对目标版本公告、业务兼容性以及数据和配置的恢复方式。
先看候选包,再模拟安装
测试机最初没有可用的 Docker CE 软件源配置,因此 apt-cache policy docker-ce 只能显示已安装版本。补齐软件源后,才看到了升级候选。
重新操作时,应按 Docker 官方安装说明 配置适合当前发行版的软件源,并查看实际可用版本:
# 在准备升级的测试宿主机执行,只查询候选版本apt-cache policy docker-ce docker-ce-cli containerd.ioapt-cache madison docker-ce docker-ce-cli containerd.io下面保留的是当时的安装模拟命令,其中 -s 表示模拟,不会实施安装:
apt-get -s install \ docker-ce=5:28.5.2-1~ubuntu.22.04~jammy \ docker-ce-cli=5:28.5.2-1~ubuntu.22.04~jammy \ containerd.io=1.7.29-1~ubuntu.22.04~jammy当时的输出摘要为:
3 upgraded0 newly installed0 to remove模拟没有报告依赖冲突或软件包移除,随后在测试机完成了安装。这里能得出的结论是“包管理器能够解析这组安装请求”,业务能否正常运行需要单独验证。
镜像拉取失败,先分辨失败发生在哪一层
升级后,Docker 与 containerd 的服务状态均为 active,但运行 hello-world 时出现镜像仓库请求超时:
Get "https://registry-1.docker.io/v2/": context deadline exceeded在同一测试宿主机执行独立请求,也出现连接超时:
curl -v --connect-timeout 10 https://registry-1.docker.io/v2/这支持先排查仓库访问链路,包括 DNS、代理、防火墙与出口连通性。它还不足以证明 Docker 配置完全正常,但说明本次失败停留在镜像获取阶段,尚未验证容器创建和进程执行。
用本地镜像补上最小运行验证
测试机已有 busybox-static。我用它构造最小根文件系统(rootfs),再通过 docker import 导入本地镜像,随后执行:
# 在已导入该本地测试镜像的宿主机执行docker run --rm local-busybox:test /bin/echo "runc test ok"实际输出:
runc test ok这条命令依赖已导入的 local-busybox:test 镜像;镜像中需要包含可执行的 BusyBox,并提供 /bin/echo 入口。它用于验证已有本地镜像的运行结果,不能单独完成镜像构建。
相比只查询版本,这一步确认了当时配置下的容器创建与命令执行路径可用。不过,空测试机上的最小容器没有覆盖业务镜像、网络、数据卷、权限、重启恢复及持续运行表现。
四、OpenSSL:为什么打了补丁还是 3.0.2
上游版本与 Ubuntu 包版本是两套判断依据
CVE-2025-15467 涉及 OpenSSL 解析带有恶意 AEAD 参数的 CMS AuthEnvelopedData 或 EnvelopedData 消息,可能造成栈缓冲区溢出,导致崩溃或潜在代码执行。风险与应用是否处理相应不可信内容有关,不能把“安装了 OpenSSL”直接等同于“所有 TLS 服务都能通过这个漏洞被利用”。OpenSSL 安全公告
上游公告为 3.0 分支给出的修复版本是 3.0.19;其他维护分支有各自的修复版本。Ubuntu 22.04 则沿用 3.0.2 基础版本,通过 backport(补丁回移) 将修复应用到发行版软件包。
因此,升级后 openssl version 仍显示 OpenSSL 3.0.2,不能据此判断补丁未安装。需要查看完整包版本和发行版公告。
Ubuntu 对 Jammy 的 openssl 源软件包列出的该漏洞修复版本为 3.0.2-0ubuntu1.21。Ubuntu CVE-2025-15467 状态页
本次记录中的安装版本从 3.0.2-0ubuntu1.18 更新到 3.0.2-0ubuntu1.29。在确认其来自对应 Ubuntu 官方更新源的前提下,它已达到这个 CVE 的修复基线。
用软件包工具比较完整版本
下面的命令在安装了 Ubuntu Jammy 官方软件包的宿主机执行,分别检查包版本、候选来源与版本比较结果:
dpkg-query -W -f='${Package}\t${Version}\n' openssl libssl3apt-cache policy openssl libssl3
# 只演示 Debian/Ubuntu 包版本比较;退出码 0 表示满足 ge(大于等于)dpkg --compare-versions '3.0.2-0ubuntu1.29' ge '3.0.2-0ubuntu1.21'echo $?这个比较应放在同一发行版、同一软件包维护来源下理解。不要只比较末尾数字,也不要拿上游 3.0.19 与 Ubuntu 的 3.0.2-0ubuntu1.29 做普通字符串大小判断。
为什么同时检查 openssl 和 libssl3
发行版安全页面通常按源软件包追踪,而安装到系统里的往往是它构建出的多个二进制软件包:
| 名称 | 在 Ubuntu 22.04 中的主要作用 |
|---|---|
openssl 源软件包 | 提供构建所用的源码与发行版补丁 |
openssl 二进制包 | 提供 OpenSSL 命令行工具 |
libssl3 二进制包 | 提供应用加载的 libssl.so.3、libcrypto.so.3 等共享库 |
libssl-dev 二进制包 | 提供编译所需的头文件与开发链接文件 |
只检查命令行工具,可能遗漏应用实际使用的库。即使磁盘上的包已更新,长期运行的进程也可能仍加载旧库;需要按维护计划重启相关服务或系统,再检查应用状态。这次测试仅核验了软件包版本,尚不能确认所有受影响进程都已重新加载新库。对应的 Ubuntu 安全更新通知 也要求在更新后重启系统。
针对这次系统包修复,没有必要为了匹配扫描报告里的上游版本号而手工覆盖系统 OpenSSL;应先核对发行版提供的更新。应用自带或静态链接的 OpenSSL 则需要沿应用自身的构建与发布渠道处理。
五、几个容易混淆的边界
同一个 CVE 下,不同软件包的状态可以不同
Ubuntu CVE 页可能同时列出 openssl、nodejs、edk2 等源软件包,需要逐行查看软件包与发行版的组合。它们可能通过直接提供、嵌入或携带相关代码而被纳入追踪。
| 状态 | 阅读时应理解为 |
|---|---|
| Fixed | 对应的软件包已有修复版本,还要核对本机是否安装 |
| Vulnerable | 对应的软件包仍标记为受影响 |
| Not affected | 对应的软件包在该发行版下不受此漏洞影响 |
| Not in release | 该发行版没有发布这个软件包 |
| Needs evaluation | 仍待评估,不能当作不受影响 |
例如,openssl1.0 在 Jammy 下显示 Not in release,不能解释成“系统上的所有 OpenSSL 都安全”。官方仓库没有某个包,也不能排除本机手工安装了相关代码。Ubuntu CVE 状态页
宿主机修复不会自动更新镜像里的库
宿主机执行 openssl version 与容器中执行同一命令,通常检查的是两份不同文件。容器里的软件包还需要按镜像自身的发行版及安装方式核验。
# 在 Docker 宿主机执行,将 CONTAINER_NAME 换成目标容器名# 仅适用于镜像内有 openssl 命令的容器docker exec CONTAINER_NAME openssl version -a这里也只是初步定位,不能替代容器内完整包版本与依赖来源检查。镜像中没有 openssl 命令,同样不能证明应用没有携带 OpenSSL 库。镜像整改应进入基础镜像更新、重新构建、重新部署和扫描复核流程。
定向更新与系统升级应分别评估
当时执行 apt-get -s dist-upgrade 后,看到的模拟计划涉及内核、系统库和其他组件,还可能改变运行时主要版本,因此没有把它作为只更新 OpenSSL 的步骤。
dist-upgrade 可以安装或移除软件包以解决依赖变化;它本身不等于把 Ubuntu 22.04 切换到 24.04。APT 命令手册 说明了这类依赖处理行为。这次两个 CVE 的修复判断也不要求仅为它们进行发行版跨版本升级。
我的理解是:定向整改需要控制变更范围,系统性安全更新也要持续安排,两者都需要各自的验证计划。
六、这次记录能证明什么
| 已有证据 | 可以支持的结论 | 尚未覆盖的范围 |
|---|---|---|
| runc 版本记录与上游公告 | 记录中的 1.1.9 不在 CVE-2021-30465 公告受影响范围内 | 扫描对象与实际文件的一致性、其他 CVE |
Docker 与 containerd 为 active | 服务已启动 | 业务功能与稳定性 |
本地镜像输出 runc test ok | 最小容器创建与命令执行成功 | 网络、数据卷、业务镜像与恢复能力 |
openssl / libssl3 完整包版本为 .29 | 对应官方 Jammy 包达到 CVE-2025-15467 修复基线 | 旧进程、自带依赖、静态链接与镜像内部组件 |
这次能够确认的是测试环境升级与补丁版本核验。业务回归、受影响进程重新加载和扫描复核的结果仍待确认,因此还不能据此认定整个整改已经完成。
七、以后遇到类似告警,我会这样查
- 明确对象:记录 CVE、扫描路径,以及它属于宿主机、镜像还是应用依赖。
- 确认来源:核对发行版、完整包版本、安装渠道与实际使用的二进制。
- 查官方依据:上游公告用于理解问题,发行版公告用于判断对应软件包的修复状态。
- 确定变更范围:把必须修复的包与其他基线升级分清,先做依赖模拟和测试验证。
- 验证补丁生效:同时检查安装版本、进程重新加载和业务行为。
- 回到原告警复核:保留验证证据,明确完成项与尚未覆盖的对象。
这次最大的收获,是把“版本看起来够新”换成更具体的判断:哪个对象、哪份代码、由谁维护、哪个包修复,以及验证覆盖到了哪里。 这些问题问清楚后,升级决策和整改结论都会更有依据。