
Prova di concetto che riproduce il difetto della chiave hardcoded di CVE-2021-22681 e convalida una correzione mutual TLS/CRL per dispositivo su EtherNet/IP simulato, con mappatura IEC 62443-4-2.
Sei script eseguibili — traffico reale del protocollo EtherNet/IP (Test 1), meccanismi reali TLS/PKI (Test 3–6) — zero software Rockwell o licenze in qualsiasi punto della catena. Costruito per testare un'affermazione prima di scriverla, non per sostenerla per fede.
Provenienza: costruito il 31-07-2026, parallelamente a un'indagine sull'impianto di trattamento acque reflue di Braham, MN — uno dei quattro servizi pubblici divulgati pubblicamente nell'incidente coordinato del settore idrico del Minnesota del 26–27 luglio 2026. Il contesto di quell'incidente è descritto nell'advisory CISA AA26-097A (congiunta FBI/CISA/NSA/EPA/DOE/USCYBERCOM/Treasury; emessa il 07-04-2026, ampliata il 22-07-2026), che copre la campagna in corso CyberAv3ngers affiliata all'IRGC. Precisazione sull'attribuzione, mantenuta con rigore: nessuna agenzia ha attribuito formalmente l'incidente del Minnesota nello specifico a quel gruppo — solo la più ampia campagna in corso lo è. Questa cartella riguarda il lato della correzione tecnica, tenuta separata dall'indagine sull'incidente di proposito.
Rockwell PSIRT ([email protected]) e RA Secure Mail ([email protected]) sono stati contattati il 31-07-2026, prima che questo repository e il documento andassero online (~30 minuti prima, secondo i timestamp dei file). Il team di architettura della sicurezza di Rockwell ha esaminato il repository e ha risposto il 03-08-2026. Citato direttamente, non parafrasato in un'affermazione più forte di quella fatta:
Rockwell Automation non approva né valida la tua interpretazione, la tua mappatura IEC 62443-4-2, o qualsiasi conclusione tratta dalla proof of concept. Ti preghiamo di non presentare il lavoro come revisionato, approvato, o approvato da Rockwell Automation... gli script dimostrano principi crittografici e di autenticazione generali piuttosto che qualcosa di specifico per CIP Security o per CVE-2021-22681.
Questo lavoro non è stato revisionato, approvato o avallato da Rockwell Automation, punto. La loro
caratterizzazione tecnica — principi generali, non specifici di CIP Security — è la stessa distinzione
che la tabella "Il confine netto" qui sotto traccia già riguardo alle affermazioni di questo repo; la loro revisione la conferma
in modo indipendente piuttosto che contestarla. Per i servizi pubblici su hardware che non può raggiungere CIP Security,
Rockwell ha indicato la propria Converged Plantwide Ethernet (CPwE) Design and Implementation Guide
(citata in PHASED_ROLLOUT.md Fase 1) — la risorsa esistente del fornitore verso cui questo progetto cerca di indirizzare
le persone piuttosto che duplicarla.
Il risultato del Test 1 — nessuna autenticazione nello stato predefinito del protocollo — non è unico di EtherNet/IP. Modbus TCP, ancora uno dei protocolli più ampiamente distribuiti nei sistemi di controllo acqua/reflui, non ha alcun concetto di autenticazione nella specifica del protocollo; risale alle comunicazioni seriali del 1979 e non è mai stato progettato pensando alla sicurezza. La CISA ha ripetutamente segnalato esattamente questo difetto nelle advisory ICS (ad es. la serie MELSEC iQ-F di Mitsubishi Electric: "MODBUS/TCP manca di una corretta autenticazione," consentendo lettura/scrittura/arresto non autorizzati). La risposta della stessa Modbus Organization, Modbus/TCP Security, è l'incapsulamento TLS con certificati X.509 — strutturalmente la stessa categoria di correzione che il Test 3 dimostra qui, standardizzata a livello di organizzazione del protocollo invece che di un singolo fornitore. DNP3 ha un'estensione opzionale Secure Authentication (SAv5, standardizzata nel 2012); analisi indipendenti e resoconti di implementatori descrivono entrambi come raramente configurata nella pratica, citando lacune di interoperabilità tra i produttori OT e una genuina complessità del protocollo — lasciando la stessa esposizione che il Test 1 dimostra per EtherNet/IP.
Il punto architetturale, espresso con precisione per non essere sopravvalutato: la correzione del Test 3 — TLS reciproco, identità per-dispositivo vincolata nel certificato (non solo validità CA), revoca tramite CRL — opera a livello di trasporto, non sul protocollo applicativo ICS. Il principio è ugualmente applicabile sotto Modbus, DNP3 o un protocollo proprietario; ciò che cambia è il wrapper, non la forma della correzione. Questo repo non ha costruito o eseguito una PoC specifica per Modbus o DNP3 — questa è una generalizzazione architetturale dalla documentazione pubblica, mantenuta allo stesso livello "dimostrato vs. fontato" di tutto il resto qui, non una nuova affermazione testata.
Fonti: Advisory ICS CISA sulle lacune di autenticazione Modbus/TCP — Industrial Cyber · Panoramica Modbus/TCP Security — Veridify · Sfide di adozione DNP3 SAv5/SAv6 — Step Function I/O
Un pacchetto di divulgazione vive o muore sul non mescolare questi elementi, perché ognuno ha una correzione diversa:
La correzione dimostrata qui — vincolo dell'identità per-dispositivo (Test 3) — affronta il guasto della chiave di flotta.
| Affermazione | Livello | Perché | |
|---|---|---|---|
| Il principio architetturale | "un singolo segreto condiviso su una flotta è compromesso a livello di flotta da una singola fuga; l'autenticazione per-dispositivo vincolata all'identità chiude questo" | DIMOSTRATO | dimostrato con codice reale in esecuzione incluso il controllo negativo che prova che il controllo è necessario, non solo che scatta: su un endpoint rigoroso, il certificato genuinamente valido per CA del Dispositivo B viene rifiutato sull'identità (Test 3 · caso 3), ma su un endpoint solo-validità-CA lo stesso certificato viene accettato (caso 4 — il controllo) → "firmato validamente dalla CA da solo == accesso a livello di flotta == Test 2 in veste TLS." Il vincolo vale anche in senso inverso: un server rogue che presenta un certificato di flotta valido viene rifiutato dal client (caso 5). Il Test 1 mostra separatamente la più ampia baseline senza autenticazione. |
| L'implementazione CIP Security specifica di Rockwell si comporta in modo identico | "abilitare CIP Security su hardware Rockwell reale corregge CVE-2021-22681 esattamente in questo modo" | INDIZIO, fontato non verificato | questo è il linguaggio dell'advisory stessa di Rockwell (PN1550) — "Quando implementata correttamente, CIP Security corregge questa vulnerabilità... non fa uso di alcuna chiave hardcoded" — non qualcosa che abbiamo confermato in modo indipendente contro hardware Logix reale. Abbiamo testato il principio che la loro advisory descrive, non la loro esatta implementazione a livello di protocollo. |
Non confondere le due righe. Il principio è dimostrato. L'implementazione specifica del fornitore è credibile (è la loro stessa intenzione progettuale dichiarata) ma non testata da noi contro apparecchiature reali.
test1_baseline_vulnerable.py — la baseline senza autenticazione, dal vivoAvvia un vero simulatore PLC EtherNet/IP (cpppo, che emula un Allen-Bradley ControlLogix) e
legge + scrive un tag di controllo con zero credenziali. (Ambito: questa è l'ampia
baseline senza autenticazione su cui la campagna faceva affidamento — non il meccanismo specifico di chiave hardcoded
di CVE-2021-22681. Tenuta distinta di proposito; vedere "Tre diversi tipi di guasto" sopra.)
python test1_baseline_vulnerable.py
test2_shared_secret_fails.py — la forma del difetto della chiave di flotta (ponte narrativo, non un test)Due endpoint detengono una chiave statica; una credenziale dal Dispositivo A apre il Dispositivo B invariata — l'analogo strutturale più vicino al difetto una-chiave-per-tutti di CVE-2021-22681. Ma è tautologico: entrambi gli handler sono costruiti per accettare quella chiave, quindi non esiste un percorso di esecuzione in cui possa fallire. Dimostra nulla che il codice non definisca nell'esistenza. Mantenuto come ponte narrativo dal Test 1 al Test 3; non porta alcun peso probatorio e deliberatamente non è una gamba di prova.
python test2_shared_secret_fails.py
test3_mutual_tls_fix.py — la correzione, con il suo controllo negativo ed entrambe le direzioniCA reale, due certificati dispositivo individualmente unici — identità vincolata nel SubjectAlternativeName, non nel deprecato CommonName. Cinque casi, tutti eseguiti:
device-a (check_hostname contro un SAN reale). Reciproco — entrambe le estremità vincolano l'identità.python test3_mutual_tls_fix.py
test4_revocation.py — la gamba del ciclo di vita: la revocaUna CRL reale firmata da CA. Una credenziale client (engineer-1) viene concessa; poi il suo seriale viene aggiunto alla
CRL, e la stessa credenziale ancora valida, non scaduta, firmata da CA viene negata — il contenuto
empirico della clausola di revoca CR 1.8 / 1.9. L'unicità (Test 3) ≠ revocabilità; questo mostra che una
credenziale può essere ritirata.
python test4_revocation.py
test5_rotation.py — la gamba del ciclo di vita che il Test 4 non ha chiuso: la rotazioneEmette una credenziale sostitutiva (v2) per la stessa identità (engineer-1) che già detiene una
valida (v1). IL CONTROLLO (caso 3): v1 viene presentata di nuovo dopo che v2 esiste ma prima
che v1 venga esplicitamente ritirata → ancora CONCESSA — dimostrando che la sola ri-emissione non ritira la vecchia
credenziale. Solo dopo che v1 viene esplicitamente aggiunta alla CRL (caso 4) viene negata; v2 non è influenzata
durante tutto il processo (caso 5) — l'identità non perde mai l'accesso durante la transizione. "Emettere un
sostituto e ritirare il precedente" di CR 1.8 sono due azioni, e questo mostra entrambe, separatamente.
python test5_rotation.py
test6_tamper_injection.py — la gamba che CR 3.1 ha nominato come "per costruzione" soltanto: un test di integrità dedicatoUn relay a livello di record si trova tra un vero client e server mutual-TLS, inoltrando record TLS
analizzando solo l'header di 5 byte — non vede mai il plaintext del payload cifrato. Controllo: ogni
byte inoltrato senza modifiche → messaggio consegnato intatto. Manomissione: un bit invertito all'interno del ciphertext
di un record Application Data attivo → il controllo AEAD dello stack TLS ricevente fallisce
(SSLV3_ALERT_BAD_RECORD_MAC) e la connessione viene abbattuta — i dati corrotti non vengono mai consegnati come
se fossero validi.
Quale byte, e perché è specificato: il corpo di un record AEAD TLS 1.2 è
explicit_nonce(8) ‖ ciphertext ‖ auth_tag(16), quindi il byte 0 è il nonce, non il payload. Invertire
il nonce fa scattare anche il controllo AEAD — ma alterando la decifratura piuttosto che facendo catturare al tag un
payload modificato. CR 3.1 riguarda la modifica non autorizzata delle informazioni trasmesse, quindi
l'inversione mira a un byte nel mezzo del ciphertext, e il test dimostra poi esattamente la
frase che afferma.
python test6_tamper_injection.py
maximum_version = TLSv1_2 per due ragioni che riguardano l'osservabilità, non la
sicurezza: sotto TLS 1.2 un certificato client mancante o rifiutato fallisce durante l'handshake, quindi
il test ottiene un errore deterministico e attribuibile piuttosto che il fallimento post-handshake di TLS 1.3; e
il tipo di contenuto del record rimane visibile in chiaro, cosa di cui il relay del Test 6 ha bisogno per identificare
un record Application Data. Distribuisci la versione TLS più alta che i tuoi dispositivi supportano — TLS 1.3
dove disponibile. Nulla in questo repo dovrebbe essere letto come un consiglio a limitare un sistema di produzione a 1.2.presented == KEY, identity in SAN) non sono a tempo costante. Non sfruttabili qui — i valori
confrontati sono stringhe di identità semi-pubbliche e TLS ha già eseguito la vera autenticazione
crittografica prima che il confronto venga eseguito — ma segnalati perché il pattern viene copiato in luoghi
dove conta davvero.python -m venv venv
venv\Scripts\activate # oppure: 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,
onestamente livellata, ogni lacuna nominata; CR 1.2, CR 1.9 e la gamba emissione/validazione/revoca di CR 1.8
sono ora dimostrate). Prima di PSIRT ([email protected] / [email protected]):
verificare il testo normativo di ogni CR contro una copia acquistata di IEC 62443-4-2:2019.PHASED_ROLLOUT.md (Fase 0 stop-the-bleeding · 1 segmentazione · 2
controlli compensativi · 3 CIP Security/PKI, se l'hardware lo consente · 4 operatività). Dimensionato per una piccola
utility; onesto sul fatto che CIP Security è vincolato all'hardware, quindi le Fasi 0–2 portano la riduzione del rischio in ogni caso.PHASE0_INVENTORY_WORKSHEET.md (un inventario dispositivi compilabile, non solo l'istruzione di crearne
uno), RESOURCES.md (assistenza gratuita CISA / EPA / WaterISAC / AWWA, verificata dal vivo, non ricordata),
e INCIDENT_RESPONSE_QUICK_REFERENCE.md (una scheda per i primi 60 minuti, esplicitamente non un piano IR
completo — la sicurezza delle operazioni viene sempre prima in esso).test5_rotation.py porta CR 1.8
da "abilitato" a pienamente dimostrato (rotazione, con il proprio controllo); test6_tamper_injection.py
porta CR 3.1 da "per costruzione" a dimostrato (una vera inversione di bit, rifiutata dal controllo AEAD di TLS).
Entrambi rieseguiti ripetutamente senza instabilità prima di essere aggiunti qui. Aggiunto anche:
PHASE3_CA_QUICKSTART.md (la CA "in poche righe di codice", come comandi openssl reali testati) e un
esempio concreto di allowlist in PHASED_ROLLOUT.md Fase 1.GLOSSARY.md, INTEGRATOR_CHECKLIST.md, PHASE3_CA_QUICKSTART.md) è ora
effettivamente collegato dal piano invece di restare senza riferimenti. Nulla in questa lista è ancora
BOZZA — tutto quanto sopra e sotto è pubblicato e live sul repo pubblico.Ogni identificatore esterno qui è stato estratto da una fonte live il 31-07-2026, non ricordato dalla
formazione: AA26-097A (multi-fonte, incl. WaterISAC / Tenable / SecurityWeek), Braham come una delle
quattro vittime divulgate, CyberAv3ngers/IRGC, PN1550 ha confermato la vera advisory Rockwell con la sua
citazione della Riga 2 verificata parola per parola ("When properly deployed, CIP Security remediates this vulnerability"
ICSA-21-056-03, e 62443-4-2 CR 1.8 (PKI) + CR 3.1
(integrità delle comunicazioni) confermati esatti.
Disciplina per il pacchetto: ripescare ogni identificatore dalle fonti primarie al momento dell'invio.
Le advisory vengono rinumerate, ampliate e sostituite — AA26-097A mostra già un ampliamento — quindi
"verificato il 31-07-2026" non è "verificato all'invio." La mappatura formale completa CR-per-CR 62443-4-2 è ora
scritta (62443-4-2_SL2_MAPPING.md) — ciò che resta è ripescare il suo testo normativo citato contro
una copia acquistata dello standard prima dell'invio, non scrivere la mappatura stessa.l0gic — Patrick Crosby · 31-07-2026.
Registro di hardening, 03-08-2026: test5_rotation.py e test6_tamper_injection.py aggiunti, chiudendo i
due elementi tecnici nominati aperti dal 31-07-2026 — ciascuno rieseguito tre volte senza instabilità prima di essere
documentato qui. PHASE3_CA_QUICKSTART.md (comandi openssl reali e testati), un esempio
concreto di allowlist firewall per la Fase 1, PHASE0_INVENTORY_WORKSHEET.md, RESOURCES.md,
INCIDENT_RESPONSE_QUICK_REFERENCE.md e la generalizzazione Modbus/DNP3 sono stati aggiunti anche lo
stesso giorno.
Registro di hardening, 03-08-2026 (continua) — due passaggi di revisione indipendenti, entrambi applicati: un
red-team di sicurezza ha trovato e corretto una vulnerabilità PKI reale nella guida rapida CA (-copy_extensions copyall
permetteva a una richiesta di certificato dannosa di auto-dichiarare CA:TRUE; corretto tramite -extfile esplicito, verificato
contro una richiesta deliberatamente dannosa in entrambi i modi) e ha corretto test6 per invertire il ciphertext
specificamente piuttosto che il byte 0 (il nonce), più una correzione reale di thread-safety e il pinning delle dipendenze.
Un audit strutturale separato — leggendo le fasi come un sistema nel tempo, non come una checklist — ha trovato
e corretto: il titolo "poche righe" della Fase 3 nascondeva che revoca/rotazione non sono semplici; la decisione CRL
fail-open/fail-closed non era mai stata presa (ora lo è, con un default e una motivazione); la scadenza dei certificati
era un nuovo modo di guasto non nominato (la Fase 4 ora porta la lezione di sovrapposizione sicura del Test 5); la Fase 3
acceca silenziosamente l'alerter di scrittura della Fase 2 (ora nominato, con sostituti suggeriti); NTP era un
prerequisito non dichiarato; CR 1.14 era inquadrata come "non soddisfatta" dove "non applicabile al
design corretto" è l'affermazione accurata; e INTEGRATOR_CHECKLIST.md/GLOSSARY.md erano documenti
orfani a cui nulla rimandava (ora collegati dalla Fase 3 e dall'inizio di questo piano). Ogni correzione verificata
rieseguendo i test interessati, non solo rileggendo il diff. Pubblicato e live — tutte e cinque le fasi
del piano di rollout sono complete, revisionate due volte, e nulla di oggi è rimasto locale.
Registro di hardening: Il Test 3 è stato rafforzato per includere il controllo negativo (caso 4, che dimostra la necessità non solo lo scatto), il caso a direzione inversa (caso 5, vincolo reciproco) e l'identità basata su SAN (non CN), poi riverificato rieseguendo tutti e cinque i casi; il Test 4 (revoca CRL) è stato aggiunto. Le didascalie dei Test 1/2 sono state ridimensionate per mantenere distinte le tre classi di guasto; il Test 2 è stato declassato da "test" a ponte narrativo; sono stati aggiunti i confini di ambito per revoca e tempo costante. La catena di citazioni è stata recuperata dalle fonti primarie e timbrata per il ripescaggio all'invio.