
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