Ohhnews

分类导航

$ cd ..
Jetbrains Blog原文

eMudhra证书颁发机构私钥泄露事件对TeamCity的影响及应对

#emudhra#证书颁发机构#teamcity#网络安全#证书信任

摘要

2026 年 10 月,由 eMudhra 运营的八个中间证书颁发机构的私钥被发现存在弱点,并且可从公开数据中恢复。根据 eMudhra 的官方声明,受影响的中间 CA 均未签发过终端实体(叶子)证书,并且 eMudhra 未发现证据表明这些弱密钥在该问题披露之前已被恢复或利用。

该问题不是 TeamCity 本身的漏洞。 但是,TeamCity 所依赖的其他软件组件(主要是 Java 运行时)维护的信任库可能仍会信任这些受泄露影响的证书。从版本 2026.2.2 和 2026.1.6 开始,如果安装仍信任受影响的证书,TeamCity 会显示健康报告以及修复步骤。

建议采取的措施:

  • TeamCity On-Premises: 检查服务器和构建代理安装,并按照下面的说明应用必要的修复。升级到 v2026.2.2 或 v2026.1.6,以获得适用于 Windows 代理安装程序的更新版 Java 运行时、更新后的 Docker 镜像和健康报告;或者按照下面的说明,对当前安装应用相同的修复。
  • TeamCity Cloud: TeamCity Cloud 已完成更新。必须按下文上传到 TeamCity 的证书所述,检查上传到 TeamCity 的证书。自托管构建代理也必须按照下面的说明进行检查。

详情

2026 年 10 月 17 日,八个 eMudhra 中间 CA 的私钥被报告为已泄露,其中六个用于 TLS,两个用于代码签名。这是全行业范围的证书颁发机构事件,并非 TeamCity 特有的问题。使用这些密钥签发的证书,在所有仍信任它们的软件看来都会是有效的。

浏览器和操作系统供应商已发布更新,以阻止这些受泄露影响的证书;系统在安装这些更新后即可受到保护。拥有独立信任库的软件(例如 Java 运行时)会继续信任这些受泄露影响的证书,直到该信任库更新为止。TeamCity 的服务器和构建代理使用其 Java 运行时的信任库。

TeamCity 下载内容并非由 eMudhra CA 签名,因此不受影响。

你需要做什么

1. 更新 Java 运行时

  1. 确定每个服务器节点和代理所使用的 Java 运行时,包括通过 javax.net.ssl.trustStore 设置的任何自定义信任库。
  2. 查看 Java 供应商发行说明中关于 CVE-2026-91403 和 CVE-2026-114820 的信息。对于 Amazon Corretto 21,修复后的构建版本为 21.0.13.0.11 或更高版本。
  3. 更新受影响的运行时,并重启每个服务器节点或代理。

可选的信任库验证可识别信任库中仍存在的受影响证书。如果无法升级,请按照同一节中的手动缓解命令操作。

移除 AAA Certificate Services 可能会中断未受影响的服务;在移除该根证书之前,请测试依赖连接。

2. 更新 TeamCity 服务器并检查已上传的证书

以下服务器维护建议适用于 TeamCity On-Premises 安装。JetBrains 会修补 TeamCity Cloud,但上传到该服务的证书仍需要检查。

  1. 尽可能升级 TeamCity。 版本 2026.2.2 和 2026.1.6 增加了健康报告、其他产品改进以及其他漏洞的修复。现有代理需要单独检查。
  2. 在每个服务器节点上更新 Java。 即使升级了 TeamCity,也要遵循更新 Java 运行时。
  3. 重新部署 Docker 安装。 在发布后从 Docker Hub 拉取已修补的 2026.2.2 或 2026.1.6 服务器镜像,或使用服务器镜像说明重新构建现有镜像。重新创建容器。
  4. 检查已上传的证书。 使用健康报告或下面的证书检查命令,然后通过根项目的 SSL / HTTPS Certificates 页面移除受影响的证书。更新 Java 不会移除已上传的证书。

更新 TeamCity 服务器不会更新集成或第三方软件。请按照检查并更新配套基础设施中所述,单独检查这些组件。

3. 更新 TeamCity 代理并检查受信任的证书

  1. 在每个构建代理上更新 Java。 遵循更新 Java 运行时。TeamCity Cloud 客户还必须检查其自托管代理使用的 Java 运行时。健康报告不会检查代理。
  2. 更新 Docker 安装。 从 Docker Hub 拉取已修补的 2026.2.2 或 2026.1.6 代理镜像。或者,使用与服务器镜像相同的重新构建说明重新构建现有镜像。使用更新后的镜像重新创建容器。
  3. 检查代理证书。 从 <agent home>/conf/trustedCertificates 中运行技术命令下的证书检查命令,并移除任何受影响的证书文件。更新 Java 不会移除这些证书。

4. 检查并更新配套基础设施

更新服务器不会更新集成或第三方软件。我们建议单独检查这些组件:

  • 操作系统和构建工具: 对操作系统信任库、其他 Java 运行时、Git 和 MinGit、Python 的 certifi、Node.js 以及其他具有独立信任库的工具应用供应商更新。
  • 自定义镜像: 更新每个受影响镜像内的操作系统和工具,重新构建镜像,并重新创建容器。
  • 连接的服务: 通过 eMudhra 的迁移流程,替换 VCS 主机、包仓库和其他服务所使用的 eMudhra 证书。## 技术命令

每个脚本都在顶部定义其环境变量:

  • TEAMCITY_JRE:默认为 JAVA_HOME;如果 TeamCity 运行在其他 JRE 或 JDK 上,请更改它。
  • TC_TRUSTSTORE:设置为任意自定义的 javax.net.ssl.trustStore(JDK 8 默认值:jre/lib/security/cacerts)。
  • TC_STOREPASS:如果密码不同,请更改它。
  • TC_CERT_DIR:在指定位置要检查的证书文件。

可选的信任库验证

请注意,结果仅适用于所选的信任库。这些命令不会检查 blocked.certs。

Linux 和 macOS(Bash 或 zsh):

$ bash
(
export TEAMCITY_JRE="${TEAMCITY_JRE:-$JAVA_HOME}"
export TC_TRUSTSTORE="$TEAMCITY_JRE/lib/security/cacerts"
export TC_STOREPASS='changeit'

keytool="${TEAMCITY_JRE:?Set TEAMCITY_JRE or JAVA_HOME to the Java home directory of the runtime in use}/bin/keytool"
[ -x "$keytool" ] || { echo "ERROR: keytool not found at $keytool. TEAMCITY_JRE must be the Java home directory (the folder that contains bin/ and lib/)." >&2; exit 1; }
output=$("$keytool" -J-Duser.language=en -list -v \
  -keystore "$TC_TRUSTSTORE" -storepass:env TC_STOREPASS) ||
  { echo "ERROR: Certificate check failed." >&2; printf '%s\n' "$output" >&2; exit 1; }

awk '
  /^Alias name:/ { alias=$0 }
  /86:A1:EC:BA:08:9C:4A:8D|D7:A7:A0:FB:5D:7E:27:31|7D:D7:8D:5F:4F:13:45:9A|F2:ED:AA:FF:3D:9B:70:1F|40:F6:AF:03:46:A9:9A:A1|1A:A0:C2:70:9E:83:1B:D6|CN=EM (DV|OV|EV) (TLS|CS) CA - G3/ {
    found=1; print alias; print
  }
  END {
    print found ? "ACTION REQUIRED: this trust store still trusts compromised eMudhra certificates (listed above). Update the Java runtime or apply the manual mitigation." \
                : "OK: no compromised eMudhra certificates found in this trust store. No action is needed for it."
  }
' <<< "$output"
)

Windows(PowerShell):

$ powershell
& {
$ErrorActionPreference = 'Stop'
if (-not $env:TEAMCITY_JRE) { $env:TEAMCITY_JRE = $env:JAVA_HOME }
$env:TC_TRUSTSTORE = "$env:TEAMCITY_JRE\lib\security\cacerts"
$env:TC_STOREPASS = 'changeit'

if (-not $env:TEAMCITY_JRE) { throw 'Set TEAMCITY_JRE or JAVA_HOME to the Java home directory of the runtime in use.' }
$keytool = "$env:TEAMCITY_JRE\bin\keytool.exe"
if (-not [IO.File]::Exists($keytool)) { throw "keytool not found at $keytool. TEAMCITY_JRE must be the Java home directory (the folder that contains bin\ and lib\)." }
$prev = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
$output = & $keytool '-J-Duser.language=en' -list -v -keystore "$env:TC_TRUSTSTORE" -storepass:env TC_STOREPASS 2>&1
$ErrorActionPreference = $prev
if ($LASTEXITCODE -ne 0) { throw "ERROR: Certificate check failed. $output" }

$pattern = '86:A1:EC:BA:08:9C:4A:8D|D7:A7:A0:FB:5D:7E:27:31|7D:D7:8D:5F:4F:13:45:9A|F2:ED:AA:FF:3D:9B:70:1F|40:F6:AF:03:46:A9:9A:A1|1A:A0:C2:70:9E:83:1B:D6|CN=EM (DV|OV|EV) (TLS|CS) CA - G3'
$alias = ''
$found = $false
foreach ($line in $output) {
    if ($line -match '^Alias name:') { $alias = $line }
    if ($line -match $pattern) { $found = $true; $alias; $line }
}
if ($found) { 'ACTION REQUIRED: this trust store still trusts compromised eMudhra certificates (listed above). Update the Java runtime or apply the manual mitigation.' }
else { 'OK: no compromised eMudhra certificates found in this trust store. No action is needed for it.' }
}

上传到 TeamCity 的证书

2026.2.2 和 2026.1.6 版本中的健康报告会列出受影响的服务器证书。如需手动检查,请检查自定义证书并识别受影响的证书。

将包含自定义证书的目录路径(TC_CERT_DIR)设置为以下路径,然后运行命令:

  • 服务器:<TeamCity Data Directory>/config/projects/_Root/pluginData/trustedCertificates
  • 代理:<agent home>/conf/trustedCertificates

Linux 和 macOS(Bash 或 zsh):

$ bash
(
export TEAMCITY_JRE="${TEAMCITY_JRE:-$JAVA_HOME}"
export TC_CERT_DIR="${TC_CERT_DIR:-/path/to/trusted/certificates}"

keytool="${TEAMCITY_JRE:?Set TEAMCITY_JRE or JAVA_HOME to the Java home directory of the runtime in use}/bin/keytool"
[ -x "$keytool" ] || { echo "ERROR: keytool not found at $keytool. TEAMCITY_JRE must be the Java home directory (the folder that contains bin/ and lib/)." >&2; exit 1; }
[ -d "$TC_CERT_DIR" ] || { echo "ERROR: directory not found: $TC_CERT_DIR. Either no custom certificates have been added yet (TeamCity creates the directory on the first upload), or TC_CERT_DIR is incorrect (server: <TeamCity Data Directory>/config/projects/_Root/pluginData/trustedCertificates, agent: <agent home>/conf/trustedCertificates)." >&2; exit 1; }
found=0
while IFS= read -r f; do
  output=$("$keytool" -J-Duser.language=en -printcert -v -file "$f") ||
    { echo "ERROR: Cannot read $f." >&2; printf '%s\n' "$output" >&2; exit 1; }
  if grep -Eq '86:A1:EC:BA:08:9C:4A:8D|D7:A7:A0:FB:5D:7E:27:31|7D:D7:8D:5F:4F:13:45:9A|F2:ED:AA:FF:3D:9B:70:1F|40:F6:AF:03:46:A9:9A:A1|1A:A0:C2:70:9E:83:1B:D6|CN=EM (DV|OV|EV) (TLS|CS) CA - G3' <<< "$output"; then
    echo "AFFECTED: $f"; found=1
  fi
done < <(find "$TC_CERT_DIR" -maxdepth 1 -type f)
[ "$found" = 1 ] && echo "ACTION REQUIRED: the files listed above are compromised eMudhra certificates. Remove them (server: SSL/HTTPS Certificates page; agent: delete the files)." ||
  echo "OK: no compromised eMudhra certificates found in $TC_CERT_DIR. No action is needed."
)

Windows(PowerShell):

$ powershell
& {
$ErrorActionPreference = 'Stop'
if (-not $env:TEAMCITY_JRE) { $env:TEAMCITY_JRE = $env:JAVA_HOME }
if (-not $env:TC_CERT_DIR) { $env:TC_CERT_DIR = 'C:\path\to\trusted\certificates' }

if (-not $env:TEAMCITY_JRE) { throw 'Set TEAMCITY_JRE or JAVA_HOME to the Java home directory of the runtime in use.' }
$keytool = "$env:TEAMCITY_JRE\bin\keytool.exe"
if (-not [IO.File]::Exists($keytool)) { throw "keytool not found at $keytool. TEAMCITY_JRE must be the Java home directory (the folder that contains bin\ and lib\)." }
if (-not [IO.Directory]::Exists($env:TC_CERT_DIR)) { throw "ERROR: directory not found: $env:TC_CERT_DIR. Either no custom certificates have been added yet (TeamCity creates the directory on the first upload), or TC_CERT_DIR is incorrect (server: <TeamCity Data Directory>\config\projects\_Root\pluginData\trustedCertificates, agent: <agent home>\conf\trustedCertificates)." }
$pattern = '86:A1:EC:BA:08:9C:4A:8D|D7:A7:A0:FB:5D:7E:27:31|7D:D7:8D:5F:4F:13:45:9A|F2:ED:AA:FF:3D:9B:70:1F|40:F6:AF:03:46:A9:9A:A1|1A:A0:C2:70:9E:83:1B:D6|CN=EM (DV|OV|EV) (TLS|CS) CA - G3'
$found = $false
foreach ($file in Get-ChildItem -LiteralPath $env:TC_CERT_DIR -File) {
    $output = & $keytool '-J-Duser.language=en' -printcert -v -file $file.FullName
    if ($LASTEXITCODE -ne 0) { throw "ERROR: Cannot read $($file.FullName). $output" }
    if ($output -match $pattern) { "AFFECTED: $($file.FullName)"; $found = $true }
}
if ($found) { 'ACTION REQUIRED: the files listed above are compromised eMudhra certificates. Remove them (server: SSL/HTTPS Certificates page; agent: delete the files).' }
else { "OK: no compromised eMudhra certificates found in $env:TC_CERT_DIR. No action is needed." }
}

手动缓解命令

执行前,请确保当前终端中的 TEAMCITY_JRE 指向实际运行 TeamCity 组件的运行时:

  • 服务器:请参阅此配置参考。
  • 代理:按照 Java 更新指南 中的说明,在该代理的 Parameters 选项卡上检查 teamcity.agent.jvm.java.home。
  1. 将包含 CVE-2026-91403 修复的 JDK 中的 lib/security/blocked.certs 复制到正在使用的运行时中的同一位置。Java 8 使用 jre/lib/security/blacklisted.certs,而某些 Java 11 和 13 构建也使用 blacklisted.certs,因此复制前请先核实路径。
  2. 备份信任库。移除 AAA Certificate Services 可能会中断未受影响的服务;请先测试依赖的连接。
  3. 运行下面的命令。它使用与上方验证命令相同的脚本头,通过指纹查找上面列出的受影响根证书,并将其删除。再次运行此命令是安全的。

Linux 和 macOS(Bash 或 zsh):

$ bash
(
export TEAMCITY_JRE="${TEAMCITY_JRE:-$JAVA_HOME}"
export TC_TRUSTSTORE="$TEAMCITY_JRE/lib/security/cacerts"
export TC_STOREPASS='changeit'

keytool="${TEAMCITY_JRE:?Set TEAMCITY_JRE or JAVA_HOME to the Java home directory of the runtime in use}/bin/keytool"
[ -x "$keytool" ] || { echo "ERROR: keytool not found at $keytool. TEAMCITY_JRE must be the Java home directory (the folder that contains bin/ and lib/)." >&2; exit 1; }
[ -w "$TC_TRUSTSTORE" ] || { echo "ERROR: cannot write to $TC_TRUSTSTORE (missing or no permission). Run with sudo or as the owner of the Java installation." >&2; exit 1; }
output=$("$keytool" -J-Duser.language=en -list -v \
  -keystore "$TC_TRUSTSTORE" -storepass:env TC_STOREPASS) ||
  { echo "ERROR: Certificate check failed." >&2; printf '%s\n' "$output" >&2; exit 1; }

affected=$(awk '
  /^Alias name:/ { alias=substr($0, 13) }
  /SHA256: (86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B|D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4|7D:D7:8D:5F:4F:13:45:9A:83:DF:F9:AB:BB:A6:2E:DB:AF:6F:2D:10:2B:F2:57:FD:71:2F:4D:9F:27:46:ED:8D|F2:ED:AA:FF:3D:9B:70:1F:B4:D1:4C:9F:28:5F:8C:0B:AD:90:FC:3B:CC:1F:A0:7D:47:F4:71:8E:DA:BA:34:A5|40:F6:AF:03:46:A9:9A:A1:CD:1D:55:5A:4E:9C:CE:62:C7:F9:63:46:03:EE:40:66:15:83:3D:C8:C8:D0:03:67|1A:A0:C2:70:9E:83:1B:D6:E3:B5:12:9A:00:BA:41:F7:EE:EF:02:08:72:F1:E6:50:4B:F0:F6:C3:F2:4F:3A:F3)/ { print alias }
' <<< "$output")
[ -n "$affected" ] || { echo "OK: this trust store contains no compromised eMudhra roots. Nothing to delete."; exit 0; }

while IFS= read -r alias; do
  echo "Deleting: $alias"
  "$keytool" -delete -alias "$alias" -keystore "$TC_TRUSTSTORE" -storepass:env TC_STOREPASS || exit 1
done <<< "$affected"
echo "DONE: removed the compromised root certificates listed above. Restart the TeamCity server node or agent, then run the verification again."
)

Windows(PowerShell):

$ powershell
& {
$ErrorActionPreference = 'Stop'
if (-not $env:TEAMCITY_JRE) { $env:TEAMCITY_JRE = $env:JAVA_HOME }
$env:TC_TRUSTSTORE = "$env:TEAMCITY_JRE\lib\security\cacerts"
$env:TC_STOREPASS = 'changeit'

if (-not $env:TEAMCITY_JRE) { throw 'Set TEAMCITY_JRE or JAVA_HOME to the Java home directory of the runtime in use.' }
$keytool = "$env:TEAMCITY_JRE\bin\keytool.exe"
if (-not [IO.File]::Exists($keytool)) { throw "keytool not found at $keytool. TEAMCITY_JRE must be the Java home directory (the folder that contains bin\ and lib\)." }
try { [IO.File]::Open($env:TC_TRUSTSTORE, 'Open', 'ReadWrite').Dispose() }
catch { throw "ERROR: cannot write to $env:TC_TRUSTSTORE. Run PowerShell as Administrator or as the owner of the Java installation. ($($_.Exception.Message))" }
$prev = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
$output = & $keytool '-J-Duser.language=en' -list -v -keystore "$env:TC_TRUSTSTORE" -storepass:env TC_STOREPASS 2>&1
$ErrorActionPreference = $prev
if ($LASTEXITCODE -ne 0) { throw "ERROR: Certificate check failed. $output" }

$roots = '86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B|D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4|7D:D7:8D:5F:4F:13:45:9A:83:DF:F9:AB:BB:A6:2E:DB:AF:6F:2D:10:2B:F2:57:FD:71:2F:4D:9F:27:46:ED:8D|F2:ED:AA:FF:3D:9B:70:1F:B4:D1:4C:9F:28:5F:8C:0B:AD:90:FC:3B:CC:1F:A0:7D:47:F4:71:8E:DA:BA:34:A5|40:F6:AF:03:46:A9:9A:A1:CD:1D:55:5A:4E:9C:CE:62:C7:F9:63:46:03:EE:40:66:15:83:3D:C8:C8:D0:03:67|1A:A0:C2:70:9E:83:1B:D6:E3:B5:12:9A:00:BA:41:F7:EE:EF:02:08:72:F1:E6:50:4B:F0:F6:C3:F2:4F:3A:F3'
$alias = ''
$aliases = foreach ($line in $output) {
    if ($line -match '^Alias name: (.+)$') { $alias = $Matches[1] }
    if ($line -match "SHA256: ($roots)") { $alias }
}
if (-not $aliases) { 'OK: this trust store contains no compromised eMudhra roots. Nothing to delete.'; return }

foreach ($a in $aliases) {
    "Deleting: $a"
    $ErrorActionPreference = 'Continue'
    $result = & $keytool -delete -alias $a -keystore "$env:TC_TRUSTSTORE" -storepass:env TC_STOREPASS 2>&1
    $ErrorActionPreference = $prev
    if ($LASTEXITCODE -ne 0) { throw "ERROR: Failed to delete $a. $result" }
}
'DONE: removed the compromised root certificates listed above. Restart the TeamCity server node or agent, then run the verification again.'
}

之后,运行上方的验证命令以确认根证书已被移除。完成缓解后,重启服务器节点或代理。检查时请包含所有自定义信任库。## 安全公告

JetBrains 对 eMudhra 证书颁发机构事件的评估和指导可在此处获取。

常见问题

TeamCity 是否受到影响?

不会直接受到影响。作为 CI/CD 系统,TeamCity 会连接多个集成,并依赖具有独立信任存储的 Java 运行时、构建工具和其他组件。这些信任存储需要独立审查和更新,如 您需要做什么 中所述。

我需要升级吗?

建议升级到 2026.2.2 或 2026.1.6。这些版本新增了健康报告、其他产品改进,并修复了其他漏洞。

升级 TeamCity 并不总会更新 Java,因此请使用上文的 可选的信任存储验证 一节检查相应运行时。如果目前无法升级 TeamCity,请应用上文 如果无法升级 中所述的缓解措施。

TeamCity Cloud 是否受到影响?

TeamCity Cloud 已经更新,但客户仍必须检查:

健康报告会检查构建代理吗?

不会。健康报告只检查服务器节点。请按照上文 TeamCity 代理 所述,手动检查并修复构建代理。

哪些 TeamCity Docker 镜像受到影响?

2026.2.2 和 2026.1.6 版本的镜像已修补,因此请尽可能更新。旧镜像可按照上文 TeamCity 服务器 和 TeamCity 代理 所述进行修补,然后用于重新创建容器。

支持

如对本问题有任何疑问,请通过提交工单联系 TeamCity 支持团队。如需报告 TeamCity 中的安全问题,请遵循我们的协调披露政策。

参考资料