
Концепт-доказательство, воспроизводящее уязвимость жёстко зашитого ключа CVE-2021-22681 и проверяющее исправление на основе взаимного TLS/CRL для каждого устройства поверх симулированного EtherNet/IP, с сопоставлением с IEC 62443-4-2.
Шесть исполняемых скриптов — реальный трафик протокола EtherNet/IP (Тест 1), реальная механика TLS/PKI (Тесты 3–6) — без единого компонента Rockwell или лицензий где-либо в цепочке. Создано для проверки утверждения до его оформления, а не для защиты его на веру.
Происхождение: создано 2026-07-31, параллельно с расследованием инцидента на водоочистной станции в Брахаме, штат Миннесота — одной из четырёх объектов коммунального хозяйства, публично раскрытых в скоординированном инциденте в водном секторе Миннесоты 26–27 июля 2026 года. Контекст этого инцидента описан в консультативном бюллетене CISA AA26-097A (совместный FBI/CISA/NSA/EPA/DOE/USCYBERCOM/Mинфин; выпущен 2026-04-07, расширен 2026-07-22), который охватывает продолжающуюся кампанию CyberAv3ngers, связанную с IRGC. Оговорка об атрибуции, сформулированная точно: ни одно ведомство официально не приписывало конкретно инцидент в Миннесоте этой группе — только более широкую продолжающуюся кампанию. Эта папка — сторона технического исправления, намеренно отделённая от расследования инцидента.
В 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, по-прежнему один из наиболее широко развёрнутых протоколов в системах управления водоснабжением/водоотведением, вообще не имеет концепции аутентификации в спецификации протокола; он восходит к последовательной связи 1979 года и никогда не проектировался с учётом безопасности. CISA неоднократно называла именно этот недостаток в консультативных бюллетенях по ICS (например, серия MELSEC iQ-F от Mitsubishi Electric: «MODBUS/TCP не имеет надлежащей аутентификации», что позволяет несанкционированное чтение/запись/остановку). Собственный ответ Организации Modbus, Modbus/TCP Security, — это инкапсуляция TLS с сертификатами X.509 — структурно та же категория исправления, которую демонстрирует Тест 3, стандартизированная на уровне организации протокола, а не одного вендора. DNP3 имеет опциональное расширение Secure Authentication (SAv5, стандартизировано в 2012 году); независимый анализ и отчёты интеграторов описывают его как редко настраиваемый на практике, ссылаясь на пробелы в совместимости между производителями ОТ и реальную сложность протокола — что оставляет ту же уязвимость, которую Тест 1 демонстрирует для EtherNet/IP.
Архитектурный тезис, сформулированный точно, чтобы не переоценивать его: исправление Теста 3 — взаимный TLS, привязка идентичности каждого устройства в сертификате (не только валидность CA), отзыв через CRL — работает на транспортном уровне, а не на прикладном протоколе ICS. Принцип в равной степени применим под Modbus, DNP3 или проприетарным протоколом; меняется обёртка, а не форма исправления. Этот репозиторий не создавал и не запускал PoC, специфичный для Modbus или DNP3 — это архитектурное обобщение на основе публичной документации, придерживающееся той же иерархии «продемонстрировано vs. подтверждено источниками», что и всё остальное здесь, а не новое проверенное утверждение.
Источники: Консультативные бюллетени CISA ICS о пробелах аутентификации Modbus/TCP — Industrial Cyber · Обзор Modbus/TCP Security — Veridify · Проблемы внедрения DNP3 SAv5/SAv6 — Step Function I/O
Пакет раскрытия живёт или умирает на том, чтобы не смешивать эти понятия, потому что у каждого своё исправление:
Продемонстрированное здесь исправление — привязка идентичности каждого устройства (Тест 3) — устраняет отказ общего ключа парка.
| Утверждение | Уровень | Почему | |
|---|---|---|---|
| Архитектурный принцип | «один общий секрет для всего парка скомпрометирован по всему парку одной утечкой; привязка идентичности к каждому устройству закрывает это» | ДОКАЗАНО | продемонстрировано реальным работающим кодом, включая отрицательный контроль, доказывающий, что проверка необходима, а не просто срабатывает: на строгой конечной точке Устройства B действительно валидный сертификат CA отклоняется по идентичности (Тест 3 · случай 3), но на конечной точке, проверяющей только валидность CA, тот же сертификат принимается (случай 4 — контроль) → «только валидная подпись CA == доступ ко всему парку == Тест 2 в обёртке TLS». Привязка также работает в обратную сторону: мошеннический сервер, предъявляющий валидный сертификат парка, отклоняется клиентом (случай 5). Тест 1 отдельно показывает более широкую базовую линию без аутентификации. |
| Конкретная реализация CIP Security от Rockwell ведёт себя идентично | «включение CIP Security на реальном оборудовании Rockwell устраняет CVE-2021-22681 именно таким образом» | ГИПОТЕЗА, подтверждена источниками, не проверена | это формулировка собственного бюллетеня Rockwell (PN1550) — «При правильном развёртывании CIP Security устраняет эту уязвимость... не использует никаких жёстко заданных ключей» — а не то, что мы независимо подтвердили на реальном оборудовании 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 «один ключ на всех». Но это тавтология: оба обработчика сконструированы так, чтобы принимать этот ключ, поэтому не существует пути выполнения, на котором он может не сработать. Он не демонстрирует ничего, что код не определяет в существование. Сохранён как нарративный мост от Теста 1 к Тесту 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 — этап жизненного цикла: отзывРеальный CRL, подписанный CA. Учётные данные клиента (engineer-1) выдаются; затем их серийный номер добавляется в
CRL, и те же всё ещё валидные, не истёкшие, подписанные CA учётные данные отклоняются — эмпирическое
содержание пункта об отзыве CR 1.8 / 1.9. Уникальность (Тест 3) ≠ возможность отзыва; это показывает, что учётные
данные можно забрать.
python test4_revocation.py
test5_rotation.py — этап жизненного цикла, который Тест 4 не закрыл: ротацияВыдаёт заменяющие учётные данные (v2) для той же идентичности (engineer-1), которая уже имеет
валидные (v1). КОНТРОЛЬ (случай 3): v1 предъявляется снова после того, как v2 существует, но до
того, как v1 явно выведен из обращения → всё ещё РАЗРЕШЕНО — доказывая, что повторная выдача сама по себе не выводит старые
учётные данные. Только после того, как v1 явно добавлен в CRL (случай 4), он отклоняется; v2 не затрагивается
на протяжении всего процесса (случай 5) — идентичность никогда не теряет доступ во время перехода. «Выдать замену
и вывести из обращения предыдущую» в CR 1.8 — это два действия, и здесь показаны оба, по отдельности.
python test5_rotation.py
test6_tamper_injection.py — этап, который CR 3.1 назвал «по построению»: выделенный тест целостностиРелей на уровне записей находится между реальным клиентом и сервером с взаимным TLS, пересылая записи TLS,
разбирая только 5-байтовый заголовок — он никогда не видит открытый текст зашифрованной полезной нагрузки. Контроль: каждый
байт пересылается без изменений → сообщение доставляется нетронутым. Вмешательство: один бит перевёрнут внутри
шифротекста живой записи Application Data → проверка AEAD принимающего стека TLS не срабатывает
(SSLV3_ALERT_BAD_RECORD_MAC), и соединение разрывается — повреждённые данные никогда не доставляются так,
как если бы они были валидными.
Какой байт и почему это указано: тело записи AEAD TLS 1.2 —
explicit_nonce(8) ‖ ciphertext ‖ auth_tag(16), поэтому байт 0 — это nonce, а не полезная нагрузка. Переворот
nonce также вызывает сбой проверки AEAD — но из-за искажения расшифровки, а не из-за того, что тег ловит
изменённую полезную нагрузку. CR 3.1 касается несанкционированной модификации передаваемой информации, поэтому
переворот нацелен на байт в середине шифротекста, и тест затем демонстрирует именно то
предложение, которое он утверждает.
python test6_tamper_injection.py
maximum_version = TLSv1_2 по двум причинам, связанным с наблюдаемостью, а не
безопасностью: в TLS 1.2 отсутствующий или отклонённый сертификат клиента завершается сбоем во время рукопожатия, поэтому
тест получает детерминированную, атрибутируемую ошибку, а не сбой после рукопожатия, как в TLS 1.3; и
тип содержимого записи остаётся видимым в открытом виде, что необходимо реле Теста 6 для идентификации
записи Application Data вообще. Развёртывайте самую высокую версию TLS, которую поддерживают ваши устройства — TLS 1.3
там, где доступно. Ничто в этом репозитории не следует читать как совет ограничивать производственную систему версией 1.2.presented == KEY, identity in SAN) не выполняются за постоянное время. Не эксплуатируемо здесь — сравниваемые
значения — это публичные строки идентичности, и 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]):
сверить нормативный текст каждого CR с приобретённой копией IEC 62443-4-2:2019.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 минут, явно не полный план
реагирования на инциденты — безопасность операций всегда на первом месте в ней).test5_rotation.py переводит CR 1.8
из «включено» в полностью продемонстрированный (ротация, с собственным контролем); test6_tamper_injection.py
переводит CR 3.1 из «по построению» в продемонстрированный (реальный переворот бита, отклонённый проверкой AEAD TLS).
Оба многократно перезапускались без сбоев перед добавлением сюда. Также добавлено:
PHASE3_CA_QUICKSTART.md (CA «в несколько строк кода», как реальные проверенные команды openssl) и
конкретный пример белого списка в PHASED_ROLLOUT.md, Фаза 1.GLOSSARY.md, INTEGRATOR_CHECKLIST.md, PHASE3_CA_QUICKSTART.md) теперь
реально связан из плана, а не висит без ссылок. Ничего в этом списке больше не в статусе ЧЕРНОВИК —
всё выше и ниже запушено и опубликовано в публичном репозитории.Каждый внешний идентификатор здесь был извлечён из живого источника 2026-07-31, а не по памяти из
обучения: AA26-097A (мультиисточник, включая WaterISAC / Tenable / SecurityWeek), Брахам как одна из
четырёх раскрытых жертв, CyberAv3ngers/IRGC, PN1550 подтверждён как реальный бюллетень Rockwell с
цитатой для строки 2, проверенной дословно («При правильном развёртывании CIP Security устраняет эту уязвимость»
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.
Журнал усиления, 2026-08-03: добавлены test5_rotation.py и test6_tamper_injection.py, закрывающие
два технических пункта, названных открытыми с 2026-07-31 — каждый перезапущен три раза без сбоев перед
описанием здесь. PHASE3_CA_QUICKSTART.md (реальные, проверенные команды openssl), конкретный
пример белого списка межсетевого экрана для Фазы 1, PHASE0_INVENTORY_WORKSHEET.md, RESOURCES.md,
INCIDENT_RESPONSE_QUICK_REFERENCE.md и обобщение для Modbus/DNP3 также добавлены в тот же день.
Журнал усиления, 2026-08-03 (продолжение) — два независимых прохода проверки, оба применены: группа
красной команды по безопасности нашла и исправила реальную уязвимость PKI в кратком руководстве по CA
(-copy_extensions copyall позволял вредоносному запросу сертификата самому объявлять CA:TRUE; исправлено через явный -extfile, проверено
против намеренно вредоносного запроса в обоих направлениях) и исправила test6, чтобы переворачивать именно
шифротекст, а не байт 0 (nonce), плюс реальное исправление потокобезопасности и фиксация зависимостей.
Отдельный структурный аудит — чтение фаз как системы во времени, а не чек-листа — нашёл
и исправил: заголовок «несколько строк» Фазы 3 скрывал, что отзыв/ротация не просты; решение о поведении CRL
при отказе (fail-open/fail-closed) никогда не было принято (теперь принято, с значением по умолчанию и обоснованием); истечение
срока сертификата было новым, неназванным режимом отказа (Фаза 4 теперь несёт урок безопасного перекрытия из Теста 5); Фаза 3
молча ослепляет сигнализатор записи Фазы 2 (теперь названо, с предложенными заменами); NTP был
неуказанным предварительным требованием; CR 1.14 был сформулирован как «не выполнен», тогда как «не применим к
исправленной конструкции» — точная формулировка; и INTEGRATOR_CHECKLIST.md/GLOSSARY.md были осиротевшими
документами, на которые ничто не ссылалось (теперь связаны из Фазы 3 и начала этого плана). Каждое исправление проверено
повторным запуском затронутых тестов, а не только перечитыванием диффа. Запушено и опубликовано — все пять фаз
плана развёртывания готовы, проверены дважды, и ничего из сегодняшнего больше не осталось локально.
Журнал усиления: Тест 3 был усилен, чтобы включить отрицательный контроль (случай 4, доказывающий необходимость, а не просто срабатывание), случай обратного направления (случай 5, взаимная привязка) и идентичность на основе SAN (не CN), затем повторно проверен повторным запуском всех пяти случаев; добавлен Тест 4 (отзыв через CRL). Подписи Тестов 1/2 были соразмерно скорректированы, чтобы держать три класса отказов различными; Тест 2 понижен с «теста» до нарративного моста; добавлены границы области для отзыва и постоянного времени. Цепочка цитирований была извлечена из первоисточников и помечена для повторного извлечения при подаче.