eMudhra 的一个证书颁发机构私钥就是数字 4。One of eMudhra's certificate authority private keys is the number 4.
不是一个以 4 开头的数,也不是 4 后面跟着几百位随机数字,就只是 4……另一个是 56,还有一个是 115。我知道这些,因为我刚才用自己的笔记本从它们的公开证书中恢复了全部这些私钥,用时不到一秒。Not a number that starts with 4, or the number 4 followed by a few hundred random digits, just, 4... Another one is 56, and another is 115. I know, because I recovered all of them from their public certificates on my laptop just now, and it took less than a second.
发生了什么What happened
10 月 17 日星期六,一位安全研究员向 Apple、Google、Microsoft、Mozilla 和 eMudhra 报告称,eMudhra 八张中间证书的私钥已泄露。当晚 Chrome 推送了 CRLSet 更新将它们封禁,几小时后 Mozilla 通过 OneCRL 在 Firefox 中将其封禁。10 月 20 日星期二,Mozilla 公开发布公告并发布了 NSS 更新移除 eMudhra 根证书,Chrome 也确认该根证书将进入 Chrome Root Store v43。On Saturday 17th October a security researcher reported to Apple, Google, Microsoft, Mozilla and eMudhra that the private keys for eight eMudhra intermediate certificates were compromised. Chrome pushed a CRLSet update blocking them that evening and Mozilla blocked them in Firefox via OneCRL a few hours later. On Tuesday 20th, Mozilla announced it publicly and shipped an NSS update removing the eMudhra root, and Chrome confirmed the root is going in Chrome Root Store v43.
这八张中间证书包括六张 TLS 签发 CA(DV、OV 和 EV,各两代)和两张代码签名 CA,全部创建于 2025 年 4 月。其中每一张在 2025 年 5 月还都被 Sectigo 的 AAA Certificate Services 根证书交叉签名。The eight intermediates are six TLS issuing CAs (DV, OV and EV, two generations each) and two code-signing CAs, all created in April 2025. Every one of them was also cross-signed by Sectigo's AAA Certificate Services root in May 2025.
以上是“发生了什么”,但真正有趣的是“如何发生的”,Ian Carroll 在讨论帖中用一句话总结了这一点:That's the "what", but it's the "how" that's the interesting part, and it was summed up in one line on the thread by Ian Carroll:
它们本质上是任何人都可以分解的弱单字节密钥,而且这个问题无法被控制住。They are inherently weak single-byte keys that anyone can factor, and the issue cannot be contained.
私钥到底是什么What a private key actually is
这些是 P-384 曲线上的椭圆曲线密钥。私钥只是一个数,d。公钥——也就是放进证书里让全世界看到的东西——是 d × G,其中 G 是人人都用的曲线上一个固定点,“×”表示用曲线的特殊运算把 G 自身相加 d 次。These are elliptic-curve keys on the P-384 curve. The private key is just a number, d. The public key, the thing that goes in the certificate for the world to see, is d × G, where G is a fixed point on the curve that everyone uses and "×" means adding G to itself d times using the curve's special arithmetic.
正向计算很容易,但反向运算——从公钥求出 d——被认为是不切实际的。这就是整个安全模型,而它只有在 d 非常大时才成立。P-384 私钥应当是一个随机的 384 比特数,这给出 2³⁸⁴ 种可能(约 10¹¹⁵),没人会对这么大的空间进行搜索。Going forwards is easy, but going backwards, from the public key to d, is considered to be impractical. That's the entire security model, and it only holds if d is enormous. A P-384 private key should be a random 384-bit number, which gives you 2³⁸⁴ possibilities (or roughly 10¹¹⁵), so nobody is ever going to search that.
eMudhra 的密钥看起来是这样的:eMudhra's keys look like this:
d = 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
P-384 私钥本应看起来是这样的:A P-384 private key should look like this:
d = 0x48ac06790886c80c48d31691fd4c0fe9ce3207f9807f2fe571d6e4ec426d78644298fc53df2b92566970e757ac998c34也就是 47 个字节的零,然后是一个字节,而且全部八个密钥都是如此。一个字节有 256 种可能的取值,所以“搜索”就是:计算 1 × G、2 × G、3 × G……直到与证书匹配为止,最多 255 步。That's 47 bytes of zeros and then one byte, and all eight keys are like that. A single byte has 256 possible values, so the "search" is: compute 1 × G, 2 × G, 3 × G, and stop when it matches the certificate, which is at most 255 steps.
于是,这整个体系——那些 CA 曾经签发过的每一张证书、每一个信任它们的客户端——全都建立在八个比特之上。So the whole thing, every certificate those CAs could ever issue, every client that trusted them, rested on eight bits.
我验证过了I checked
Mozilla 在公告中附上了受影响的证书,所以我拉取了全部 16 张(8 把密钥,各自用于两张已签发证书:一次由 eMudhra 签发,一次由 Sectigo 签发)并进行了暴力破解。每一张都瞬间被破解,六张 TLS 证书如此,两张代码签名密钥也同样弱:Mozilla attached the affected certificates to their announcement, so I pulled all 16 (8 keys, each used in two issued certificates: once by eMudhra and once by Sectigo) and brute-forced them. Every single one fell over instantly, the six TLS values, and the two code-signing keys are just as weak:
| Intermediate | Type | d |
|---|---|---|
| EM DV TLS CA - G3B | TLS | 156 |
| EM DV TLS CA - G3C | TLS | 230 |
| EM EV TLS CA - G3B | TLS | 115 |
| EM EV TLS CA - G3C | TLS | 56 |
| EM OV TLS CA - G3B | TLS | 155 |
| EM OV TLS CA - G3C | TLS | 4 |
| EM OV CS CA - G3B | Code signing | 66 |
| EM OV CS CA - G3C | Code signing | 237 |
有意思的是,这些证书从外观上看没有任何异常。由 d = 4 导出的公钥是完全合法的曲线上的点,编码没问题,曲线没问题,每一项结构检查都能通过。唯一的发现办法就是去试。It's interesting that nothing about these certificates looks wrong. A public key derived from d = 4 is a perfectly valid point on the curve, the encoding is fine, the curve is fine, every structural check passes. The only way to find out is to try.
为什么说“无法被控制住”Why "it cannot be contained"
通常当 CA 密钥泄露时,意味着有人偷了它!有小偷,有一个泄露发生的时刻,你至少可以推断现在谁可能接触到了那把密钥。Normally when a CA key is compromised, someone stole it! There's a thief, there's a moment it happened, and you can at least reason about who now has access to that key.
而在这里,没人需要偷任何东西。自证书诞生之日起,私钥实际上就是公开的。自 2025 年 4 月以来的任何时候,任何看过其中一张证书的人,都可以在普通硬件上于几毫秒内恢复出密钥。那是一个长达 17 个月的时间窗口,而且根本无法知道是否有人已经这么做了。Here, nobody had to steal anything. The key was effectively public from the day the certificate existed. Anyone who looked at one of these certificates, at any point since April 2025, could have recovered the key in milliseconds on commodity hardware. That's a 17 month window and there is no way to know whether someone did.
Chrome 指出,由于 Chrome 强制执行证书透明度(Certificate Transparency),而没有任何链到这些 CA 的 TLS 证书出现在 CT 中,因此伪造的证书在 Chrome 中不可能默认被信任。这是事实,对浏览器来说令人放心。但 CT 只是检测错误签发,并不能阻止它,而且很多软件根本不检查 CT。Chrome made the point that because Chrome enforces Certificate Transparency, and no TLS certificates chaining to these CAs ever showed up in CT, no forged certificate could have been trusted by default in Chrome. That's true and it's reassuring for browsers. CT detects mis-issuance though, it doesn't prevent it, and a lot of software doesn't check CT at all.
今天谁处于风险之中Who's exposed today
主流且保持更新的浏览器几乎肯定没问题。Chrome 和 Firefox 直接封禁了这些中间证书,两者都要求 CT,Safari 也要求 CT。Mainstream and updated browsers are likely absolutely fine. Chrome and Firefox block the intermediates directly and both require CT, and Safari requires CT too.
但其他一切都完全是另一回事了。大多数非浏览器 TLS 客户端(基本上就是使用默认设置的其他一切),比如 curl、Python、Node、Go、Java、git、包管理器以及你所有的脚本和守护进程,会做三件与此相关的事情:它们信任磁盘上的 CA 包,默认不检查吊销,也不检查 CT。因此 eMudhra 和 Sectigo 已经吊销这些中间证书这一事实对它们毫无帮助。这是我在谈论 TLS 和 PKI 时经常发现的一点:客户端生态系统有多么庞大,这一点很容易被忽视。我们在培训中也常常关注这一点,比如 CRLite 这类现代吊销方案在桌面浏览器上效果极佳,但对任何其他 TLS 客户端毫无作用。想想现在有哪些东西需要更新它们的根证书库,而这只是冰山一角:Everything else is a different story, though. Most non-browser TLS clients (so basically everything else, with default settings), things like curl, Python, Node, Go, Java, git, package managers and every script and daemon you've got, do three things that matter here: they trust a CA bundle on disk, they don't check revocation by default, and they don't check CT. So the fact that eMudhra and Sectigo have revoked these intermediates doesn't help them at all. This is something that I find often when talking about TLS and PKI, understanding just how massive the client ecosystem is can be overlooked. We often focus on this in training when we look at modern solutions for revocation like CRLite, which works wonders on the desktop browsers, but does nothing for any other TLS client. Consider what needs its root store updating now, and this is just the tip of the iceberg:
- curl | sh 安装器和自更新器。许多工具通过 HTTPS 下载脚本或二进制文件进行安装或更新,而且不校验签名(rustup、nvm、Homebrew 的安装器、数不清的 "install.sh" 页面、IoT 固件更新器)。在这里伪造证书意味着运行攻击者的代码,而且常常是以 root 身份。curl | sh installers and self-updaters. Many tools install or update by downloading a script or binary over HTTPS with no signature check (rustup, nvm, Homebrew's installer, countless "install.sh" pages, IoT firmware updaters). A forged certificate here means running the attacker's code, often as root.
- CI/CD 流水线。GitHub/GitLab 的 runner、Jenkins 和构建容器会拉取依赖并推送制品,同时还保管着云端密钥、仓库令牌和签名密钥。它们运行在共享网络中的过时基础镜像上,其底层全是 curl、Python、Go 和 Node。CI/CD pipelines. GitHub/GitLab runners, Jenkins and build containers fetch dependencies and push artifacts, while holding cloud keys, registry tokens and signing keys. They run on stale base images in shared networks, and they're all curl, Python, Go and Node underneath.
- 服务器之间的 API 调用。支付服务商、webhook、OAuth 令牌交换和云 SDK(AWS CLI 和 boto 使用自己的 certifi 包;Terraform 和 kubectl 使用 Go)。它们在请求头中携带 API 秘密和客户数据。Server-to-server API calls. Payment providers, webhooks, OAuth token exchanges and cloud SDKs (AWS CLI and boto use their own certifi bundle; Terraform and kubectl use Go). These carry API secrets and customer data in headers.
- IoT、嵌入式和 OT 设备。摄像头、路由器、汽车、医疗设备、智能家电、支付终端和工业控制器,会连回云端获取命令和更新。它们的证书包被冻结到 2039 年,这正是“无法被控制住”这一点成真的地方。IoT, embedded and OT devices. Cameras, routers, cars, medical devices, smart appliances, payment terminals and industrial controllers phoning home for commands and updates. Their bundles are frozen until 2039, which is the "cannot be contained" point made real.
- Windows 代码签名。这不是 TLS,但缺陷相同:Windows 直接信任 eMudhra CS 根证书。对于任何不检查吊销的程序来说,用 EM OV CS CA - G3B/G3C 签名的恶意软件看起来就像来自真实发行商的签名软件。Windows code signing. It isn't TLS, but it's the same flaw: Windows trusts the eMudhra CS root directly. Malware signed with EM OV CS CA - G3B/G3C looks like signed software from a real publisher to anything that doesn't check revocation.
我在自己的机器上用本地服务器和一张 api.stripe.com 的证书测试了这一点——一个我们大概都会认同极其高价值的域名。在默认设置下,curl 输出了 SSL certificate verify ok,Python 完成了握手,Node 输出了 authorized: true。我测试的每一台 Linux 和 macOS 机器上都是如此。在 Windows 上,除非开启吊销检查,否则 CryptoAPI 会接受该证书链,而即便开启了,两条路径里也只有一条返回了已吊销的结果。I tested this on my own machines using a local server and a certificate for api.stripe.com, a name that we can all likely agree is very high value. With default settings, curl said SSL certificate verify ok, Python completed the handshake, and Node said authorized: true. That was true on every Linux and macOS machine I tested. On Windows, CryptoAPI accepted the chain unless revocation checking was switched on, and even then only one of the two routes came back as revoked.
scott@Middle-Earth:~$ curl -v --resolve api.stripe.com:18888:127.0.0.1 https://api.stripe.com:18888/
* Added api.stripe.com:18888:127.0.0.1 to DNS cache
* Hostname api.stripe.com was found in DNS cache
* Trying 127.0.0.1:18888...
...snip...
* Server certificate:
* subject: CN=api.stripe.com
* start date: Oct 20 18:39:57 2026 GMT
* expire date: Oct 21 18:44:57 2026 GMT
* subjectAltName: host "api.stripe.com" matched cert's "api.stripe.com"
* issuer: C=NL; O=eMudhra BV; CN=EM DV TLS CA - G3B
* SSL certificate verify ok.
> GET / HTTP/1.1
> Host: api.stripe.com:18888
> User-Agent: curl/7.68.0
> Accept: */*
>
...snip...
< HTTP/1.0 200 ok
< Content-type: text/html
<
<HTML><BODY BGCOLOR="#ffffff">
<pre>
s_server -accept 127.0.0.1:18888 -cert /tmp/tmp.rBcIpjTbZV/leaf.pem -key /tmp/tmp.rBcIpjTbZV/leaf.key -cert_chain /home/scott/code/test/intermediate-sectigo.pem -www -quiet
...snip...
</pre></BODY></HTML>
...snip...
* Closing connection 0
* TLSv1.3 (OUT), TLS alert, close notify (256):
scott@Middle-Earth:~$你可以在那张证书里放入任何你想要的主机名,然后就拥有了拦截该设备流量所需的一切,你要做的只是让自己处于网络路径之上。You can put any other hostname you want in that certificate and you have everything you need to intercept that device's traffic, all you have to do is get on the network path.
你现在应该做什么What you should do now
幸运的是,对于你能更新的设备来说,所需的步骤并不特别复杂,建议基本上可以归结为“去更新它们”。问题在于那些你无法更新或不易更新的设备。It's fortunate that the steps required here aren't particularly complicated for the devices you can update, and the advice essentially boils down to 'update them'. The problem is the devices you can't update or update easily.
- 更新所有自带 CA 包的东西:你的操作系统
ca-certificates包、certifi、Node、Homebrew,并在基础镜像获得更新后重新构建你的容器镜像。Update everything that ships a CA bundle: your OSca-certificatespackage, certifi, Node, Homebrew, and rebuild your container images once the base images pick up the change. - 无法更新的设备才是真正的难题。路由器、打印机、电视、固件被冻结的家电会一直信任这些证书直到过期,也就是 2039 年 7 月……Devices you can't update are the hard part. Routers, printers, TVs, appliances with frozen firmware will trust these certificates until expiry, which is July 2039...
八个比特。整整 17 个月,将海量非浏览器软件与“一张任意网站的伪造证书就能击穿你所有加密流量安全”隔开的,仅仅是这八个比特。Eight bits. For 17 whole months, that's all that stood between a huge amount of non-browser software and a forged certificate for any site blowing the security on all of your encrypted traffic.
来源Sources
MDSP 讨论帖:https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/0R3XLJL6FuE/m/Kp1v6CPuAAAJMDSP thread: https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/0R3XLJL6FuE/m/Kp1v6CPuAAAJ
研究公告:https://emudhra.we-have-a-problem.com/Research announcement: https://emudhra.we-have-a-problem.com/
2026 年 10 月 20 日 20:16 UTC 更新在发布本文后不久,Mozilla 公开了他们的事件工单:https://bugzilla.mozilla.org/show_bug.cgi?id=2083449Update 20th Oct 2026 20:16 UTC
Shortly after publishing, Mozilla made their incident ticket public: https://bugzilla.mozilla.org/show_bug.cgi?id=2083449
eMudhra 已在该工单上给出了他们对事件经过的说明。看来是仪式中使用脚本中的一个 bug,在 HSM 上生成了第二对弱密钥,由于名称相似,人为失误导致错误的密钥被用于 CSR。根据这一新信息,我上文的内容没有任何需要修改之处。我还可以确认,只需在 AI 智能体中输入一个简单提示,我就能恢复 CA 的私钥。Claude 仅用一个提示就恢复了私钥。eMudhra have given their account of what happened on that ticket. It seems there was a bug in the script used during the ceremony, a second, weak key pair was created on the HSM, and having similar names a human error lead to the wrong keys being used for the CSR. Nothing in the text of my article above has changed as a result of this new information. I can also confirm that a simple prompt in an AI agent allowed me to recover the private keys of the CA. Claude was able to recover the private keys with a single prompt.