最后更新:2026 年 10 月 23 日
Halo 已关注此次 Java 证书信任安全问题。对于官方容器镜像,目前正在等待上游 Eclipse Temurin 基础镜像完成修复更新;待上游更新可用并完成验证后,我们将提供相应的 Halo 镜像更新指引。在此期间,容器部署用户可按照下述方案先行撤销相关证书信任。通过 java -jar 直接部署的用户,可自行更新实际使用的 Java 运行环境,无需等待 Halo 容器镜像更新。
影响范围
此次问题涉及 eMudhra 中间 CA 私钥失陷。仍信任相关证书链的 Java 运行环境,可能接受攻击者伪造的服务端证书。
对 Halo 部署而言,典型风险发生在 Halo 或插件作为客户端连接 HTTPS 等 TLS 服务时:攻击者还需要让连接到达其控制的端点,例如通过劫持 DNS、代理或网络路径。访问固定的正规 API 地址,也不能完全排除此类风险。
安装 Halo 或对外提供网站访问,并不意味着已经遭到攻击。该公告也不表示攻击者仅发送一个普通网页请求就能在 Halo 中执行任意代码。 实际风险取决于运行环境的证书信任配置、网络条件和所用功能。
普通网站访客无需在浏览器中执行本方案;以下操作由 Halo 部署维护人员在服务器上完成。使用第三方镜像、自定义 Java 信任库或插件自带证书库的部署,需要检查对应组件,不能仅按 Halo 版本号判断是否受影响。
通过 java -jar 直接部署
使用默认信任库的部署,通常升级到 Java 发行商明确包含此次修复的 JRE/JDK,并使用新运行时重启 Halo,即可完成该运行环境的修复。 Oracle 公告将更新 Java 列为解决方式;不同发行商的修复版本和构建号可能不同,请以所使用发行商的安全公告为准,不要仅凭“已安装最新版”或 Java 主版本号判断。
操作时请注意:
- 更新 Halo 实际使用的运行时。 优先选择当前 Halo 支持的 Java 主版本中的修复更新,例如继续使用受支持的 Java 21 更新构建,无需仅为此次问题跨 Java 主版本升级。JRE 或 JDK 均可,取决于原有部署方式;若使用系统软件包提供的 Java,应同时确认其依赖的证书包已经按发行版公告更新。
- 检查启动路径并重启 Halo。 启动脚本、systemd 服务或进程管理工具可能固定了旧的
java路径。安装新运行时后,应调整实际启动配置并重启 Halo;仅修改终端的JAVA_HOME或执行一次java -version,不能证明运行中的 Halo 已切换。 - 单独检查自定义信任库。 如果配置了
javax.net.ssl.trustStore、jssecacerts或插件独立信任库,升级 JRE/JDK 未必会更新这些文件,需要同步撤销相关证书信任。不要把旧的整份cacerts直接覆盖到新运行时中。 - 确认更新生效并检查业务连接。 确认新启动的 Halo 进程使用的是修复后的运行时及预期信任库,再检查页面和所用外部服务连接。
如果当前 Java 发行商尚未提供修复构建,或暂时无法升级,也可先备份 Halo 实际使用的信任库,按下文所述原理移除两个目标根证书,再重启 Halo。应使用对应 Java 安装目录下的 keytool,并以实际证书指纹和别名为准;下文的 docker 命令仅用于容器部署,不能直接用于 JAR 部署。
本节为依据官方公告给出的运行环境升级指引;本文记录的实测对象为下述容器镜像,未对各发行商的 JRE/JDK 升级过程逐一验证。
官方容器镜像:临时方案及适用条件
按照 Oracle 官方缓解建议,从应用实际使用的信任库中移除以下两个根证书,阻断受影响证书链:
- emSign ECC Root CA - G3
- AAA Certificate Services
本方案适用于使用默认 Java 信任库、默认信任库密码 changeit 的 Halo 官方镜像。已在官方 halohub/halo:2.27 的 Linux arm64 镜像、Temurin 21.0.12.1+1-LTS 上完成处理前后验证。其他镜像构建可能采用不同的别名,因此下面的脚本按证书 SHA-256 指纹识别目标,不依赖 Oracle 示例中的短别名。
如果自行配置了 javax.net.ssl.trustStore、自定义 TLS TrustManager,或插件使用独立信任库,请由维护人员检查并处理实际使用的信任库。下面的脚本只处理默认 cacerts;如发现优先级更高的 jssecacerts,会停止。修改过信任库密码或使用只读容器的部署,也不适用以下直接执行步骤。
官方容器镜像:操作步骤
以下命令均在运行 Halo 的 Docker 宿主机终端执行。重启会造成短暂服务中断,请选择合适的维护时间。
1. 确认容器并备份
先查找容器名称:
docker ps --format 'table {{.Names}} {{.Image}}'
将下面的 halo 替换为实际容器名称,在同一个终端中执行:
HALO_CONTAINER=halo
HALO_BACKUP="./halo-cacerts-before-emudhra-$(date +%Y%m%d-%H%M%S)"
docker cp "${HALO_CONTAINER}:/opt/java/openjdk/lib/security/cacerts" "$HALO_BACKUP"
确认备份成功后再继续。 若路径不存在或命令失败,应先核实镜像与信任库位置。该备份保存在宿主机,请妥善保留。
2. 移除目标根证书并检查结果
复制执行下面的完整命令。脚本会核对两个目标根证书的指纹,只删除实际存在的目标条目,并检查处理结果。证书指纹来自事件披露方提供的根证书清单。
docker exec -i -u 0 "$HALO_CONTAINER" sh <<'SH'
#!/bin/sh
# Run inside a Halo container as root. Only for the default Java truststore.
set -eu
truststore="${JAVA_HOME:?JAVA_HOME is not set}/lib/security/cacerts"
if [ -e "$JAVA_HOME/lib/security/jssecacerts" ]; then
echo 'STOP: jssecacerts exists; inspect the actual application truststore first.' >&2
exit 1
fi
workdir=$(mktemp -d /tmp/halo-emudhra.XXXXXX)
keytool -J-Duser.language=en -J-Duser.country=US -list -v -cacerts \
-storepass changeit > "$workdir/before.txt"
find_targets() {
awk '
/^Alias name: / { alias = substr($0, 13) }
/SHA256:/ {
fingerprint = $0
sub(/^.*SHA256: */, "", fingerprint)
gsub(/:/, "", fingerprint)
gsub(/[[:space:]]/, "", fingerprint)
if (fingerprint == "86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B" ||
fingerprint == "D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4")
print alias
}
' "$1"
}
find_targets "$workdir/before.txt" > "$workdir/aliases.txt"
if [ ! -s "$workdir/aliases.txt" ]; then
echo 'NO_CHANGE: neither target root certificate is present in the default cacerts.'
exit 0
fi
cp -p "$truststore" "$workdir/cacerts.before"
echo "Backup: $workdir/cacerts.before"
while IFS= read -r alias; do
echo "Removing: $alias"
keytool -delete -cacerts -storepass changeit -alias "$alias"
done < "$workdir/aliases.txt"
keytool -J-Duser.language=en -J-Duser.country=US -list -v -cacerts \
-storepass changeit > "$workdir/after.txt"
find_targets "$workdir/after.txt" > "$workdir/remaining.txt"
if [ -s "$workdir/remaining.txt" ]; then
echo 'FAILED: target root certificates remain.' >&2
exit 1
fi
echo 'VERIFIED: both target root certificates are absent from the default cacerts.'
echo 'Restart the Halo container, then check application connectivity.'
SH
- 输出
VERIFIED:两个目标根证书已从默认信任库中移除,继续重启。 - 输出
NO_CHANGE:默认信任库中没有这两个目标根证书,本次没有修改。它不代表已检查所有自定义信任库或插件;如刚完成过移除,仍需确保已经重启。 - 输出
STOP、FAILED或其他错误:不要当作成功,应保存输出并联系维护人员。若中途已有条目被移除,修复操作可能尚未全部完成。
3. 重启并检查 Halo
处理成功后执行:
docker restart "$HALO_CONTAINER"
docker logs --tail 100 "$HALO_CONTAINER"
确认 Halo 正常启动,登录页面可访问,并检查正在使用的第三方 API、对象存储、邮件及其他外部服务连接。需要复查默认信任库时,可重新执行上一步脚本;预期输出 NO_CHANGE。
不要用浏览器或容器内 curl 的验证结果代替 Java 信任库检查,它们可能使用不同的证书库。
处理后的影响与后续更新
- 这是一项临时缓解措施。 修改保存在当前容器的可写层中,同一容器重启后仍保留;删除并重建容器、重新部署或更新镜像后,可能丢失。修复镜像发布前若重新部署,应重新检查并在需要时处理。
- 移除根证书后,某些仍依赖这两个根建立信任的正常外部服务也可能出现证书验证失败。应联系对应服务提供方更新证书链,或由维护人员评估其他经验证的缓解方案;不要通过关闭 TLS 证书校验解决连接问题。
- 本操作只更新 Java 默认信任库,不修改 Halo 文章、附件或数据库。仍应按原有流程维护业务备份。
- Halo 正在等待上游基础镜像修复更新。上游跟进 PR在本说明验证时仍未合并;合并后还需要镜像构建、Halo 镜像更新与验证。请以 Halo 后续发布的镜像更新说明为准,完成更新后重新部署。
已完成的验证
在上述固定的 Halo 2.27 arm64 镜像上,使用 JDK HTTPS 客户端和镜像自带的 Reactor Netty 分别测试:
| 检查项 | 处理前 | 处理并重启后 |
|---|---|---|
| 经 emSign G3 根建立信任的两条公开测试连接 | 两种客户端均接受 | 两种客户端均因证书链校验失败而拒绝 |
| 经 AAA 根建立信任的两条公开测试连接 | 两种客户端均拒绝,该镜像原本没有此根 | 继续拒绝 |
| Halo 官网、Oracle 官网的正常证书连接 | TLS 校验通过 | TLS 校验通过 |
| Halo 启动及登录页访问 | 正常 | 正常 |
信任库从 118 项变为 117 项,仅移除了目标 emSign G3 根,其余证书指纹保持不变。重复执行脚本没有继续修改。
测试覆盖同一容器中的 Java 客户端和 Halo 启动检查,未覆盖所有历史镜像、架构和第三方插件配置;请结合自身部署检查业务连接。