eMudhraの証明書:攻撃者に必要なものと、それを止める対策
eMudhraの鍵で証明書を偽造できますが、攻撃には別の条件も必要です:5つの攻撃ベクトルとその止め方。
この記事は教育目的であり、正式なスマートコントラクト監査、法的助言、金融助言、またはプロトコル固有のセキュリティレビューに代わるものではありません。
eMudhraの8つの中間CA(TLS用が6つ、コード署名用が2つ)の秘密鍵は、証明書そのものから復元できます。TLS用の6つを使えば、誰でも任意のドメインの証明書に署名でき、更新されていないクライアントはそれを有効な証明書として受け入れます。コード署名用の2つでは、Windowsが署名済みとして受け入れるソフトウェアに署名できました。ただし、それだけではトラフィックを傍受できません。接続の途中に入り込む必要もあります。
侵害されたCAや中間CAは、それだけでは傍受もリモートコード実行も可能にしません。傍受するには、ネットワーク経路、DNS、プロキシ、またはパッケージのダウンロード元をさらに制御する必要があり、しかもクライアントが影響を受けたチェーンを信頼し続けている必要があります。ルート証明書を更新するか中間CAをブロックすれば、その信頼はなくなります。ハッシュ、署名、範囲チェックはパッケージやデータの改ざんを止めますが、トークンやトラフィックの読み取りは止めません。

攻撃が成り立つ条件
これらの鍵で接続を傍受または改ざんするには、次の2つが同時に成り立つ必要があります。
- クライアントが影響を受けたチェーンを信頼し続けており、Certificate Transparency(CT)を要求していない。
- 攻撃者がクライアントとサーバーの間の経路上の一点を制御している:ネットワーク、DNSの名前解決、プロキシ、またはパッケージの配布ポイント。
トークンやリクエストを読むにはこれで十分です。改ざんしたパッケージやデータを紛れ込ませるには、3つ目の条件が必要です:固定したハッシュ、データへの署名、範囲チェックなど、ほかのコントロールが変更を検出しないことです。
1つ目の条件には補足があります。TLS用の6つの中間CAはemSign Root TLS CA - G3に、コード署名用の2つはemSign Root CS CA - G3につながっています。この2つはいずれもemSign ECC Root CA - G3の下にあり、トラストストアに入っているのはこのルート証明書です。さらに、各中間CAにはSectigoのルート証明書であるAAA Certificate Servicesのクロス署名があります。トラストストアがTLS用にAAAを受け入れ続けていれば、emSign ECC Root CA - G3を削除するだけでは不十分です。修正されていないOpenJDKのcacertsや古いバージョンのcertifiがこれに当たります。両方の経路を断つのは更新です。修正済みのバージョンは中間CAをブロックするからです。
Cloudflareは次のように説明しています:鍵が弱いため、攻撃者は任意のドメインの偽の証明書を作成し、それを使ってサーバーになりすまし、トラフィックを傍受または改ざんできる。これはあらゆる中間者攻撃の仕組みと同じです。違いは、偽の証明書が更新されていないクライアントの検証を通ってしまうことです。
GoogleとMozillaのDennis Jacksonは、これらの中間CAの証明書はCTログに存在しないため、CTを要求するブラウザは攻撃されえなかったという点で一致しています。リスクがあるのはCTを要求しないクライアントです:curl、各言語のランタイム、pip、uv、コンテナイメージ。Dockerは、10月22日のDocker Desktop 4.96.3のリリースノートで、自社のCLIとデーモンについてそう述べています:ネットワークにアクセスできる攻撃者は、これらの証明書を使ってTLS接続を傍受できた可能性がある。
CA自身によると、これらの中間CAは2025年4月30日に作成され、8つのうち7つは本番環境で一度も使われず、使われた1つも証明書を発行していません。Jacksonは、CTを要求しないクライアントに対して誰かがこの欠陥を悪用したかどうかはわからないと書いています。悪用の証拠は公表されていませんが、外部から否定する方法もありません。そのため、こうしたクライアントを通ったトラフィックを見直すことには意味があります。

ベクトルA:外部APIへのなりすまし
これらの鍵を使えば任意のドメインの有効なTLS証明書を偽造できますが、傍受に使えるのは、攻撃者がネットワーク経路(Wi-Fi、プロキシ、DNS、BGP)も制御し、かつクライアントがまだチェーンを信頼していてCertificate Transparencyを要求していない場合だけです。Chrome、Firefox、Edge、Safariはすでに中間CAをブロックし、CTを要求しています。
バックエンド、CIジョブ、エージェントが、ヘッダーにトークンを付けてHTTPSで外部APIを呼び出すとします。攻撃者が傍受ポイントを制御し、クライアントが偽のチェーンを受け入れれば、その呼び出しはプロバイダーではなく攻撃者に届きます。攻撃者はトークン、リクエスト、レスポンスを読むことができ、書き換えることもできます。
ここではハッシュは役に立ちません。トークンが傍受された接続を通るなら、攻撃者はそれを読めます。このベクトルを断つのは、クライアント側で最新のルート証明書かブロック済みの中間CA、ピンニング、またはmTLSです。
ベクトルB:HTTPSでダウンロードするアーティファクト
攻撃者がHTTPSのダウンロード(タグ指定のdocker pull、pip、npm、curl | sh)を傍受できれば、改ざんしたアーティファクトを配信できます。ただし、それは別の手段で完全性を検証するものが何もない場合に限られます。固定したイメージのダイジェスト、npmのロックファイルのintegrity、pip --require-hashes、cosign/Sigstoreによる検証、GPGで署名されたapt/rpmのリポジトリは、変更を検出します。
ビルドやデプロイの間には、イメージ、パッケージ、スクリプトがダウンロードされます。攻撃者が配布ポイントを制御し、クライアントが偽のチェーンを受け入れれば、差し替えられたアーティファクトがビルドや本番環境で実行され、手の届くシークレットを読むことができます。ダウンロード方法ごとに防御策があるか、ないかが分かれます。
- 「docker pull image:tag」は傍受されたチャネルでタグを解決します。「image@sha256:…」はコンテンツでアドレス指定されます。
- npmはロックファイルにすでに固定されているものについてintegrityを確認しますが、最初の解決や新しいパッケージの「npm install」は保護しません。
- pipとuvは、ハッシュを要求しない限り、配信されたものをそのままインストールします。
- 「curl | sh」には何の防御もありません。届いたものがそのまま実行されます。
- aptとrpmは、ダウンロードがTLSを使わなくても、マシンにすでにある鍵でGPG署名付きのメタデータを検証します。
Pythonには、手順の順序を変える細かな点があります。Christian HeimesはPython Discourseのこのインシデントに関するスレッドで、pipはシステムのルート証明書に加えて内蔵のcertifiのルート証明書を常に読み込み、それを無効にするサポートされた方法はないと説明しています。システムを更新するだけでは足りず、pipを更新する必要があります。
ベクトルC:判断に使われるデータ
あるサービスが価格、残高、リスクパラメーターをHTTPSで取得し、それをもとに自動で判断を下すとします。経路上の攻撃者が偽の証明書でプロバイダーになりすませば、改ざんしたデータを返せます。クライアントは有効に見えるチャネルでそれを受け取り、誤りは担保、清算、評価、ポジションクローズの判断に表れます。
ここではTLSは一つの層にすぎません。チャネルが破られても機能するコントロールは、データそのものにあります。
- 署名付きフィード:プロバイダーがデータに署名し、クライアントはサーバー証明書とは関係なく、事前に知っている鍵で署名を検証する。
- 独立したソース:同じ値を複数のプロバイダーやオラクルから取得して比較し、食い違う場合は動かない。
- 上限・下限:想定範囲外の値や、最後に受け入れた値から急激に変化した値を拒否する。
- 鮮度:タイムスタンプを必須にし、古いデータや重複したデータを破棄する。
- ベクトルAやBと同じく、クライアントのルート証明書を最新にしておく。
これらのチェックのいずれかが失敗したときは、疑わしいデータで動くのではなく、自動化を止めて人に判断を委ねるのが賢明です。
ベクトルD:Windowsでのコード署名
8つの中間CAのうち2つ(EM OV CS CA - G3BとG3C)はコード署名用のCA(EKU codeSigning)で、WindowsがCode Signing用に信頼するルート証明書(emSign Root CS CA - G3とAAA Certificate Services)につながっています。その鍵を使えば、Microsoftが2026年10月20日にブロックするまで、Windowsが有効に署名されたものとして受け入れるソフトウェアに署名できました。
Mozillaのインシデント報告(Bugzilla 2083449)は次のように述べています:証明書のうち6つはTLS用で、残りの2つはコード署名用であり、Microsoftに信頼されている。このベクトルはネットワーク経路上にいる必要はありませんが、リモート実行にもつながりません。誰かが署名済みのバイナリをダウンロードして実行する必要があります。これらの中間CAで署名されたバイナリについての公開された記録はありません。
ベクトルE:署名付きペイロードとクライアント証明書

TLS用の6つの中間CAの鍵があれば、攻撃者は好きな名前で自分用の新しい証明書を発行し、それで改ざんしたペイロードに署名したり、mTLSのクライアント証明書として提示したりできます。それを受け入れるのは、期待する鍵、証明書、CAを固定せずに、公開ルートストア(certifi、システムのストア、Javaのcacerts)につながる署名やクライアント証明書なら何でも受け入れるバックエンドだけです。よく使われるペイロード署名の方式(HMAC、固定した公開鍵、「kid」によるJWKS、プライベートCA)は影響を受けません。
攻撃者は正規の署名者の鍵も証明書も手に入れられず、既存の署名の有効性も変わりません。攻撃者が得るのは自分の証明書で、チェーンが公開ルート証明書に届くかどうかだけを確認するベリファイアに対しては有効なものとして通ります。証明書の名前を確認しても防げません。名前は攻撃者が自由に決められるからです。
これが起こりうるケースは具体的です。
- x5c ヘッダーに自身の証明書を含み、公開ストアに対して検証されるJWSやJWTのトークン。RFC 7515はチェーンの検証を義務づけていますが、それで誰が署名したかはわかりません。鍵は、その出所を認証できるソースから取得する必要があります。
- パラメーターなしでsignxmlで検証されるXML署名(SAML、WS-Security)。そのドキュメントは、この場合システムのCAストアが受け入れる証明書で作られた署名なら何でも信頼すると警告しています。
- 任意の公開CAのクライアント証明書を受け入れ、名前で認可するmTLSサーバー。TLS用の6つの中間CAはクライアント証明書を発行できます。
Pythonは、署名の検証のためにこれらの証明書をデフォルトで信頼することはありません。標準ライブラリはペイロードの署名を検証せず、cryptographyではストアとベリファイアを明示的に構築する必要があります。さらに、改ざんしたペイロードはバックエンドに届かなければなりません。エンドポイントが有効な署名者なら誰からでも直接の送信を受け付けるか、ベクトルAのように攻撃者が経路上にいるかのどちらかです。
もたらさないもの:リモートコード実行
直接のリモートコード実行はもたらしません。偽造した証明書でできるのはTLSサーバーへのなりすましだけで、攻撃者のコードが実行されるのは、被害者が改ざんされたアーティファクトをインストールして実行した場合(ベクトルB)か、クライアントに別の脆弱性がある場合だけです。
Mozilla、Google、Cloudflareのいずれも、アドバイザリーでコード実行には触れていません。述べているのは、サーバーへのなりすましとトラフィックの傍受や改ざんです。
対策
macOSでのツールごとの詳しい手順は、末尾にリンクした修正手順の記事にあります。ここでは、Linuxでそのまま実行したコマンドとともに、ベクトルごとの最低限の対策を示します。
ベクトルAへの対策:トラストストア
最初の一歩は、どのバージョンを使っているかを知ることです。DebianやUbuntuのマシンやイメージでは次のとおりです。
node --version
python3 -m pip list 2>/dev/null | grep -iE '^(pip|certifi|uv) '
dpkg -s ca-certificates | grep '^Version'一次情報源が示す最低バージョン:
- Node:ブランチに応じて24.22.1、22.23.4、26.12.1(10月21日のNodeのセキュリティアドバイザリー)。
- certifi 2026.10.19、pip 26.2.2、uv 0.13.4(Python DiscourseでのSeth Larsonのお知らせ)。
- ca-certificates:利用しているディストリビューションがこの変更を含めて公開するバージョン。それが出るまでの間、Jacksonのページは影響を受けたルート証明書を信頼しないようにする手順を示しつつ、一般ユーザー向けではないと注意しています。
- Docker Desktop 4.96.3以降。リリースノートには、これらの証明書への信頼を取り除くとあります。
さらに、次のgrepは、Python、Node、Go、curlで検証を無効にするよくあるパターンをリポジトリ内から見つけます。証明書エラーへの対処として検証を無効にするのは決して正解ではありません。検証しないクライアントは、これらの中間CAの証明書に限らず、どんな証明書でも受け入れてしまいます。
grep -rnE --exclude-dir={.git,node_modules,.venv} \
'verify *= *False|CERT_NONE|rejectUnauthorized: *false|NODE_TLS_REJECT_UNAUTHORIZED|InsecureSkipVerify: *true|curl +(-k|--insecure)' .見つかった箇所は一つずつ読む必要があります。テストやローカルツールの中にあるものもあるでしょう。重要なのは、外部APIを呼び出すコードの中にあるものです。
失いうるものを減らす対策がさらに2つあります。各サービスの外向きのネットワーク通信を必要な宛先に限定すれば、傍受されうる接続が減ります。そして、更新されていないクライアントから自分で管理していないネットワークを通ったトークンをローテーションすれば、過去にさかのぼって露出の窓を閉じられます。
Java:最も複雑なケース
更新されていないJDKはeMudhraの証明書をブロックしません。emSign ECC Root CA - G3と、AAA Certificate Servicesのクロス署名を通じて、これらの中間CAを信頼しています。しかも、実際にはそうでないのに、すべて最新だと思い込みやすいのです。10月21日のOpenJDKのアドバイザリー(CVE-2026-91403)がその理由を説明しています。
- アーカイブとしてダウンロードし、個別にインストールしたOpenJDKのランタイムは独自のストアを持ち、侵害された証明書を含んでいる可能性が高い。更新が必要です。
- Arch Linux、Debian、Devuan、Fedora、Gentoo、Oracle Linux、Red Hat Enterprise Linux、UbuntuのOpenJDKパッケージはOSの証明書を使うため、OSの証明書を更新すれば足ります。FreeBSDとHomebrewのものは直接更新する必要があります。
- 複数のランタイムがインストールされている場合は、すべてで同じ手順を繰り返す必要があります。
- Javaを含むコンテナイメージは独自のJREと独自のストアを持っています。ホストを更新してもそれらは変わらないため、修正済みのJDKで再ビルドする必要があります。
この修正はOpenJDKではJDK-8395142、OracleではJDK-8395555と呼ばれ、ルート証明書は削除しません。8つの中間CAとその8つのAAAクロス署名を、JDKのブロック済み証明書のリストに追加します。このリストがlib/securityのblocked.certsファイルで、Java 8ではblacklisted.certsです。emSign ECC Root CA - G3とAAA Certificate ServicesはOpenJDKのcacertsに残っています。そのため、前者だけを削除しても不十分で、AAAの削除は推奨されません。このルート証明書にまだつながっているサービスへの接続が切れるおそれがあり、Jacksonも、これを信頼しないようにするのは一般ユーザー向けではないと注意しています。
10月21日より前のJDKは、これらの中間CAをブロックしません。修正済みのバージョンに更新し、それができない場合は、lib/security/blocked.certsをOracleが https://ops.java/emudhra で公開しているもの(Java 8ではblacklisted.certs)に置き換え、Javaアプリケーションを再起動してください。
リリースノートによる、またはバイナリで確認した、すでにブロックを含むバージョン:
- Oracle JDK 27.0.1.0.2、25.0.5.0.2、21.0.13.0.2、17.0.21.0.2、11.0.33.0.2、8u511 b32(1.8.0_511-b32)。10月21日に公開。OpenJDKはjdk.java.netで27.0.1.0.2を公開しています。
- Amazon Corretto 25.0.5.0.9、21.0.13.0.10、8.512.08.1。
- Homebrewの現在のフォーミュラopenjdk、openjdk@21、openjdk@17。
10月23日時点で修正がないもの:Homebrewのopenjdk@11とopenjdk@8、Azul Zulu、Red Hat Enterprise LinuxのOpenJDKパッケージ、Temurin 8と11。Oracle JDK 8では、10月20日に公開された8u511(1.8.0_511-b09)は引き続き影響を受けます。「java -version」の出力に「b32」がなければ更新してください。Debian、UbuntuのJava 8には、確認済みの修正バージョンはありません。Temurin 21と25はブロックを含みませんが、emSign ECC Root CA - G3もAAA Certificate Servicesも同梱していないため、このチェーンを拒否します。
ops.javaはこのアドバイザリーのためのOracleの公式ドメインですが、10月23日には403エラーを返していたため、それだけに頼らないほうがよいでしょう。主な手段は引き続き更新です。末尾にリンクしたmacOSの修正手順の記事に詳しい手順があります:ダウンロード、ファイルの形式を確認し、既存のエントリーを残し、コピーを保存するスクリプト、そしてjshellでの検証です。keytool -deleteでルート証明書を削除するのは主な手段ではありません。
どこを見ればよいかを知るために、次のコマンドはLinuxマシンやコンテナで個別にインストールされたランタイムのストアを一覧表示します。
find / -type f -name cacerts -path '*/lib/security/*' 2>/dev/nullそれぞれについて、次のコマンドはそのcacertsに2つのルート証明書のいずれかが含まれているかを示します。エイリアスはJDKをビルドした側によって変わるため、Jacksonが公開しているリストから取ったSHA-256フィンガープリントで検索します。JAVA_HOMEは確認するJDKのフォルダーに合わせてください。
"$JAVA_HOME/bin/keytool" -list -v -cacerts -storepass changeit \
| awk '/^Alias name:/ {alias=$0}
/SHA256: 86:A1:EC:BA:08:9C:4A:8D:3B:BE:27:34:C6:12:BA:34:1D:81:3E:04:3C:F9:E8:A8:62:CD:5C:57:A3:6B:BE:6B/ {print alias, "-> emSign ECC Root CA - G3"}
/SHA256: D7:A7:A0:FB:5D:7E:27:31:D7:71:E9:48:4E:BC:DE:F7:1D:5F:0C:3E:0A:29:48:78:2B:C8:3E:E0:EA:69:9E:F4/ {print alias, "-> AAA Certificate Services"}'さらに次のコマンドは、同じリストから取った16個のフィンガープリントのうちいくつがblocked.certsにあるかを数えます。ファイルの1行目は「Algorithm=SHA-256」で、数は16でなければなりません。それより少なければ、ブロックがないか不完全です。ダウンロードしたりコピーしたりしたblocked.certsをインストール前に確認するのにも使えます。Java 8ではパスは "$JAVA_HOME/jre/lib/security/blacklisted.certs" です。
F="$JAVA_HOME/lib/security/blocked.certs"
head -1 "$F"
cat > /tmp/emudhra16.txt <<'L'
0164D7CEAE5BB909C272AF151FF44B1177AE9B47D9424B94F1D9558C30934D19
42C4ACCD12C4F59597925E1A08A7A90102608543809308D50EC427E118BA1493
432EC25F0D28DE9006D97BF752F17BBB2CD41F669933FCB072D9A1148D0261F3
45A883F349AEDFD5F3A649B5498FFB121712BA8B448F3F2A91DBC5A766FCDDBE
4B2460A466F95FD40113BCDCCA2CEB29B3B093921DE45474DA7DF48874C630B1
579480AEA166290BD3C643589E795FC0D0E54E86505D180F1DFF99EFD3199512
5F1DED4DD590AF74631290CF073B122A2AAC6A1FA479778A0BD43579BD832699
62B7F9868263F72F6911FC78CEAF72C1979158B7368BF648683BB2AA39B02E72
7485B51C0ED192878FA1D2F948FB529AE1842CC5086C4B67F7BE5B2039DE9489
84112972B8E59D14560AE7E7119654474AAD119990CF3722BFC1E3B91615E1FD
98B896625C0C230ADEEC3EB2CF6AAD5001ECF9156A68644E82374CE5833BC25B
9E39DE2881273ACC225A149C72BA3E0F51424963669AB633EE1DC7DF5DB067B4
A496E23AC0CAB8155FC9B117B97EAE368EFCBB9041AEC8804D5A308D2D01C2D8
CB6E15165AC802E9B3F993F16C25D41F4E81C21EBDE71AE1B012AC00BF9C7C3F
E7568FE6E12331F4FD7DE61DA4C7559D0F8EB192A218D4741A3BA75184333838
FD9D5C2DBBD4995AB69B8D50C439D9527B00529199C2FD01B2174EB58F07021F
L
grep -c -x -F -f /tmp/emudhra16.txt "$F"結果の読み方:
- cacertsにどちらのルート証明書もない:そのJDKは、ブロックはしていなくてもチェーンを信頼しません。これらのコマンドのテストに使ったTemurin 21.0.13と、Temurin 25.0.5がこのケースです。一方、それぞれのブランチで最新の公開バージョンであるTemurin 8u504と11.0.32.1には、emSign ECC Root CA - G3が含まれています。
- cacertsにルート証明書があり、blocked.certsに16個ある:ブロックは適用されています。
- cacertsにルート証明書があり、blocked.certsが16個未満:そのJDKはチェーンを受け入れます。更新するかOracleのblocked.certsを適用し、Javaアプリケーションを再起動する必要があります。OpenJDKのアドバイザリーは、27.0.1、25.0.5、21.0.13、17.0.21、11.0.32.1、8u504以前を影響を受けるバージョンとしています。
- DebianやUbuntuのJDK:ストアは /etc/ssl/certs/java/cacerts で、ca-certificates-javaパッケージが管理しています。OSの証明書を更新したあと、Jacksonのページはrootで「touch /var/lib/ca-certificates-java/fresh」と「dpkg-reconfigure ca-certificates-java」を実行して再生成するよう示しています。
結果を検証するには、修正手順のガイドと同じjshellのテストを使います。これらの中間CAのチェーンを返すJacksonのテストサーバーへの接続を試みます。
"$JAVA_HOME/bin/jshell" <<'JAVA'
import java.net.*;
import javax.net.ssl.*;
var url = URI.create("https://dv-g3b-emudhra.we-have-a-problem.com/").toURL();
try {
var c = (HttpsURLConnection) url.openConnection();
c.setConnectTimeout(10000);
c.setReadTimeout(10000);
System.out.println("WARNING: TLS ACCEPTED: " + c.getResponseCode());
} catch (SSLHandshakeException e) {
System.out.println("TLS REJECTED: " + e.getMessage());
} catch (Exception e) {
System.out.println("INCONCLUSIVE: " + e);
}
/exit
JAVAブロックが適用されていれば、出力は「TLS REJECTED」で、「Untrusted certificate: CN=EM DV TLS CA - G3B」が含まれるはずです。ルート証明書を同梱していないTemurin 21と25では、拒否は「PKIX path building failed」として表示されます。テストでは、cacertsに2つのルート証明書があり、元のblocked.certsのままのJDKは「WARNING: TLS ACCEPTED: 200」を返しました。
更新もファイルの置き換えもできない場合、OpenJDKのアドバイザリーは、「-Dcom.sun.net.ssl.checkRevocation=true -Dcom.sun.security.enableCRLDP=true」で失効確認を有効にすること、そしてJDK 17以降では「jarsigner -verify -revcheck -strict」で署名済みJARを検証することも提案しています。
ベクトルBへの対策:ダウンロードしたものの完全性
ダウンロードしたものは、攻撃者が制御できないものと照合して確認する必要があります:事前に固定したハッシュやダイジェスト、または既知の鍵による署名です。ツールごとに見ていきます。
- イメージ:タグだけでなく、DockerfileのFROMとデプロイでダイジェスト(「image@sha256:…」)で参照する。そして、Pythonのお知らせのとおり、キャッシュなしで再ビルドする:「docker build --pull --no-cache」。
- npm:バージョン管理しているロックファイルから「npm ci」でインストールする。ロックファイルのintegrityはすでに固定されているものを保護します。新しいパッケージは、更新済みのネットワークとクライアントから追加する必要があります。
- イメージとアーティファクトの署名:デプロイ前にcosign/Sigstoreで、期待するアイデンティティを要求して検証する。どんな署名でも受け入れる検証では何も止められません。
- aptとrpm:署名のないリポジトリをインストールしたり、エラーを回避するために信頼済みとしてマークしたりしない。
- スクリプト:「curl | sh」を使わない。スクリプトをダウンロードし、プロジェクトが公開していればハッシュや署名を確認してから実行する。
Pythonでは、pipのハッシュチェックモードを使います。--require-hashes を付けると、pipはすべてのパッケージにハッシュを要求し、ダウンロードしたものが一致しなければ停止します。ハッシュ付きのファイルはuvで生成できます。
uv --version
mkdir -p /tmp/emudhra-uv && cd /tmp/emudhra-uv
echo "certifi>=2026.10.19" > requirements.in
uv pip compile requirements.in --generate-hashes -o requirements.txt
grep -A1 "^certifi" requirements.txtハッシュは、すでに更新したツールで、自分が管理するネットワークから生成してください。傍受されたダウンロードから計算したハッシュは、間違ったパッケージを固定してしまいます。
ハッシュ付きのインストールは次のようになります。
python3 -m venv /tmp/emudhra-hash && . /tmp/emudhra-hash/bin/activate
cd /tmp/emudhra-hash
cat > requirements.txt <<'REQ'
certifi==2026.10.19 \
--hash=sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b
REQ
python3 -m pip install --require-hashes -r requirements.txtダウンロードしたパッケージが想定どおりでなければ、pipはインストール前に停止します。わざとハッシュを変えた場合の出力は次のとおりです。
ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE.
certifi==2026.10.19 from https://files.pythonhosted.org/packages/.../certifi-2026.10.19-py3-none-any.whl
Expected sha256 00d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b
Got 42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1bビルドのシークレットは、ビルド引数や環境変数に入れるべきではありません。Dockerのドキュメントは、それらはこの用途に適していないとして、シークレットのマウントを推奨しています。
ベクトルDへの対策:Windowsでのコード署名
Jacksonのページによると、MicrosoftはWindowsの信頼された証明書リストの自動更新を通じて、これらの中間CAのブロックを配信しています。まず、各マシンがその更新を受け取れるようにすることです。
有効な署名があるだけでは、バイナリを信頼する理由になりません。ダウンロードしたソフトウェアを実行する前に、誰が署名したかを確認するのがよいでしょう。PowerShellでは、Get-AuthenticodeSignatureが署名者の証明書を表示します。チェーンがEM OV CS CA - G3BまたはEM OV CS CA - G3Cを通るバイナリは実行すべきではありません。
ベクトルEへの対策:信頼する相手を固定する
防御策は、署名者のアイデンティティの判断を公開ストアに任せないことです。
- 帯域外で共有した固定の鍵や証明書、HMAC、または設定済みの発行元のJWKS(鍵は「kid」で解決)で検証する。
- トークンやメッセージ自体に含まれる証明書を検証用の鍵として使わない。
- signxmlでは、期待する証明書を x509_cert= で、または自分のCAを ca_pem_file= で渡す。cert_subject_name は名前を確認するだけです。
- mTLSでは、プライベートCAでクライアント証明書を発行し、サーバーにはそのCAだけを読み込ませる。
- チェーンを検証するときは用途(EKU)を要求する。
- 業務上の検証はサーバー側に残す:上限・下限、ロールによる認可、照合。
今週確認すること
- インターネットに接続する各クライアントが、どのランタイムとどのトラストストアを使っているか。CIジョブやエージェントも含む。
- 各マシンと各イメージにJDKがいくつあるか。emSign ECC Root CA - G3またはAAA Certificate Servicesを含むものは、blocked.certsにすでに16個のフィンガープリントがあるか、そのあとアプリケーションを再起動したか。
- 各イメージのpip、uv、certifiがPythonのお知らせの最低バージョンになっているか、イメージをキャッシュなしで再ビルドしたか。
- イメージをダイジェストで参照しているか、npmがロックファイルからインストールしているか、本番のrequirementsがハッシュを使っているか。
- TLSの検証を無効にしているコードがないか。
- どのトークンが更新されていないクライアントを通ったか、ローテーションする価値があるか。
- Windowsのマシンが証明書の自動更新を受け取っているか。
- どの自動判断が、署名も、2つ目のソースも、上限・下限もない外部データに依存しているか。
- どの署名やクライアント証明書のベリファイアが、公開ストアに届くチェーンなら何でも受け入れているか。
TLSのルート証明書が最新であるか、8つの中間CAがブロックされていれば、偽造した証明書は通用しなくなります。ハッシュと署名は、ダウンロードするものに2つ目の防壁を加えます。
自分のサービスやビルドでこれらの確認について相談したい場合は、お問い合わせページからご連絡ください。メッセージでは、この記事と想定しているサービスやパイプラインに触れ、シークレット、トークン、機密の運用情報は含めないでください。
参考資料
2026年10月23日に参照した資料。Linux(Debian)で実行し、出力を保存したもの:バージョンの棚卸し、grep、uvでのハッシュ生成、ハッシュ付きのインストール(正しい場合と変えた場合)、そしてTemurinのJDKとルート証明書を追加したコピーで、元のblocked.certsと16個のフィンガープリントを入れたものの両方に対して実行したJavaのコマンド(Jacksonのテストサーバーへのjshellでの接続を含む)。実行していないもの:docker、npm、cosign、PowerShell、そしてmacOSやWindowsでのすべての操作。
- Mozilla Bugzilla 2083449:インシデント報告
- Python Discourse:Pythonユーザー向けの情報
- Cloudflare Blog、2026/10/20
- Google Security Blog、2026/10/20
- Mozilla Security Blog、2026/10/20
- Dennis Jackson:インシデントの概要
- Dennis Jackson:影響を受けた中間CA
- Dennis Jackson:中間CAのルート証明書
- CAによるインシデントに関する声明
- Node.js:2026年10月のセキュリティリリース
- OpenJDK:2026/10/21の脆弱性アドバイザリー
- Oracle Security Alert CVE-2026-91403
- Oracle JDK 25.0.5.0.2:リリースノート
- Oracle JDK 8u511 b32:リリースノート
- Oracle JDK 8u511:リリースノート
- jdk.java.netのOpenJDK 27.0.1.0.2
- Amazon Corretto 25:CHANGELOG
- Amazon Corretto 8:CHANGELOG
- Homebrew:openjdkのフォーミュラ
- OpenJDK:cacerts
- Docker Desktopのリリースノート、4.96.3
- RFC 7515:JSON Web Signature
- signxml:信頼の確立
- cryptography:X.509の検証
- pip:ハッシュチェックモード
- uv:CLIリファレンス
- Docker:build secrets
- Docker:build variables


