编程 Go 1.27.3 / 1.26.10:crypto/x509 拉黑中间证书后的升级与排查

2026-10-21 21:04:37

Go 1.27.3 / 1.26.10:crypto/x509 拉黑中间证书后的升级与排查

Go 1.27.3 与 1.26.10 于 2026-10-19 发布,这一版只包含一个安全修复:crypto/x509 针对某个特定 CA 的安全问题做缓解,拉黑一小批中间证书。Verify 现在会过滤包含这些中间证书的链,不会把这类链返回给调用方。对应 CVE-2026-111943、Go issue 82345,官方建议立即升级。

修复内容

问题的核心不在根证书,而在中间证书。某些中间证书已经不应再被信任,但部分环境里的信任材料并不会自动失效。修复方式是在 crypto/x509 中加入 denylist:当调用 Verify 且待验证的链中包含已知不可信的中间证书时,直接返回错误。

这意味着失败点被前移到了证书校验层。TLS 握手失败时看到的是 x509 相关错误,而不是证书过期、域名不匹配之类的常见提示,排查方向需要相应调整。

GO-2026-6809 与受影响版本

GO-2026-6809 于 2026-10-21 发布,符号为 Certificate.Verify。受影响区间:

  • before go1.26.10
  • from go1.27.0-0 before go1.27.3

也就是说,1.26 线需要升到 1.26.10,1.27 线需要升到 1.27.3。该条目别名 CVE-2026-111943,相关记录还有 RUSTSEC-2026-0381、RUSTSEC-2026-0382、RUSTSEC-2026-0383。

系统信任库为什么不一定跟得上

大多数平台上 Go 使用系统根证书,但在一些较窄的场景下,系统信任库不会自动更新。只升级系统 CA 包(如 ca-certificates)不足以在所有环境覆盖:

  • 容器镜像可能长期固定某个旧版本 CA 包,构建时不会重新拉取。
  • 旧发行版的更新渠道可能滞后,或该中间证书并不在其吊销范围内。
  • 离线镜像、内网制品库中的基础镜像同样可能持有旧中间证书。

因此这次缓解放在 Go 自身的 Verify 层,而不是依赖平台信任库的状态。升级 Go 工具链和重新构建二进制,是覆盖面更确定的做法。

Go 1.27 起 SystemCertPool 的行为变化

Go 1.27 release notes 中 crypto/x509 有一处相关变化:SystemCertPool 在 Windows 和 Darwin 上也尊重 SSL_CERT_FILE 和 SSL_CERT_DIR。设置这两个环境变量时,根证书从磁盘加载,并改用 Go 原生证书校验器,而不是平台证书校验 API。

这会影响两类环境:

  • 之前只在 Linux 上通过 SSL_CERT_FILE 注入自定义根的部署,在 Windows / Darwin 上行为可能改变。
  • 依赖平台校验 API 特性(如系统级策略)的场景,在设置环境变量后会走 Go 原生校验路径。

如需关闭该行为,可设置:

GODEBUG=x509sslcertoverrideplatform=0

排查方法

先确认运行时版本,避免出现「代码已改、镜像里还是旧 Go」的情况:

go version
go list -m all | grep '^go '

在容器内对比实际下发的证书链:

openssl s_client -showcerts -connect example.com:443 </dev/null

观察输出中的中间证书是否属于被拉黑的那一批。随后用 Certificate.Verify 打印错误原文,确认失败发生在校验链阶段:

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"log"
)

func main() {
	conn, err := tls.Dial("tcp", "example.com:443", &tls.Config{
		ServerName: "example.com",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	state := conn.ConnectionState()
	leaf := state.PeerCertificates[0]

	roots, err := x509.SystemCertPool()
	if err != nil {
		log.Fatal(err)
	}

	intermediates := x509.NewCertPool()
	for _, c := range state.PeerCertificates[1:] {
		intermediates.AddCert(c)
	}

	_, err = leaf.Verify(x509.VerifyOptions{
		DNSName:       "example.com",
		Roots:         roots,
		Intermediates: intermediates,
	})
	if err != nil {
		fmt.Printf("verify failed: %v\n", err)
		return
	}
	fmt.Println("verify ok")
}

如果业务需要显式信任某条链,应显式配置 RootCAs(以及必要的中间证书),而不是依赖系统池:

pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caPEM)

cfg := &tls.Config{
	ServerName: "example.com",
	RootCAs:    pool,
}

go.mod 固定 toolchain

在 go.mod 中写明版本,避免本地与 CI 使用不同工具链:

module example.com/app

go 1.27.3

toolchain go1.27.3

1.26 线则对应写成 go 1.26.10 与 toolchain go1.26.10。构建镜像时同步更新基础镜像中的 Go 版本,并重新执行 go build,确保产出二进制携带新的 denylist 逻辑。

复制全文 生成海报 Go 安全升级 TLS

推荐文章

程序员茄子在线接单