
Концепт-доказательство, воспроизводящее уязвимость жёстко зашитого ключа CVE-2021-22681 и проверяющее исправление на основе взаимного TLS/CRL для каждого устройства поверх симулированного EtherNet/IP, с сопоставлением с IEC 62443-4-2.
Четыре исполняемых скрипта. Реальный трафик протокола EtherNet/IP, реальная криптография, ни одного компонента или лицензии Rockwell во всей цепочке. Создано, чтобы проверить утверждение до того, как оформить его в текст, а не защищать его на веру.
Происхождение: создано 2026-07-31, параллельно с отдельным расследованием инцидента на очистных сооружениях (WWTF) в Брахаме, штат Миннесота, — одном из четырёх публично раскрытых коммунальных предприятий в скоординированном инциденте в водном секторе Миннесоты 26–27 июля 2026 года. Контекст этого инцидента — в бюллетене CISA AA26-097A (совместный FBI/CISA/NSA/EPA/DOE/USCYBERCOM/Минфин США; выпущен 2026-04-07, расширен 2026-07-22), который охватывает продолжающуюся кампанию CyberAv3ngers, связанную с IRGC. Оговорка об атрибуции, выдержанная точно: ни одно ведомство официально не приписывало конкретно инцидент в Миннесоте этой группе — только более широкую продолжающуюся кампанию. Эта папка — сторона технического исправления, намеренно отделённая от расследования инцидента.
Пакет раскрытия выживает или погибает в зависимости от того, не смешаны ли эти вещи в одну кучу, ведь у каждого — своё исправление:
Продемонстрированное здесь исправление — привязка идентичности к каждому устройству (Test 3) — устраняет именно отказ из-за общего ключа парка.
| Утверждение | Уровень | Почему | |
|---|---|---|---|
| Архитектурный принцип | «один общий секрет на весь парк означает, что одна утечка компрометирует весь парк; аутентификация с привязкой идентичности к устройству закрывает это» | ДОКАЗАНО | продемонстрировано на реально работающем коде, включая негативный контроль, доказывающий, что проверка необходима, а не просто срабатывает: на строгой конечной точке подлинно валидный по CA сертификат устройства B отклоняется по идентичности (Test 3 · случай 3), но на конечной точке с проверкой только валидности CA тот же сертификат принимается (случай 4 — контроль) → «одна лишь валидная подпись CA == доступ ко всему парку == Test 2 в обёртке TLS». Привязка действует и в обратную сторону: мошеннический сервер, предъявляющий валидный сертификат парка, отклоняется клиентом (случай 5). Test 1 отдельно показывает более широкий базовый сценарий без аутентификации. |
| Конкретная реализация CIP Security от Rockwell ведёт себя идентично | «включение CIP Security на реальном оборудовании Rockwell устраняет CVE-2021-22681 именно так» | LEAD, из первоисточника, не проверено | это формулировка из собственного бюллетеня Rockwell (PN1550) — «When properly deployed, CIP Security remediates this vulnerability... does not make use of any hardcoded keys» — а не то, что мы независимо подтвердили на реальном оборудовании Logix. Мы проверили принцип, который описывает их бюллетень, а не их точную реализацию на уровне протокола. |
Не смешивайте две строки. Принцип доказан. Конкретная реализация этого принципа вендором правдоподобна (это заявленное самим вендором проектное намерение), но нами на реальном оборудовании не проверена.
test1_baseline_vulnerable.py — базовый сценарий без аутентификации, вживуюПоднимает реальный симулятор ПЛК EtherNet/IP (cpppo, эмулирующий Allen-Bradley ControlLogix) и читает + записывает управляющий тег с нулевыми учётными данными. (Область: это широкий базовый сценарий без аутентификации, на который опиралась кампания, — а не конкретный механизм жёстко заданного ключа из CVE-2021-22681. Намеренно разведено; см. «Три разных типа отказа» выше.)
python test1_baseline_vulnerable.py
test2_shared_secret_fails.py — форма дефекта общего ключа парка (сюжетный мост, а не тест)Две конечные точки хранят один статический ключ; учётные данные с устройства A без изменений открывают устройство B — ближайший структурный аналог дефекта «один ключ для всех» из CVE-2021-22681. Но это тавтология: оба обработчика сконструированы так, чтобы принимать этот ключ, поэтому не существует пути выполнения, на котором он мог бы отказать. Он не демонстрирует ничего, чего код не определял бы самим своим существованием. Оставлен как сюжетный мост от Test 1 к Test 3; он не несёт никакой доказательной нагрузки и намеренно не является самостоятельной опорой доказательства.
python test2_shared_secret_fails.py
test3_mutual_tls_fix.py — исправление, с негативным контролем и в обоих направленияхРеальный CA, два индивидуально-уникальных сертификата устройств — идентичность привязана в SubjectAlternativeName, а не в устаревшем CommonName. Пять случаев, все исполняются:
device-a (check_hostname против реального SAN). Взаимно — оба конца привязывают идентичность.python test3_mutual_tls_fix.py
test4_revocation.py — этап жизненного цикла: отзывНастоящий подписанный CA CRL. Учётные данные клиента (engineer-1) предоставляются; затем их серийный номер добавляется в CRL, и те же всё ещё действительные, не истёкшие, подписанные CA учётные данные отклоняются — эмпирическое содержание пункта об отзыве из CR 1.8 / 1.9. Уникальность (Test 3) ≠ возможность отзыва; это показывает, что учётные данные можно забрать обратно.
python test4_revocation.py
test4_revocation.py); ротация — ещё нет. Уникальность каждого устройства (Test 3) — не то же самое, что возможность отзыва; Test 4 закрывает этот пробел — всё ещё действительные, не истёкшие, подписанные CA учётные данные предоставляются до отзыва и отклоняются после, исключительно потому, что подписанный CA CRL теперь содержит их серийный номер. Ротация (перевыпуск заменяющего сертификата и вывод старого из обращения) тесно связана и поддерживается той же PKI, но здесь отдельно не демонстрируется — поэтому «привязка идентичности к устройству» не должна молча расширяться до «ротация решена».presented == KEY, identity in SAN) не являются constant-time. Здесь это не эксплуатируемо — сравниваемые значения — это почти публичные строки идентичности, а TLS уже выполнил настоящую криптографическую аутентификацию до выполнения сравнения, — но отмечено, потому что этот паттерн копируют в места, где это действительно важно.python -m venv venv
venv\Scripts\activate # or: 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]): сверить нормативный текст каждого CR с приобретённой копией IEC 62443-4-2:2019.PHASED_ROLLOUT.md (Этап 0: остановить кровотечение · 1: сегментация · 2: компенсирующие меры · 3: CIP Security/PKI, если позволяет оборудование · 4: эксплуатация). Подогнан под небольшое коммунальное предприятие; честно признаёт, что CIP Security ограничен требованиями к оборудованию, поэтому снижение риска несут Этапы 0–2 в любом случае.Каждый внешний идентификатор здесь был извлечён из живого источника 2026-07-31, а не воспроизведён по памяти из обучающих данных: AA26-097A (мультиисточник, вкл. WaterISAC / Tenable / SecurityWeek), Брахам как одна из четырёх раскрытых жертв, CyberAv3ngers/IRGC, PN1550 подтверждён как настоящий бюллетень Rockwell, цитата из строки 2 сверена дословно («When properly deployed, CIP Security remediates this vulnerability» + «does not make use of any hardcoded keys»), «cannot be mitigated with a patch» дословно, CVSS 10.0 / CRITICAL (v3.1), отслеживание CISA ICSA-21-056-03, а также 62443-4-2 CR 1.8 (PKI) + CR 3.1 (целостность коммуникаций) подтверждены точно.
Дисциплина для пакета: перепроверить каждый идентификатор по первоисточникам в момент подачи. Бюллетени перенумеровывают, расширяют и заменяют — AA26-097A уже демонстрирует одно расширение, — так что «проверено 2026-07-31» — это не «проверено при подаче». Полная формальная карта соответствия 62443-4-2 по каждому CR уже написана (62443-4-2_SL2_MAPPING.md) — осталось перед подачей перепроверить её цитируемый нормативный текст по приобретённой копии стандарта, а не писать саму карту.
l0gic — Patrick Crosby · 2026-07-31.
Журнал усиления: Test 3 усилен: добавлены негативный контроль (случай 4, доказывающий необходимость, а не просто срабатывание), обратное направление (случай 5, взаимная привязка) и идентичность на основе SAN (а не CN); затем все пять случаев перепроверены повторным запуском; добавлен Test 4 (отзыв через CRL). Подписи к Test 1/2 откалиброваны, чтобы три класса отказов оставались различимыми; Test 2 понижен с «теста» до сюжетного моста; добавлены границы области действия для отзыва и constant-time. Цепочка цитирования извлечена из первоисточников и помечена для перепроверки при подаче.