四个可运行的脚本。真实的 EtherNet/IP 协议流量、真实的加密,整条链路中零 Rockwell 软件或授权。构建目的是在撰写报告之前验证一个论断,而非仅凭信念去论证它。
来源:构建于 2026-07-31,与对明尼苏达州 Braham 污水处理厂(WWTF)的平行调查同时进行——该厂是 2026 年 7 月 26–27 日明尼苏达州水务部门协同事件中公开披露的四家公用事业机构之一。该事件的背景见 CISA 公告 AA26-097A(FBI/CISA/NSA/EPA/DOE/USCYBERCOM/Treasury 联合发布;2026-04-07 发布,2026-07-22 扩充),涵盖正在进行的 IRGC 关联的 CyberAv3ngers 攻击活动。归属声明,严格限定: 没有任何机构正式将明尼苏达事件本身归于该组织——只有更广泛的持续攻击活动被如此归因。本文件夹是技术修复侧,有意与事件调查保持分开。
一份披露材料成败的关键在于不能把这些混为一谈,因为每一种都有不同的修复方式:
此处演示的修复方案——每设备身份绑定 (测试 3) — 解决的是机群共享密钥这一失败。
| 论断 | 层级 | 原因 | |
|---|---|---|---|
| 架构原理 | "整个机群共享一个秘密意味着一次泄露即导致机群级失陷;按设备进行身份绑定认证可封堵这一点" | 已验证(PROVEN) | 以真实可运行的代码演示,包括证明该检查是必要的阴性对照,而不仅仅是它会触发:在严格端点设备 B 上,真正由 CA 签发的合法证书会因身份不匹配而被拒绝(测试 3 · 案例 3),但在仅验证 CA 合法性的端点上,同一证书会被接受(案例 4 — 对照)→ "仅凭有效 CA 签名 == 机群级访问 == 披着 TLS 外衣的测试 2。"该绑定在反方向同样成立:伪装服务器出示有效的机群证书会被客户端拒绝(案例 5)。测试 1 单独展示了更广泛的免认证基线。 |
| Rockwell 的特定 CIP Security 实现行为相同 | "在真实 Rockwell 硬件上启用 CIP Security 正是以这种方式修复 CVE-2021-22681" | 线索(LEAD),有来源但未经核实 | 这是 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 — 机群密钥缺陷的形态(叙事桥梁,而非测试)两个端点持有一个静态密钥;来自设备 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 — 生命周期环节:吊销一张真实 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_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])之前:对照购买到的 IEC 62443-4-2:2019 副本核实各 CR 的规范文本。PHASED_ROLLOUT.md(阶段 0 止血 · 1 分段 · 2 补偿性控制 · 3 CIP Security/PKI,视硬件许可 · 4 运维)。针对小型公用事业机构适当裁剪;诚实说明 CIP Security 受硬件限制,因此无论何种情况,阶段 0–2 都承载风险削减。此处的每个外部标识符都是在 2026-07-31 从实时来源拉取的,而非凭训练记忆:AA26-097A(多来源,包括 WaterISAC / Tenable / SecurityWeek)、Braham 作为四家披露受害者之一、CyberAv3ngers/IRGC、PN1550 确认为真实的 Rockwell 公告,其第 2 行引文逐字核对("当正确部署时,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 从"测试"降级为叙事桥梁;新增吊销与恒定时间范围边界。引用链已从主要来源检索,并标记为提交时重新拉取。