Skip to content

security: ssl/ 下提交了 *.naominet.live 通配符证书的私钥(有效期至 2034) #54

Description

@LittlePenhu

ssl/ 目录下有两个私钥被提交进了仓库,.gitignore 也没拦住。我把证书内容拉出来核了一遍,其中一张建议尽快处理:

文件 Subject Issuer 有效期 私钥是否配对
ssl/LocalRootCA.crt CN=LocalRootCA 自签根 CA 2024-04-03 → 2034-04-01 未入库
ssl/naominet.live.crt CN=*.naominet.live CN=LocalRootCA 2024-04-03 → 2034-02-10 是,配 ssl/naominet.live.key
ssl/portal.naominet.live.crt CN=portal.naominet.live 自签 2026-09-02 → 2027-09-02 是,npm run gen:cert 生成的那对

核对用的命令:

openssl x509 -in ssl/naominet.live.crt -noout -subject -issuer -dates
openssl verify -CAfile ssl/LocalRootCA.crt ssl/naominet.live.crt   # -> OK
openssl x509 -in ssl/naominet.live.crt -noout -pubkey | openssl md5
openssl rsa  -in ssl/naominet.live.key -pubout        | openssl md5  # -> 指纹一致,19c3568d…

严重性我想说准一点,不夸。先补一个影响判断的细节:ssl/naominet.live.crt 是 X509 v1、零扩展、完全没有 SAN,主机名只写在 CN 里(对比 portal. 那张是 v3,SAN 里有 portal.naominet.live, localhost, 127.0.0.1)。这决定了它到底能骗过谁:

  • 泄漏的是 *.naominet.live 的叶子私钥,aqua. / portal. / status. 都在 CN 通配符的覆盖范围内,有效到 2034 年;

  • 根 CA 的私钥(LocalRootCA.key)没入库,所以伪造不出新证书;

  • 无 SAN 意味着现代浏览器不认它 —— Chrome 58 / Firefox 48 之后,主机名一律只按 SAN 匹配、CN 被完全忽略。所以拿这张证书去中间人浏览器流量,会在主机名校验这一步就被挡下。这一条我只在 OpenSSL 系客户端上实测过,浏览器侧受本机信任库限制没能单独隔离验证(想用 --ignore-certificate-errors-spki-list 绕过信任问题,结果那个开关把主机名校验一起绕过了,连 evil.example.com 都放行,测不出结论),所以浏览器那半句是依据已知策略而非实测。

  • 真正能骗到的是仍回退到 CN 做校验的客户端,也就是 OpenSSL 那一系。我用这张证书实际起了个 HTTPS 服务测:

    客户端 请求的 servername 结果
    Node / OpenSSL aqua.naominet.live 通过(回退到 CN *.naominet.live)
    Node / OpenSSL portal.naominet.live 通过
    Node / OpenSSL deep.sub.naominet.live 拒绝
    Node / OpenSSL evil.example.com 拒绝

    第三行的报错原文是 Hostname/IP does not match certificate's altnames: ... is not cert's CN: *.naominet.live,正好反证它确实在用 CN 匹配(通配符只覆盖一层)。

  • 最后还有个前提:得有机器把 LocalRootCA 装进信任库。这一点我没找到证据 —— 仓库里没有任何地方引导信任这个根 CA(README 让你装的是 portal. 那张自签证书),这三个文件本身也全无引用。当初它们怎么用起来的、有没有机器信过这个根,我从仓库里看不出来。

所以我倒不觉得"反正是自签的,不用管"。但也不想把它说成"拿到 key 就能完整中间人全部 *.naominet.live 流量"——准确的说法是:对浏览器无效,对仍按 CN 校验的客户端有效,且需要目标机器信任这个根 CA。

另外这三个文件在仓库里已经没有引用了,看着就是遗留物:

  • scripts/gen-cert.mjs:11-12 只生成 portal.naominet.live.crt/key,复现不出上面三个;
  • vite.config.ts:12-13 和 scripts/serve-legacy-baseline.mjs:12-13 都只读 portal. 那一对;
  • 全仓也 grep 不到 naominet.live.crt / naominet.live.key / LocalRootCA.crt。

改的话是这样:

git rm --cached ssl/LocalRootCA.crt ssl/naominet.live.crt ssl/naominet.live.key

.gitignore 加一条:

# 本地开发证书,用 npm run gen:cert 重新生成
/ssl/

然后顺手轮换一下:凡是把 LocalRootCA 装进过信任库的机器,都该把这个根 CA 摘掉,改用 gen-cert.mjs 那种单域名自签证书。

最后提一句历史问题——这几个文件在 git 历史里还在,要彻底清掉得用 git filter-repo 重写。社区项目值不值得做到这一步,你们自己判断。但至少先让 key 从当前代码里消失。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions