
CVE-2021-22681의 하드코딩 키 결함을 재현하고 시뮬레이션된 EtherNet/IP 환경에서 장치별 상호 TLS/CRL 수정을 검증하며 IEC 62443-4-2 매핑을 포함하는 개념 증명(PoC)
실행 가능한 스크립트 6개 — 실제 EtherNet/IP 프로토콜 트래픽(테스트 1), 실제 TLS/PKI 메커니즘 (테스트 3~6) — 체인 어디에도 Rockwell 소프트웨어나 라이선스가 없습니다. 주장을 신앙으로 받아들이기 위한 것이 아니라, 문서로 작성하기 전에 주장을 검증하기 위해 구축되었습니다.
출처: 2026-07-31에 구축되었으며, 미네소타주 브라함(Braham) WWTF에 대한 병행 조사와 함께 진행 — 2026년 7월 26~27일 미네소타 수도 부문 조율된 사고에서 공개적으로 공개된 4개 유틸리티 중 하나. 해당 사고의 맥락은 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는 여전히 물/폐수 제어 시스템에서 가장 널리 배포된 프로토콜 중 하나이며, 프로토콜 사양에 인증 개념이 전혀 없습니다; 1979년 직렬 통신으로 거슬러 올라가며 보안을 염두에 두고 설계된 적이 없습니다. CISA는 ICS 권고 전반에 걸쳐 정확히 이 실패를 반복적으로 지목했습니다(예: Mitsubishi Electric의 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) — 플릿 키 실패를 해결합니다.
| 주장 | 계층 | 이유 | |
|---|---|---|---|
| 아키텍처 원리 | "플릿 전체에 걸친 단일 공유 비밀은 한 번의 유출로 플릿 전체가 손상됩니다; 장치별 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 하드웨어에 대해 독립적으로 확인한 것이 아닙니다. 우리는 그들의 권고가 설명하는 원리를 테스트했지, 그들의 정확한 와이어 수준 구현을 테스트하지 않았습니다. |
두 행을 혼동하지 마십시오. 원리는 입증되었습니다. 공급업체의 특정 구현은 신뢰할 수 있지만(그들 자신의 명시된 설계 의도) 실제 장비에 대해 우리가 테스트하지 않았습니다.
test1_baseline_vulnerable.py — 무인증 기준선, 실시간실제 EtherNet/IP PLC 시뮬레이터(cpppo, Allen-Bradley ControlLogix 에뮬레이션)를 시작하고
자격 증명 없이 제어 태그를 읽고 씁니다. (범위: 이는 캠페인이 의존한 광범위한
무인증 기준선 — CVE-2021-22681의 특정 하드코딩 키 메커니즘이 아님. 의도적으로 구별 유지;
위의 "세 가지 다른 실패" 참조.)
python test1_baseline_vulnerable.py
test2_shared_secret_fails.py — 플릿 키 결함의 형태 (내러티브 브리지, 테스트 아님)두 엔드포인트가 하나의 정적 키를 보유; Device A의 자격 증명이 변경 없이 Device B를 엽니다 — CVE-2021-22681의 하나의 키가 모두에게 맞는 결함에 가장 가까운 구조적 유사체. 그러나 동어반복입니다: 두 핸들러 모두 해당 키를 수락하도록 구성되어 있으므로 실패할 수 있는 실행 경로가 없습니다. 코드가 정의로 존재하게 만드는 것 외에는 아무것도 입증하지 않습니다. 테스트 1에서 테스트 3으로의 내러티브 브리지로 유지; 증거적 가중치가 없으며 의도적으로 증명 다리가 아닙니다.
python test2_shared_secret_fails.py
test3_mutual_tls_fix.py — 수정, 음성 대조군 및 양방향 포함실제 CA, 두 개의 개별 고유 장치 인증서 — 폐기된 CommonName이 아닌 SubjectAlternativeName에 바인딩된 ID. 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의 "교체 발급 및 이전 것 폐기"는 두 가지 작업이며,
이것은 둘 다 별도로 보여줍니다.
python test5_rotation.py
test6_tamper_injection.py — CR 3.1이 "구성상"으로만 명명한 다리: 전용 무결성 테스트레코드 수준 릴레이가 실제 상호 TLS 클라이언트와 서버 사이에 위치하여 5바이트 헤더만 파싱하여 TLS
레코드를 전달합니다 — 암호화된 페이로드의 평문을 결코 볼 수 없습니다. 대조군: 모든 바이트가
수정 없이 전달됨 → 메시지가 온전하게 전달됨. 변조: 활성 Application Data 레코드의 암호문 내부에서
한 비트가 뒤집힘 → 수신 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