pcrosby-1990/cip-security-poc
GitHub: pcrosby-1990/cip-security-poc
使用开源工具复现并验证 Rockwell Logix 硬编码密钥漏洞(CVE-2021-22681)的设备级身份绑定修复原理,面向小型水务设施提供可运行的 IEC 62443 合规验证与分阶段部署方案。
Stars: 0 | Forks: 0
# cip-security-poc — 证明 CVE-2021-22681 背后的修复*原理*(而不仅仅是其缺陷)
四个可运行脚本。真实的 EtherNet/IP 协议流量,真实的密码学,整个链条中没有任何 Rockwell
软件或许可证。旨在声明某个论点之前对其进行*测试*,而不是仅凭信仰为其辩护。
来源:构建于 2026-07-31,与对明尼苏达州布雷厄姆(Braham, MN)
WWTF 的平行调查同时进行——该设施是 2026 年 7 月 26 日至 27 日协调披露的明尼苏达州水务部门事件中**公开披露的四个公用设施**之一。该事件的背景记录在 CISA 公告 **AA26-097A**
(FBI/CISA/NSA/EPA/DOE/USCYBERCOM/财政部联合发布;发布于 2026-04-07,扩充于 2026-07-22)中,涵盖了正在进行中与 IRGC 有关的 **CyberAv3ngers** 活动。**归因警示(精准记录):**没有任何机构将*明尼苏达州事件具体*归因于该
组织——只有更广泛且正在进行的活动被如此归因。此文件夹是技术修复部分,有意与事件调查分开。
## 三种不同的失败——请保持区分
一份披露报告的成败取决于是否将这些内容混为一谈,因为每一种都有不同的修复方式:
- **无/缺失身份验证** *(测试 1)* —— 完全没有凭证层暴露的设备。这是
CyberAv3ngers 活动所依赖的广泛基准(许多受害者可以通过缺失或默认凭证被访问)。
- **整个机群中一个硬编码/共享密钥** *(CVE-2021-22681 的具体形式,由测试 2 建模)* —— 提取一次密钥,即可全机群伪造。**这是实际的 CVE。**
- **默认凭证** —— 从未更改的出厂凭证。此处未建模;特意命名,以免与前两者混淆。
此处演示的修复——**设备级身份绑定** *(测试 3)* —— 解决了机群密钥失败问题。
## 严格的边界(先阅读此部分)
| | 声明 | 级别 | 原因 |
|---|---|---|---|
| **架构原则** | “跨机群的单个共享密钥会因一次泄露而导致全机群被入侵;设备级身份绑定验证可消除此问题” | **已证实** | 通过实际运行的代码进行了演示,**包括证明该检查是*必要的*的对照组,而不仅仅是证明它触发了**:在严格的 endpoint 上,设备 B 真正 CA 有效的证书在身份验证时被*拒绝*(测试 3 · 情况 3),而在**仅**验证 CA 有效性的 endpoint 上,*相同*的证书被*接受*(情况 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),并
以**零凭证**读取和写入控制 tag。*(范围:这是该活动所依赖的广泛
**无身份验证基准**——**不是** CVE-2021-22681 特定的硬编码密钥机制。特意保持区分;参见上文“三种不同的失败”。)*
```
python test1_baseline_vulnerable.py
```
### `test2_shared_secret_fails.py` —— 机群密钥缺陷的*形态*(叙述桥梁,非测试)
两个 endpoint 持有一个静态密钥;来自设备 A 的凭证可以原封不动地打开设备 B——这是与 CVE-2021-22681 的一钥通行缺陷最接近的结构类比。**但这是同义反复的:**两个处理程序都被
*构造*为接受该密钥,因此不存在它可能失败的执行路径。它
没有演示任何代码未定义其存在的内容。作为从测试 1 到测试 3 的**叙述桥梁**保留;它**不具有任何证据效力**,并且特意不作为证明支撑。
```
python test2_shared_secret_fails.py
```
### `test3_mutual_tls_fix.py` —— 修复方案,包含其对照组和双向验证
真实的 CA,两个各自唯一的设备证书——**身份绑定在 SubjectAlternativeName 中,而不是在已弃用的 CommonName 中**。五个情况,全部执行:
- **[1]** 设备 A 自己的证书 → 允许 · **[2]** 无证书 → 在 TLS 握手时被拒绝 · **[3]** 设备 B 的 CA 有效证书 → 在身份验证时被拒绝(严格 endpoint)。
- **[4] 对照组** —— *相同*的设备 B 证书针对**仅**验证 CA 有效性的 endpoint → **允许**。这正是让 [3] 具有意义的原因:如果没有身份检查,任何机群证书都可以打开任何设备(== 披着 TLS 外衣的测试 2)。
- **[5] 反向** —— 呈现设备 B 证书的恶意服务器会被绑定 `device-a`(针对真实 SAN 进行 `check_hostname`)的客户端拒绝。双向的——两端都绑定身份。
```
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
```
## 范围边界(未声明的内容)
- **吊销——现已演示(`test4_revocation.py`);轮换——尚未演示。**设备级
*唯一性*(测试 3)与*可吊销性*不同;**测试 4 弥合了这一差距**——一个仍然有效、未过期的、CA 签名的凭证在吊销前被允许,并在吊销后**被拒绝**,仅仅是因为
CA 签名的 CRL 现在列出了其序列号。**轮换**(重新签发替换凭证并使旧的
凭证退役)密切相关,并由相同的 PKI *启用*,但此处未单独演示——
因此,“设备级身份绑定”仍不得悄然扩展为“已解决轮换问题”。
- **常数时间(低严重性,出于规范目的而命名)。** 身份字符串比较
(`presented == KEY`、`identity in SAN`)不是常数时间的。在此处不可利用——被比较的
值是半公开的身份字符串,并且在比较运行之前,TLS 已经完成了真正的密码学
身份验证——但被标记出来,因为这种模式会被复制到确实*关系重大*的
地方。
## 设置
```
python -m venv venv
venv\Scripts\activate # or: source venv/bin/activate
pip install -r requirements.txt
```
## 后续步骤(剩下一个实际项目,外加两个小的可选项目)
- **62443-4-2 SL 2 映射 —— 已起草** → `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 之前(psirt@rockwellautomation.com / secure@ra.rockwell.com):
对照购买的 IEC 62443-4-2:2019 副本验证每个 CR 的规范性文本。
- **分阶段推行 —— 已起草** → `PHASED_ROLLOUT.md`(阶段 0 止血 · 1 分段 · 2
补偿控制 · 3 CIP Security/PKI(在硬件允许的情况下)· 4 运营)。适合小型
公用设施;并诚实地说明 CIP Security 受硬件门控限制,因此无论硬件如何,阶段 0-2 都能承担降低风险的任务。
- **仍然开放 —— 实际剩余项目:** 负责任的披露顺序。先联系 PSIRT,
然后再进行公开撰写 / 更新 LinkedIn,以便按顺序记录来源链条。尚未
起草的内容:PSIRT 电子邮件本身。
- **两个小的技术项目,明确命名而非暗箱操作**(根据映射文档本身的摘要):一项
*轮换*测试(重新签发 + 退役),将 CR 1.8 从“已启用”转变为完全演示,以及一项
*篡改注入*测试,将 CR 3.1 从“按构造”转变为已演示。两者都不是
核心声明的承重部分;如果着手进行,工作量都很小。
## 引用——是检索到的,而非回忆的(并在提交前重新拉取)
此处的每一个外部标识符都是在 **2026-07-31** 从实时来源**拉取的**,而不是从
训练中回忆的:`AA26-097A`(多来源,包括 WaterISAC / Tenable / SecurityWeek),布雷厄姆作为四个
已披露的受害者之一,CyberAv3ngers/IRGC,`PN1550` 确认了真正的 Rockwell 公告及其
第二行引文已**逐字**核对(“如果部署得当,CIP Security 可以修复此漏洞”
+ “不使用任何硬编码密钥”),“无法通过补丁缓解”为逐字引用,**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 验证”并不等于“在提交时验证”。完整的按 CR 划分的正式 `62443-4-2` 映射现已
编写完成(`62443-4-2_SL2_MAPPING.md`)——剩下的是在提交前对照购买的
标准副本重新拉取其引用的规范性文本,而不是编写映射本身。
*l0gic — Patrick Crosby · 2026-07-31.*
*加固日志:*测试 3 得到了加强,包含了**对照组**(情况 4,证明是必要的而不仅仅是触发),**反向**情况(情况 5,双向绑定),以及**基于 SAN 的身份验证**
(不是 CN),然后通过重新运行所有五个情况进行了重新验证;添加了测试 4(CRL 吊销)。测试 1/2 的
说明文字进行了适当调整,以保持三种失败类别的区分;测试 2 从“测试”降级为
叙述桥梁;添加了吊销和常数时间范围的边界。引用链条
已从主要来源检索,并标记为在提交时重新拉取。
标签:EtherNet/IP, OT安全, PKINIT, 密码学, 工控安全, 底层编程, 手动系统调用, 概念验证, 漏洞复现, 逆向工具