
CVE-2021-22681のハードコードされた鍵の欠陥を再現し、シミュレートされたEtherNet/IP上でデバイス単位の相互TLS/CRL修正を検証する概念実証。IEC 62443-4-2マッピング付き。
6つの実行可能なスクリプト — 実際のEtherNet/IPプロトコルトラフィック(テスト1)、実際のTLS/PKIメカニズム (テスト3〜6)— チェーン全体にRockwellのソフトウェアやライセンスは一切不要。主張を文章化する前に検証するために構築されており、盲目的に信じて議論するためのものではない。
来歴: 2026-07-31に構築。ミネソタ州Braham WWTFに関する並行調査と同時に行われた — 2026年7月26〜27日のミネソタ州水道セクター協調インシデントで公に開示された4つのユーティリティのうちの1つ。そのインシデントの文脈はCISA勧告 AA26-097A(FBI/CISA/NSA/EPA/DOE/USCYBERCOM/財務省共同発行、2026-04-07発行、2026-07-22拡大)に記載されており、進行中のIRGC関連CyberAv3ngersキャンペーンを扱っている。帰属に関する注意事項(厳密に保持): いかなる機関もミネソタ州のインシデントに特化してそのグループを正式に帰属させていない — より広範な進行中のキャンペーンのみが対象。このフォルダは技術的修正側であり、意図的にインシデント調査から分離されている。
Rockwell PSIRT([email protected])およびRA Secure Mail([email protected])には 2026-07-31に連絡済み(このリポジトリと文書が公開される約30分前、ファイルタイムスタンプによる)。Rockwellのセキュリティアーキテクチャチームはリポジトリをレビューし、2026-08-03に返信した。 彼らの主張よりも強い主張に言い換えることなく、直接引用する:
Rockwell Automationは、お客様の解釈、IEC 62443-4-2マッピング、または概念実証から導き出された結論を承認または検証するものではありません。本作業をRockwell Automationによってレビュー、承認、または推奨されたものとして表現しないでください... スクリプトは、CIP SecurityやCVE-2021-22681に固有のものではなく、一般的な暗号化および認証の原理を示しています。
本作業はRockwell Automationによってレビュー、承認、または推奨されたものではありません。 彼らの
技術的特徴付け — 一般的な原理であり、CIP Security固有ではない — は、以下の「厳格な境界」表がこのリポジトリ自身の主張についてすでに示している区別と同じものである。彼らのレビューはそれに異議を唱えるのではなく、独立して確認している。CIP Securityに到達できないハードウェア上のユーティリティについては、Rockwellは自社のConverged Plantwide Ethernet(CPwE)設計および実装ガイド(PHASED_ROLLOUT.mdフェーズ1で引用)を指摘した — このプロジェクトが複製するのではなく人々を導こうとしている既存のベンダーリソース。
テスト1の調査結果 — プロトコルのデフォルト状態に認証がないこと — はEtherNet/IPに固有ではない。 Modbus TCPは、上下水道制御システムで今も最も広く展開されているプロトコルの1つであり、プロトコル仕様に認証の概念がまったくない。1979年のシリアル通信に由来し、セキュリティを考慮して設計されたことは一度もない。CISAはICS勧告全体でまさにこの欠陥を繰り返し指摘している(例: 三菱電機のMELSEC iQ-Fシリーズ: 「MODBUS/TCPは適切な認証を欠いており」、不正な読み取り/書き込み/停止を許可)。Modbus Organization自身の回答であるModbus/TCP Securityは、X.509証明書によるTLSカプセル化であり — 構造的にテスト3がここで示す修正カテゴリと同じで、単一ベンダーではなくプロトコル組織レベルで標準化されている。DNP3にはオプションのSecure Authentication拡張機能(SAv5、2012年標準化)がある。独立した分析と実装者の報告はどちらも、OTメーカー間の相互運用性ギャップと真のプロトコルの複雑さを理由に、実際にはほとんど設定されていないと説明しており — テスト1がEtherNet/IPに対して示すのと同じ露出を残している。
アーキテクチャ上のポイントを、過大評価されないよう正確に述べる: テスト3の修正 — 相互TLS、 証明書にバインドされたデバイスごとのID(CAの有効性だけでなく)、CRLによる失効 — は ICSアプリケーションプロトコルではなくトランスポート層で動作する。この原理はModbus、DNP3、または独自プロトコルの下でも同様に適用可能である。変わるのはラッパーであり、修正の形状ではない。このリポジトリはModbusまたはDNP3固有のPoCを構築または実行していない — これは公開文書からのアーキテクチャ上の一般化であり、ここにある他のすべてと同じ「実証済み vs. 情報源付き」の階層に保持されており、新しいテスト済みの主張ではない。
情報源: Modbus/TCP認証ギャップに関するCISA ICS勧告 — Industrial Cyber · Modbus/TCP Security概要 — Veridify · DNP3 SAv5/SAv6導入の課題 — Step Function I/O
開示パケットは、これらを混同しないことで成否が決まる。それぞれに異なる修正があるため:
ここで実証される修正 — デバイスごとのIDバインディング (テスト3) — はフリートキーの欠陥に対処する。
| 主張 | 階層 | 理由 | |
|---|---|---|---|
| アーキテクチャ上の原理 | 「フリート全体で共有される単一の秘密は、1つの漏洩でフリート全体が侵害される。デバイスごとのIDバインド認証がそれを閉じる」 | 実証済み | 実際に動作するコードで実証済み。チェックが必要であることを証明するネガティブコントロールを含む(単に発火するだけではない): 厳格なエンドポイントDevice Bの真正なCA有効証明書はIDで拒否される(テスト3・ケース3)が、CA有効性のみのエンドポイントでは同じ証明書が受け入れられる(ケース4 — コントロール)→ 「CA署名が有効なだけ == フリート全体のアクセス == TLSの衣を着たテスト2」。バインディングは逆方向でも成立する: 有効なフリート証明書を提示する不正サーバーはクライアントによって拒否される(ケース5)。テスト1は別途、より広範な認証なしベースラインを示す。 |
| Rockwellの特定のCIP Security実装も同様に動作する | 「実際のRockwellハードウェアでCIP Securityを有効にすると、CVE-2021-22681がまさにこの方法で修正される」 | リード、情報源付き・未検証 | これはRockwell自身の勧告(PN1550)の文言である — 「適切に展開された場合、CIP Securityはこの脆弱性を修正します... ハードコードされたキーは使用されません」 — 実際のLogixハードウェアに対して独立に確認したものではない。我々は彼らの勧告が説明する原理をテストしたのであり、彼らの正確なワイヤーレベル実装ではない。 |
2つの行を混同してはならない。原理は実証済みである。ベンダーのその特定の実装は 信頼できる(彼ら自身の表明した設計意図である)が、実際の機器に対する我々のテストは未実施である。
test1_baseline_vulnerable.py — 認証なしベースライン、ライブ実際のEtherNet/IP PLCシミュレータ(cpppo、Allen-Bradley ControlLogixをエミュレート)を起動し、
ゼロ資格情報で制御タグを読み書きする。(範囲: これはキャンペーンが依存した広範な
認証なしベースラインであり — CVE-2021-22681の特定のハードコードキーメカニズムではない。意図的に区別して保持。上記の「3つの異なる欠陥」を参照。)
python test1_baseline_vulnerable.py
test2_shared_secret_fails.py — フリートキー欠陥の形状(ナラティブブリッジであり、テストではない)2つのエンドポイントが1つの静的キーを保持。デバイスAからの資格情報が変更されずにデバイスBを開く — CVE-2021-22681の1キーですべて対応する欠陥に最も構造的に類似。しかしこれはトートロジーである: 両方のハンドラーがそのキーを受け入れるように構築されているため、失敗する実行パスが存在しない。コードが定義によって存在させる以外の何も実証しない。テスト1からテスト3へのナラティブブリッジとして保持。証拠としての重みはなく、意図的に証明の脚ではない。
python test2_shared_secret_fails.py
test3_mutual_tls_fix.py — 修正、ネガティブコントロールと双方向を含む実際のCA、2つの個別に一意なデバイス証明書 — IDは非推奨のCommonNameではなくSubjectAlternativeNameにバインド。5つのケース、すべて実行:
device-aをバインドするクライアント(実際のSANに対するcheck_hostname)によって拒否される。相互 — 両端がIDをバインド。python test3_mutual_tls_fix.py
test4_revocation.py — ライフサイクル脚: 失効実際のCA署名CRL。クライアント資格情報(engineer-1)が許可される。次にそのシリアルが
CRLに追加され、同じ依然として有効で、期限切れでなく、CA署名された資格情報が拒否される — CR 1.8 / 1.9失効条項の実証的内容。一意性(テスト3)≠ 失効可能性。これは資格情報を取り消すことができることを示す。
python test4_revocation.py
test5_rotation.py — テスト4が閉じなかったライフサイクル脚: ローテーションすでに有効なもの(v1)を保持している同じID(engineer-1)に対して置換資格情報(v2)を発行する。コントロール(ケース3): v1がv2の存在後かつv1が明示的に退役前に再度提示される → 依然として許可 — 再発行だけでは古い資格情報を退役させないことを証明。v1が明示的にCRLに追加された後(ケース4)にのみ拒否される。v2は全体を通じて影響を受けない(ケース5)— IDは移行中にアクセスを失うことはない。CR 1.8の「置換を発行し、以前のものを退役させる」は2つのアクションであり、これは両方を別々に示す。
python test5_rotation.py
test6_tamper_injection.py — CR 3.1が「構造上」としてのみ挙げた脚: 専用の整合性テストレコードレベルのリレーが実際の相互TLSクライアントとサーバーの間に位置し、5バイトのヘッダーのみを解析してTLSレコードを転送する — 暗号化されたペイロードの平文は決して見えない。コントロール: すべてのバイトが変更されずに転送 → メッセージが無傷で配信。タンパリング: ライブのApplication Dataレコードの暗号文内の1ビットを反転 → 受信TLSスタックのAEADチェックが失敗(SSLV3_ALERT_BAD_RECORD_MAC)し、接続が切断 — 破損したデータが有効なものとして配信されることはない。
どのバイトか、そしてなぜ指定するのか: TLS 1.2 AEADレコード本体は
explicit_nonce(8) ‖ ciphertext ‖ auth_tag(16) であるため、バイト0はノンスでありペイロードではない。ノンスを反転してもAEADチェックは作動する — ただし、変更されたペイロードをタグが検出するのではなく、復号を破壊することによる。CR 3.1は送信情報の不正な変更に関するものであるため、反転は暗号文の中央のバイトを対象とし、テストはその後、主張する文を正確に実証する。
python test6_tamper_injection.py
maximum_version = TLSv1_2を固定している理由は2つあり、それは可観測性に関するものでありセキュリティではない: TLS 1.2では、欠落または拒否されたクライアント証明書はハンドシェイク中に失敗するため、テストはTLS 1.3のハンドシェイク後失敗ではなく、決定的で帰属可能なエラーを得る。また、レコードのコンテンツタイプが平文で表示されたままになり、テスト6のリレーがApplication Dataレコードを識別するためにそれが必要。デバイスがサポートする最高のTLSバージョンを展開してください — 利用可能な場合はTLS 1.3。このリポジトリの何も、本番システムを1.2に制限するアドバイスとして読むべきではない。presented == KEY、identity in SAN)は定数時間ではない。ここでは悪用可能ではない — 比較される値は公開に近いID文字列であり、TLSが比較の実行前に実際の暗号認証をすでに完了している — ただし、このパターンが重要になる場所にコピーされるためフラグ付けされている。python -m venv venv
venv\Scripts\activate # または: source venv/bin/activate
pip install -r requirements.txt
62443-4-2_SL2_MAPPING.md(CR 1.2 / 1.8 / 1.9 / 1.14 / 3.1、
正直に階層化され、すべてのギャップが明示。CR 1.2、CR 1.9、およびCR 1.8の発行/検証/失効の脚は現在実証済み)。PSIRT([email protected] / [email protected])に送る前: 購入したIEC 62443-4-2:2019のコピーに対して各CRの規範テキストを検証。PHASED_ROLLOUT.md(フェーズ0止血 · 1セグメント化 · 2
補完的コントロール · 3 CIP Security/PKI(ハードウェアが許す場合)· 4運用)。小規模ユーティリティに適したサイズ。CIP Securityがハードウェアに依存することを正直に認め、フェーズ0〜2がリスク削減を担う。PHASE0_INVENTORY_WORKSHEET.md(記入可能なデバイスインベントリであり、作成する指示だけではない)、RESOURCES.md(無料のCISA / EPA / WaterISAC / AWWA支援、記憶ではなくライブで検証済み)、およびINCIDENT_RESPONSE_QUICK_REFERENCE.md(最初の60分間のカード。完全なIR計画ではないことを明示 — 運用の安全性が常に最優先)。test5_rotation.pyがCR 1.8を
「有効」から完全に実証済みに移行(ローテーション、独自のコントロール付き)。test6_tamper_injection.pyがCR 3.1を「構造上」から実証済みに移行(実際のビット反転、TLSのAEADチェックで拒否)。
両方ともここに追加される前に繰り返し実行され、不安定さがないことを確認。さらに追加:
PHASE3_CA_QUICKSTART.md(「数行のコード」のCAを、実際にテスト済みのopensslコマンドとして)およびPHASED_ROLLOUT.mdフェーズ1の具体的な許可リスト例。GLOSSARY.md、INTEGRATOR_CHECKLIST.md、PHASE3_CA_QUICKSTART.md)が参照されずに放置されるのではなく、実際に計画からリンクされた。このリストの何もまだDRAFTではない — 上記および以下のすべては公開リポジトリにプッシュされライブである。ここにあるすべての外部識別子は、トレーニングからの記憶ではなく、2026-07-31にライブソースから取得された: AA26-097A(複数ソース、WaterISAC / Tenable / SecurityWeekを含む)、開示された4つの被害者の1つとしてのBraham、CyberAv3ngers/IRGC、PN1550が実際のRockwell勧告であることを確認し、そのRow-2引用が逐語的にチェックされた(「適切に展開された場合、CIP Securityはこの脆弱性を修正します」+「ハードコードされたキーは使用されません」)、「パッチで緩和できない」が逐語的、CVSS 10.0 / CRITICAL(v3.1)、CISA追跡ICSA-21-056-03、および62443-4-2 CR 1.8(PKI)+ CR 3.1(通信整合性)が正確に確認された。
パケットの規律: 提出時にすべての識別子を一次ソースから再取得する。
勧告は再番号付け、拡大、および置き換えが行われる — AA26-097Aはすでに1回の拡大を示している — そのため
「2026-07-31に検証済み」は「提出時に検証済み」ではない。完全な正式なCRごとの62443-4-2マッピングは
現在執筆済み(62443-4-2_SL2_MAPPING.md)— 残っているのは、提出前に購入した標準コピーに対して引用された規範テキストを再取得することであり、マッピング自体を書くことではない。
l0gic — Patrick Crosby · 2026-07-31.
ハードニングログ、2026-08-03: test5_rotation.pyおよびtest6_tamper_injection.pyが追加され、2026-07-31以来未解決として名前が付けられていた2つの技術項目をクローズ — それぞれここに記載される前に3回再実行され、不安定さがないことを確認。PHASE3_CA_QUICKSTART.md(実際のテスト済みopensslコマンド)、具体的なフェーズ1ファイアウォール許可リスト例、PHASE0_INVENTORY_WORKSHEET.md、RESOURCES.md、INCIDENT_RESPONSE_QUICK_REFERENCE.md、およびModbus/DNP3の一般化も同日に追加された。
ハードニングログ、2026-08-03(続き)— 2回の独立したレビューパス、両方適用済み: セキュリティ
レッドチームがCAクイックスタートで実際のPKI脆弱性を発見し修正した(-copy_extensions copyallが悪意のある証明書要求にCA:TRUEを自己宣言させた。明示的な-extfileで修正され、意図的に悪意のある要求に対して両方向で検証済み)。また、test6をバイト0(ノンス)ではなく暗号文を具体的に反転するように修正。さらに実際のスレッドセーフティ修正と依存関係のピン留め。
別の構造監査 — フェーズをチェックリストではなく時間を通じたシステムとして読む — が以下を発見し修正:
フェーズ3の「数行」という見出しが、失効/ローテーションが単純ではないことを隠していた。CRLのフェイルオープン/フェイルクローズが決定されていなかった(現在は決定済み、デフォルトと推論付き)。証明書の期限切れが新しく名前のない停止モードだった(フェーズ4が現在テスト5自身の安全なオーバーラップの教訓を担う)。フェーズ3がフェーズ2の書き込みアラートを暗黙的に盲目にしていた(現在は明示され、代替案が提案されている)。NTPが明示されていない前提条件だった。CR 1.14が「未達成」と表現されていたが、「修正された設計には適用されない」が正確な表現。INTEGRATOR_CHECKLIST.md/GLOSSARY.mdは何もリンクしていない孤立文書だった(現在はフェーズ3とこの計画の先頭からリンク)。すべての修正は、差分を再読するだけでなく、影響を受けるテストを再実行して検証された。プッシュ済みでライブ — ロールアウト計画の全5フェーズが完了し、2回レビューされ、今日のものはローカルに残っていない。
ハードニングログ: テスト3はネガティブコントロール(ケース4、発火だけでなく必要性を証明)、逆方向ケース(ケース5、相互バインディング)、およびSANベースのID(CNではなく)を含むように強化され、その後5つのケースすべてを再実行して再検証された。テスト4(CRL失効)が追加された。テスト1/2のキャプションは3つの失敗クラスを区別し続けるように適正化された。テスト2は「テスト」からナラティブブリッジに格下げされた。失効と定数時間の範囲境界が追加された。引用チェーンは一次ソースから取得され、提出時の再取得用にスタンプされた。