不是所有 Windows 都能自动更新。
先把事情说清楚:这次出问题的是 eMudhra 的 8 个中间 CA 证书。
中间 CA 的作用,是「替根证书给网站签证书」。这 8 个中间 CA 的私钥泄露了。
泄露的方式特别离谱,值得看一眼。正常的 P-384 私钥长这样(一长串随机数字):
priv: 3f:9a:... (48 字节,几乎不可能猜出来)
而 eMudhra 出问题的那一份,是这样的:
priv: 00:00:00:...:00:04
对,就是 4。研究人员从 1 开始往回枚举,猜到 4 的时候,算出来的公钥和证书里公开的公钥完全一致 —— 私钥就这么破了。
拿到私钥之后,攻击者可以给任意域名签一张"看起来完全合法"的证书,包括冒充你的银行、你的公司 OA、你的路由器管理页。
打个比方:这相当于小偷拿到了能制作正规通行证的官方印章,想给谁发证就给谁发证。
正常情况下,浏览器会自己处理掉。各家动作都很快:
- Google Chrome:10 月 17 日,通过 CRLSet 远程封锁,不用更新、不用重启浏览器就生效;
- Mozilla Firefox:10 月 17 日,通过 OneCRL 远程封锁,从收到报告到分发完不到 3 小时;
- Apple / Safari:10 月 19 日,系统证书信任体系已封锁;
- Microsoft / Edge:10 月 20 日,同样已封锁。
所以只要你的浏览器是最近更新的、而且连着网,基本已经免疫了。
但「浏览器安全了」不等于「你的机器安全了」。curl、Python、Node.js、以及大量直接读系统证书库的应用程序,走的不是浏览器那套远程封锁机制 —— 它们只认系统里的根证书/中间证书列表。系统不更新,它们就还信着。
这也是本文为什么要手动把证书塞进「不信任列表」:拦的不是浏览器,是那些不走浏览器规则的程序。
但现实是:内网机、工控机、老系统,还有一堆「能上网但不让打补丁」的机器 —— 收不到这个更新。它们到现在还信着这些证书。
本文就是把它们手动丢进「不信任列表」。三步,五分钟,不需要联网授权,不需要等推送。
先确认一件事:你的机器是不是"中招"状态
打开这个检测站:
https://emudhra.we-have-a-problem.com/
红的就是还信着,绿的就是已经阻断。先看颜色,再决定要不要往下做。
红的话:任何用这 8 个中间 CA 签发的证书,你的浏览器都会当合法证书 —— 包括别人给自己签的「你的银行」「你的公司 OA」。
第一步:建脚本文件
随便找个目录,比如 D: 根目录。新建一个文本文件,全名改成:
Block-EmudhraCompromisedCA.ps1
注意是 .ps1,不是 .ps1.txt。Windows 默认隐藏扩展名,改之前先进「查看 → 文件扩展名」把它显示出来,不然你会得到一个打不开的文本文件。
把下面这段粘贴进去,保存:
#Requires -Version 5.1
#Requires -RunAsAdministrator
$ErrorActionPreference = 'Stop'
Import-Module PKI
$manifestSha256 = 'F67B3ABE9B4879F4E91874ED041BECCD71257F1DB41C588C724CCF12AA90C59E'
$url = 'https://emudhra.we-have-a-problem.com/impacted-intermediates.json'
$file = [IO.Path]::GetTempFileName()
try {
Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $file
$hash = (Get-FileHash -LiteralPath $file -Algorithm SHA256).Hash
if ($hash -ne $manifestSha256) {
throw "清单 SHA-256 不匹配,未导入任何证书。当前:$hash"
}
$certs = Get-Content -LiteralPath $file -Raw -Encoding UTF8 | ConvertFrom-Json
$n = 0
foreach ($c in $certs) {
$b64 = $c.pem -replace '-----(BEGIN|END) CERTIFICATE-----', '' -replace 's', ''
[IO.File]::WriteAllBytes($file, [Convert]::FromBase64String($b64))
Import-Certificate -FilePath $file -CertStoreLocation Cert:LocalMachineDisallowed | Out-Null
$n++
}
Write-Host "已导入 $n 个中间 CA 到不信任列表" -ForegroundColor Green
}
finally {
Remove-Item -LiteralPath $file -ErrorAction SilentlyContinue
}
这里有个细节值得说一下:脚本会先校验清单文件的 SHA-256,对不上就直接退出、一张证书都不导入。因为清单是从公网下载的,不校验等于「谁劫持了下载,谁就能决定你信谁」——那就本末倒置了。
第二步:运行
在保存脚本的文件夹里,资源管理器顶部的地址栏直接输入 powershell 回车 —— 这样打开的命令行会定位在当前目录,不用 cd。
然后执行:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .Block-EmudhraCompromisedCA.ps1
要点:
- 必须是管理员。写
LocalMachine的证书存储需要提权,普通窗口会报权限错误; -ExecutionPolicy Bypass是必须的 —— 默认执行策略会拦下未签名的脚本;- 跑完应该看到
已导入 N 个中间 CA 到不信任列表。
第三步:验证
回到那个检测站,刷新。
变绿就成了。

想用命令行确认也可以,看一眼不信任列表里有没有这些证书:
Get-ChildItem Cert:LocalMachineDisallowed | Where-Object { $_.Subject -like '*eMudhra*' } | Select-Object Subject, Thumbprint
这不算"修好了"
手动导入只是让这一台机器不再信任它们。真正该做的是等 Windows 正式更新把根列表推下来 —— 那时候系统会自动处理,你这两步也就不需要了。
所以适用场景很明确:
- 能自动更新、能等推送的机器 → 别折腾,直接更新;
- 离线机、隔离网、不能打补丁的老机器 → 用这个;
- 批量的话,把脚本丢进组策略 / SCCM 的启动脚本里,比一台台敲快得多。
另外补一句:改完记得重启浏览器。Chrome / Edge 有自己的一份缓存,不重启可能还是旧状态。
来源
这套做法最早是我在 NodeSeek 看到网友「80hou」整理的《中间 CA 私钥泄露修复留档》里学到的,原文写得很干净。本文按自己的习惯重写了一遍,脚本也加了中文提示和错误信息。
- 检测站与清单:
https://emudhra.we-have-a-problem.com/ - 站点源码:
https://github.com/galadran/we-have-a-problem.com - 原帖(需登录):
https://www.nodeseek.com/post-1000277-1 - 私钥枚举细节与各家处理时间表:小众软件这篇(写得更通俗,推荐先看)
- 事件背景(中间 CA 泄露是怎么回事):一周七个雷
清单哈希以检测站当前发布为准;如果你跑的时候报 SHA-256 不匹配,先去检测站核对最新值,别硬改脚本。

F12笔记






