本文へ移動
技術を、自社の仕事に。
判断と実行を助けるメディア

記事を検索

特集:脆弱性の緊急対応を記録で振り返る

eMudhra中間CA侵害 Macの開発端末でcurl・Python・Nodeを点検

目次 · 8項目

Safari で確認用のページを開くと、証明書のエラーが出て「守られている」と分かります。ところが同じ Mac のターミナルで curl を打つと、侵害された中間CAの証明書を使うテスト用のホストに HTTP 200 で接続できました。ブラウザが無事でも、開発で使う curl・Python・Node.js は、それぞれ別の証明書の束を見ているからです。

2026年10月22日の午前、私たち(グリームハブの開発チーム)は、開発に使う Mac 1台でこの点検をしました。本記事はその記録です。どの道具がどの束を見ているか、どう確かめたか、更新の途中でどこにつまずいたかを、実際のコマンドと結果で残します。

本記事は、10月21〜22日に私たちが行ったセキュリティ対応を3回に分けて記録する特集の第2回です。第1回はPHPの緊急リリースへの対応、第3回はNode.js 22.23.4 への社内の一括更新を扱います。

ブラウザの外は、それぞれの束を更新するまで残る

問題をまとめたページ(emudhra.we-have-a-problem.com、10月20日公開)によると、eMudhra の中間CA 8件の秘密鍵が侵害されました。CAが鍵を誤った手順で作ったため、証明書から誰でも鍵を復元できる状態だったとしています。報告は10月17日で、遮断は Firefox と Chrome が17日、Apple が19日、Microsoft が20日です。

この鍵があれば、どのサイトにもなりすませます。ブラウザと OS は、失効の情報を配る仕組み(Firefox の OneCRL や CRLite、Chrome の CRLSets など)で一斉に止めました。一方で同じページは、多くのクライアントは失効の情報を読まないため、ソフトウェアの更新が必要だと書いています。Cloudflare のブログも「ブラウザのテストが通っても、裏で動くサービスが守られている証明にはならない」「アプリが別の検証の仕組みを使うなら curl のテストも同じ」と注意しています。

テスト用のホストは、侵害された中間CAの証明書を使っています。証明書の連なりは2通りあり、dv-g3b-emudhra は emSign ECC Root CA - G3 に、dv-g3b-aaa-emudhra は Sectigo(旧 Comodo)の AAA Certificate Services につながっていました。束で対応する場合は、この2つのルートを外すのが基本です。Python の certifi の束で何が消えたかは、certifiのemSign削除の記事で整理しています。

テスト用のホストに、道具ごとに接続する

判定は単純で、接続できたら危険、証明書のエラーになれば保護されています。ブラウザは確認用のページの「Run check」で調べられます。ターミナルの道具は、2つのホストにそれぞれ接続します。

# curl(パスを指定して、どの curl かをはっきりさせる)
/usr/bin/curl -sS -o /dev/null -w '%{http_code}\n' https://dv-g3b-emudhra.we-have-a-problem.com/
/opt/homebrew/opt/curl/bin/curl -sS -o /dev/null -w '%{http_code}\n' https://dv-g3b-aaa-emudhra.we-have-a-problem.com/

# Python(どの束を読むかも表示する)
python3 -c "import ssl; print(ssl.get_default_verify_paths().openssl_cafile)"
python3 -c "import urllib.request as u; print(u.urlopen('https://dv-g3b-emudhra.we-have-a-problem.com/').status)"

# Node.js
node -e "require('https').get('https://dv-g3b-emudhra.we-have-a-problem.com/', r => console.log(r.statusCode)).on('error', e => console.log(e.code))"

python3 や node は、PATH の順番で別の版が動くことがあります。macOS 標準の /usr/bin/python3、Homebrew の Python、pyenv の Python のように、入っているものをすべてパスで指定して試しました。

落とし穴1:nscurl は失敗しても終了コードが0

macOS 標準の通信の仕組み(URLSession)は nscurl で確かめられます。ただし、終了コードでは判定できません。証明書のエラーでも、期限切れの証明書を返すテストサイト(expired.badssl.com)でも、終了コードは0でした。-v を付けて出力を読みます。

nscurl -v https://dv-g3b-emudhra.we-have-a-problem.com/ 2>&1 | grep -E 'successfully connected|Code=-1202'

拒否されると NSURLErrorDomain Code=-1202 が出ます。接続できた場合は、テスト用のホストが返す「Your client successfully connected to the site. That’s bad!」という本文が表示されます。

もう1つ、同じ日の昼前に再確認したところ、12あるテスト用のホストのうち11は -1202 で拒否され、dv-g3b-emudhra の1つだけ nscurl で接続できました。同じ証明書の連なりを security verify-cert で調べると、結果は失効(CSSMERR_TP_CERT_REVOKED)です。原因は分かっていません。1つのホストだけで判断せず、複数のホストで確かめることを勧めます。

対応前と対応後の結果

対応前と、対応後の10月22日の再確認の結果です。macOS は 26 系、Mac 1台での結果です。

道具参照する証明書対応前対応後(10月22日の再確認)
SafarimacOS の信頼の仕組み拒否変更なし
macOS 標準の通信(nscurl)macOS の信頼の仕組み拒否(-1202)12ホスト中11で拒否、1で接続
標準の curl(LibreSSL)と /usr/bin/python3/etc/ssl/cert.pem接続(200)接続(200)のまま
Homebrew の curl(OpenSSL)Homebrew の ca-certificates接続(200)拒否
Homebrew と pyenv の PythonHomebrew の OpenSSL の cert.pem(実体は ca-certificates)接続(200)拒否
Node.js(Volta)Node.js に組み込みの束22.19.0 で接続(200)22.23.4 で拒否(CERT_REVOKED)

Chrome は、各社の発表では遮断済みですが、この端末では確かめていません。拒否されたときの表示は、Homebrew の curl が unable to get local issuer certificate、Python が CERTIFICATE_VERIFY_FAILED でした。どちらも、通常のサイト(example.com)には接続できています。

Homebrew の ca-certificates を上げる

Homebrew の curl と Python は、Homebrew の ca-certificates が作る束を読みます。この端末の版は2026-08-13版でした。確認用のページは Homebrew の修正版を「2026-09-25_1」としています。その版の定義(Formula)には、AAA Certificate Services と emSign G3 のルートを公開鍵で除くという記述があります。

brew upgrade ca-certificates

2026-10-20版に上がり、Homebrew の curl と Python 2種はテスト用のホストを拒否するようになりました。更新後の束を openssl で読むと、emSign ECC Root CA - G3 と AAA Certificate Services は入っていません。

落とし穴2:約78件が一緒に更新され、Python の venv が6つ止まった

このコマンドは、ca-certificates だけでなく依存するパッケージもまとめて更新し、約78件が同時に上がりました。その中で python@3.14 がなくなって python@3.15 に置き換わり、/opt/homebrew/bin/python3 は 3.15.0 になりました。

困ったのは、Homebrew の Python 3.14 で作った venv(Python の仮想環境)です。6つが起動しなくなりました。venv の pyvenv.cfg は、作ったときの Python の場所を記録しています。

grep '^home' <venv のフォルダ>/pyvenv.cfg
# home = /opt/homebrew/opt/python@3.14/bin

この場所がなくなったため、venv の Python が動かなくなりました。復旧は、入るものが1つだけかを先に確かめてから、python@3.14 を入れ直しました。

brew install --dry-run python@3.14   # 入るのが python@3.14 の1件だけかを確かめる
brew install python@3.14             # 3.14.9 が入った
<venv のフォルダ>/bin/python -V
<venv のフォルダ>/bin/python -m pip check   # uv で作った環境は uv pip check

6つとも起動し、依存関係の整合も取れていました。uv がビルドに使った一時フォルダ539個も同じ場所を指していましたが、使い終わった残りなので実害はありません。

なぜ大量に更新されたのかは、man brew に説明があります。brew upgrade は、HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK を設定していない限り、更新したパッケージに依存していて古くなったもの(dependents)も更新します。この環境変数を設定すると、更新される dependents は減ります。ただしマニュアルは、その分 brew upgrade で壊れることが増えるかもしれないとも書いています。

python@3.14 が消えた理由は、ログが残っておらず確かめられていません。私たちの推測では、python@3.14 は他のパッケージの依存として入っていて、依存元が python@3.15 に切り替わったため、不要になった依存として自動で削除されました。いまの端末では、python@3.14 に依存するパッケージは0件、python@3.15 に依存するパッケージは1件です。マニュアルによると、brew upgrade の後には brew cleanup が走ります。不要になった依存の自動削除は、HOMEBREW_NO_AUTOREMOVE で止められます。

次からは、更新の前に何が動くかを見ます。

brew upgrade --dry-run ca-certificates

--dry-run は、更新されるものを表示するだけで、実際には何も更新しません。ただし、今回のように python@3.14 が消えることまで予告されるかは、確かめていません。venv をたくさん持っている端末では、更新の前に pyvenv.cfg の home を一覧しておくと、影響する範囲が先に分かります。

Volta の Node.js を 22.23.4 に上げる

Node.js は、組み込みの証明書の束を使います。修正版の 22.23.4(10月21日公開)のリリースノートには、「root certificates を NSS 3.131.1 に更新」とあります。私たちは Volta で Node.js を管理しているため、既定の版を上げました。

volta install node@22.23.4

テスト用のホストは CERT_REVOKED で拒否されました。ただし、古い 22.19.0 は今も接続できます(volta run --node 22.19.0 で確認)。古い Node.js を消したわけではないので、その版で動くものは危険なままです。

落とし穴3:CLI ツールは入れたときの Node.js で動き続ける

Volta で入れた CLI ツールは、インストールしたときの Node.js の版に固定されます。今回は 22.17.0 や 20.19.0 に固定されたものがありました。既定の版を上げても、これらは古い Node.js で動き続けます。固定先は次のコマンドで確かめられます。

volta list --format plain | grep '^package'
# package <名前>@<版> / <コマンド> / node@22.17.0 npm@built-in (default)

ツールの版は変えずに入れ直すと、固定先が今の既定(22.23.4)に変わります。Volta で入れた CLI ツール11個をこの方法で入れ直しました。

volta install <パッケージ>@<今の版>

もう1つ、同じ名前のコマンドが、PATH の前の方にある別のインストール(公式のインストーラーで入れたものなど)に隠れている例がありました。この場合、Volta 側を入れ直しても、実際に動くコマンドは変わりません。type -a <コマンド名> で、同じ名前のコマンドがどこに何個あるかを確かめます。

なお、コマンドを持たないため入れ直さなかったパッケージが2個、古い Node.js に固定されたまま残っています。

残った課題

/etc/ssl/cert.pem は更新していません。macOS 標準の curl や python3 はこのファイルで検証し、失効は確かめません。確認用のページによると、このファイルは更新されていません。この端末のファイルにも、emSign ECC Root CA - G3 と AAA Certificate Services が入ったままでした。

確認用のページには、2つのルートを sudo でこのファイルから除く手順があります。私たちは、OS が管理するファイルを書き換えることになるため、まだ実施していません。macOS 標準の pip3 や、標準の python3 から作った venv の pip は、さらに別の古い束を使うと同じページは書いています(この点は確かめていません)。

記事をまとめる段階で、git も確かめました。macOS 標準の git だけでなく、Homebrew の git もテスト用のホストに接続できました。Homebrew の git の git-remote-https は、macOS の /usr/lib/libcurl.4.dylib を使っています。Homebrew で入れた道具でも、Homebrew の束を使うとは限りません。

otool -L "$(git --exec-path)/git-remote-https" | grep curl
# /usr/lib/libcurl.4.dylib

もう1つ残っているのが、各プロジェクトの package.json などで固定した Node.js の版です。既定の Node.js を上げても、プロジェクトで古い版を指定していれば、その版で動きます。社内のリポジトリとビルド環境をまとめて 22.23.4 に上げた記録は、第3回で扱います。Python 3.15 が既定になったときに uv で起きる変化は、uv 0.13の変更点で整理しています。

確認した範囲

2026年10月22日の午前、私たちの開発チームの Mac 1台(macOS 26.5.1)で、テスト用のホストへの接続、Homebrew の ca-certificates の更新、python@3.14 の入れ直しと venv 6つの起動・依存関係の確認、Volta の Node.js の更新と CLI ツール11個の入れ直しを行いました(自社の実務経験・実機作業)。同じ日の昼前に、12のテスト用のホストへの nscurl・curl 2種の接続、Python 4種と Node.js 22.19.0・22.23.4 の接続、security verify-cert、git 2種、両方の束に入っているルートを再確認しました。資料は、問題をまとめたページ、Cloudflare のブログ、Homebrew の ca-certificates の公開情報と Formula、Node.js 22.23.4 のリリースノート、man brew を確認しました(資料調査)。Mozilla のブログは開けませんでした。Chrome での遮断、/etc/ssl/cert.pem を書き換える手順、macOS 標準の pip3、Intel の Mac、Windows と Linux の端末は確認していません。

開発端末やビルド環境の点検・更新の進め方は、開発・AI・自動化のご相談からお問い合わせください。

参考資料

この記事を共有XFacebook
鈴木 翔

技術の可能性に魅了され、学生時代からプログラミングとデジタルアートの分野に深い関心を持つ

テーマに沿って読み進める脆弱性の緊急対応を記録で振り返る

この記事のテーマを、自社の次の一歩へ

自社での進め方を、具体的に。

つくりたい仕組み、既存システム、運用の条件を整理し、実現に向けた次の一歩を考えます。

  • 実現したい仕組み
  • 既存環境との接続
  • 運用の条件
開発・運用の構想を相談する

構想段階からご相談いただけます。この記事の情報を相談フォームに引き継ぎます。

最新記事をメールで受け取る