Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cip-security-poc — Proof-of-concept reproducing CVE-2021-22681's hardcoded-key flaw and validating a per-device mutual TLS/CRL fix over simulated EtherNet/IP, with IEC 62443-4-2 mapping. | Kitploit
Tools/GitHubGitHub/pcrosby-1990/cip-security-poc
Vulnerability AnalysisSCADA/ICS SecurityCryptographyThreat IntelligenceAuthenticationIncident Response
GitHubpcrosby-1990/cip-security-poc

cip-security-poc

Proof-of-concept reproducing CVE-2021-22681's hardcoded-key flaw and validating a per-device mutual TLS/CRL fix over simulated EtherNet/IP, with IEC 62443-4-2 mapping.

View Repository
71 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

cip-security-poc — proving the fix principle behind CVE-2021-22681 (not just its flaw)

Six runnable scripts — real EtherNet/IP protocol traffic (Test 1), real TLS/PKI mechanics (Tests 3–6) — zero Rockwell software or licensing anywhere in the chain. Built to test a claim before writing it up, not to argue for it on faith.

Provenance: built 2026-07-31, alongside a parallel investigation into the Braham, MN WWTF — one of the four utilities publicly disclosed in the coordinated Minnesota water-sector incident of July 26–27, 2026. Context for that incident lives in CISA advisory AA26-097A (joint FBI/CISA/NSA/EPA/DOE/USCYBERCOM/Treasury; issued 2026-04-07, expanded 2026-07-22), which covers the ongoing IRGC-affiliated CyberAv3ngers campaign. Attribution caveat, held precisely: no agency has formally attributed the Minnesota incident specifically to that group — only the broader ongoing campaign is. This folder is the technical-fix side, kept separate from the incident investigation on purpose.

Vendor status (updated 2026-08-03)

Rockwell PSIRT ([email protected]) and RA Secure Mail ([email protected]) were contacted 2026-07-31, before this repository and writeup went live (~30 minutes before, by file timestamps). Rockwell's security architecture team reviewed the repository and replied 2026-08-03. Quoted directly, not paraphrased into a stronger claim than they made:

Rockwell Automation does not endorse or validate your interpretation, your IEC 62443-4-2 mapping, or any conclusions drawn from the proof of concept. Please do not represent the work as reviewed, approved, or endorsed by Rockwell Automation... the scripts demonstrate general cryptographic and authentication principles rather than anything specific to CIP Security or to CVE-2021-22681.

This work has not been reviewed, approved, or endorsed by Rockwell Automation, full stop. Their technical characterization — general principles, not CIP-Security-specific — is the same distinction "The hard boundary" table below already draws about this repo's own claims; their review confirms it independently rather than contesting it. For utilities on hardware that can't reach CIP Security, Rockwell pointed to their own Converged Plantwide Ethernet (CPwE) Design and Implementation Guide (cited in PHASED_ROLLOUT.md Phase 1) — the existing vendor resource this project tries to route people toward rather than duplicate.

This shape isn't Rockwell-specific

Test 1's finding — no authentication in the protocol's default state — is not unique to EtherNet/IP. Modbus TCP, still one of the most widely deployed protocols in water/wastewater control systems, has no authentication concept in the protocol specification at all; it dates to 1979 serial communications and was never designed with security in mind. CISA has repeatedly named exactly this failure across ICS advisories (e.g. Mitsubishi Electric's MELSEC iQ-F series: "MODBUS/TCP lacks proper authentication," allowing unauthorized read/write/halt). The Modbus Organization's own answer, Modbus/TCP Security, is TLS encapsulation with X.509 certificates — structurally the same fix category Test 3 demonstrates here, standardized at the protocol-organization level instead of one vendor's. DNP3 has an optional Secure Authentication extension (SAv5, standardized 2012); independent analysis and implementer accounts both describe it as rarely configured in practice, citing interoperability gaps between OT manufacturers and genuine protocol complexity — leaving the same exposure Test 1 demonstrates for EtherNet/IP.

The architectural point, stated precisely so it isn't oversold: Test 3's fix — mutual TLS, per-device identity bound in the certificate (not just CA validity), revocation via CRL — operates at the transport layer, not the ICS application protocol. The principle is equally applicable underneath Modbus, DNP3, or a proprietary protocol; what changes is the wrapper, not the shape of the fix. This repo has not built or run a Modbus- or DNP3-specific PoC — this is an architectural generalization from public documentation, held to the same "demonstrated vs. sourced" tiering as everything else here, not a new tested claim.

Sources: CISA ICS advisories on Modbus/TCP authentication gaps — Industrial Cyber · Modbus/TCP Security overview — Veridify · DNP3 SAv5/SAv6 adoption challenges — Step Function I/O

Three different failures — keep them distinct

A disclosure packet lives or dies on not smearing these together, because each has a different fix:

  • No / absent authentication (Test 1) — a device exposed with no credential layer at all. The broad baseline the CyberAv3ngers campaign leaned on (many victims were reachable with absent or default creds).
  • One hardcoded / shared key across a fleet (the specific shape of CVE-2021-22681, modeled by Test 2) — extract the one key once, forge fleet-wide. This is the actual CVE.
  • Default credentials — factory creds never changed. Not modeled here; named so it isn't conflated with the two above.

The fix demonstrated here — per-device identity binding (Test 3) — addresses the fleet-key failure.

The hard boundary (read this first)

ClaimTierWhy
The architectural principle"a single shared secret across a fleet is compromised fleet-wide by one leak; per-device identity-bound authentication closes that"PROVENdemonstrated with real running code including the negative control that proves the check is necessary, not merely that it fires: on a strict endpoint Device B's genuinely CA-valid cert is rejected on identity (Test 3 · case 3), but on a CA-validity-only endpoint the same cert is accepted (case 4 — the control) → "validly CA-signed alone == fleet-wide access == Test 2 in TLS clothing." The binding also holds in reverse: a rogue server presenting a valid fleet cert is rejected by the client (case 5). Test 1 separately shows the broader no-auth baseline.
Rockwell's specific CIP Security implementation behaves identically"enabling CIP Security on real Rockwell hardware remediates CVE-2021-22681 exactly this way"LEAD, sourced not verifiedthis is Rockwell's own advisory (PN1550) language — "When properly deployed, CIP Security remediates this vulnerability... does not make use of any hardcoded keys" — not something we've independently confirmed against real Logix hardware. We tested the principle their advisory describes, not their exact wire-level implementation.

Do not conflate the two rows. The principle is proven. The vendor's specific implementation of it is credible (it's their own stated design intent) but untested by us against real equipment.

Tools

test1_baseline_vulnerable.py — the no-auth baseline, live

Spins up a real EtherNet/IP PLC simulator (cpppo, emulating an Allen-Bradley ControlLogix) and reads + writes a control tag with zero credentials. (Scope: this is the broad no-authentication baseline the campaign leaned on — not the specific hardcoded-key mechanism of CVE-2021-22681. Kept distinct on purpose; see "Three different failures" above.)

root@kitploit:~
python test1_baseline_vulnerable.py

test2_shared_secret_fails.py — the shape of the fleet-key flaw (narrative bridge, not a test)

Two endpoints hold one static key; a credential from Device A opens Device B unchanged — the closest structural analog to CVE-2021-22681's one-key-fits-all flaw. But it is tautological: both handlers are constructed to accept that key, so there is no execution path in which it can fail. It demonstrates nothing the code doesn't define into existence. Kept as the narrative bridge from Test 1 to Test 3; it carries no evidential weight and is deliberately not a proof-leg.

root@kitploit:~
python test2_shared_secret_fails.py

test3_mutual_tls_fix.py — the fix, with its negative control and both directions

Real CA, two individually-unique device certs — identity bound in the SubjectAlternativeName, not the deprecated CommonName. Five cases, all executed:

  • [1] Device A's own cert → GRANTED · [2] no cert → rejected at the TLS handshake · [3] Device B's CA-valid cert → DENIED on identity (strict endpoint).
  • [4] THE CONTROL — the same Device B cert against a CA-validity-only endpoint → GRANTED. This is what makes [3] mean anything: without the identity check, any fleet cert opens any device (== Test 2, in TLS clothing).
  • [5] REVERSE — a rogue server presenting Device B's cert is rejected by a client that binds device-a (check_hostname against a real SAN). Mutual — both ends bind identity.
root@kitploit:~
python test3_mutual_tls_fix.py

test4_revocation.py — the lifecycle leg: revocation

A real CA-signed CRL. A client credential (engineer-1) is granted; then its serial is added to the CRL, and the same still-valid, unexpired, CA-signed credential is denied — the empirical content of the CR 1.8 / 1.9 revocation clause. Uniqueness (Test 3) ≠ revocability; this shows a credential can be taken back.

root@kitploit:~
python test4_revocation.py

test5_rotation.py — the lifecycle leg Test 4 didn't close: rotation

Issues a replacement credential (v2) for the same identity (engineer-1) already holding a valid one (v1). THE CONTROL (case 3): v1 is presented again after v2 exists but before v1 is explicitly retired → still GRANTED — proving re-issuance alone does not retire the old credential. Only after v1 is explicitly added to the CRL (case 4) is it denied; v2 is unaffected throughout (case 5) — the identity never loses access during the transition. CR 1.8's "issue a replacement and retire the prior one" is two actions, and this shows both, separately.

root@kitploit:~
python test5_rotation.py

test6_tamper_injection.py — the leg CR 3.1 named as "by construction" only: a dedicated integrity test

A record-level relay sits between a real mutual-TLS client and server, forwarding TLS records by parsing only the 5-byte header — it never sees the encrypted payload's plaintext. Control: every byte forwarded unmodified → message delivered intact. Tamper: one bit flipped inside a live Application Data record's ciphertext → the receiving TLS stack's AEAD check fails (SSLV3_ALERT_BAD_RECORD_MAC) and the connection is torn down — corrupted data is never delivered as if it were valid. Which byte, and why it's specified: a TLS 1.2 AEAD record body is explicit_nonce(8) ‖ ciphertext ‖ auth_tag(16), so byte 0 is the nonce, not the payload. Flipping the nonce also trips the AEAD check — but by garbling decryption rather than by the tag catching an altered payload. CR 3.1 is about unauthorised modification of transmitted information, so the flip targets a byte in the middle of the ciphertext, and the test then demonstrates exactly the sentence it claims.

root@kitploit:~
python test6_tamper_injection.py

Scope boundaries (what is NOT claimed)

  • Revocation and rotation — both now demonstrated. Per-device uniqueness (Test 3) is not the same as revocability; Test 4 closes that gap — a still-valid, unexpired, CA-signed credential is granted before revocation and denied after. Test 5 closes the remaining lifecycle gap, rotation — re-issuing a replacement credential for the same identity and explicitly retiring the prior one, with its own negative control showing the two are separate actions.
  • TLS 1.2 in these scripts is a TEST-DETERMINISM ARTIFACT, not a deployment recommendation. Every script pins maximum_version = TLSv1_2 for two reasons that are about observability, not security: under TLS 1.2 a missing or rejected client certificate fails during the handshake, so the test gets a deterministic, attributable error rather than TLS 1.3's post-handshake failure; and the record content type stays visible in the clear, which Test 6's relay needs in order to identify an Application Data record at all. Deploy the highest TLS version your devices support — TLS 1.3 where available. Nothing in this repo should be read as advice to cap a production system at 1.2.
  • Constant-time (low severity, named for hygiene). The identity string compares (presented == KEY, identity in SAN) are not constant-time. Not exploitable here — the compared values are public-ish identity strings and TLS has already done the real cryptographic authentication before the compare runs — but flagged because the pattern gets copied into places where it does matter.

Setup

root@kitploit:~
python -m venv venv
venv\Scripts\activate        # or: source venv/bin/activate
pip install -r requirements.txt

Next steps (all named items closed as of 2026-08-03 — everything below is live, pushed, nothing held back)

  • 62443-4-2 SL 2 mapping — DRAFTED → 62443-4-2_SL2_MAPPING.md (CR 1.2 / 1.8 / 1.9 / 1.14 / 3.1, honestly tiered, every gap named; CR 1.2, CR 1.9, and CR 1.8's issuance/validation/revocation leg are now demonstrated). Before PSIRT ([email protected] / [email protected]): verify each CR's normative text against a purchased copy of IEC 62443-4-2:2019.
  • Phased rollout — DRAFTED → PHASED_ROLLOUT.md (Phase 0 stop-the-bleeding · 1 segment · 2 compensating controls · 3 CIP Security/PKI, hardware-permitting · 4 operate). Right-sized for a small utility; honest that CIP Security is hardware-gated, so Phases 0–2 carry the risk reduction regardless.
  • Responsible-disclosure sequencing — DONE. PSIRT contacted 2026-07-31, repo/writeup live ~30 minutes later the same day, Rockwell's reply 2026-08-03. Full detail in "Vendor status" above; nothing left open on this item.
  • Added 2026-08-03 — turning advice into artifacts a small utility can actually use: PHASE0_INVENTORY_WORKSHEET.md (a fillable device inventory, not just the instruction to make one), RESOURCES.md (free CISA / EPA / WaterISAC / AWWA assistance, verified live, not recalled), and INCIDENT_RESPONSE_QUICK_REFERENCE.md (a first-60-minutes card, explicitly not a full IR plan — safety of operations always comes first in it).
  • Both previously-named technical items — DONE (2026-08-03). test5_rotation.py moves CR 1.8 from "enabled" to fully demonstrated (rotation, with its own control); test6_tamper_injection.py moves CR 3.1 from "by construction" to demonstrated (a real bit-flip, rejected by TLS's AEAD check). Both re-run repeatedly with no flakiness before being added here. Also added: PHASE3_CA_QUICKSTART.md (the "few lines of code" CA, as real tested openssl commands) and a concrete allowlist example in PHASED_ROLLOUT.md Phase 1.
  • The phased rollout itself — DONE, all five phases (0–4). Every phase has been reviewed and fixed for internal consistency, not just drafted once: a real vulnerability found and closed in the Phase 3 CA setup, the CRL fail-open/fail-closed decision that was missing is now made and stated, certificate expiry is named as the new outage mode it is, Phase 3's silent effect on Phase 2's monitoring is named with replacements, NTP is listed as a prerequisite, and every supporting document (GLOSSARY.md, INTEGRATOR_CHECKLIST.md, PHASE3_CA_QUICKSTART.md) is now actually linked from the plan instead of sitting unreferenced. Nothing on this list is still DRAFT — everything above and below is pushed and live on the public repo.

Citations — retrieved, not recalled (and re-pull before submission)

Every external identifier here was pulled from a live source on 2026-07-31, not recalled from training: AA26-097A (multi-source, incl. WaterISAC / Tenable / SecurityWeek), Braham as one of the four disclosed victims, CyberAv3ngers/IRGC, PN1550 confirmed the real Rockwell advisory with its Row-2 quote checked verbatim ("When properly deployed, CIP Security remediates this vulnerability"

  • "does not make use of any hardcoded keys"), "cannot be mitigated with a patch" verbatim, CVSS 10.0 / CRITICAL (v3.1), CISA tracking ICSA-21-056-03, and 62443-4-2 CR 1.8 (PKI) + CR 3.1 (comm integrity) confirmed exact. Discipline for the packet: re-pull every identifier from primary sources at submission time. Advisories get renumbered, expanded, and superseded — AA26-097A already shows one expansion — so "verified on 2026-07-31" is not "verified at submit." The full formal CR-by-CR 62443-4-2 mapping is now written (62443-4-2_SL2_MAPPING.md) — what's left is re-pulling its cited normative text against a purchased standard copy before submission, not writing the mapping itself.

l0gic — Patrick Crosby · 2026-07-31.

Hardening log, 2026-08-03: test5_rotation.py and test6_tamper_injection.py added, closing the two technical items named open since 2026-07-31 — each re-run three times with no flakiness before being written up here. PHASE3_CA_QUICKSTART.md (real, tested openssl commands), a concrete Phase 1 firewall-allowlist example, PHASE0_INVENTORY_WORKSHEET.md, RESOURCES.md, INCIDENT_RESPONSE_QUICK_REFERENCE.md, and the Modbus/DNP3 generalization were also added the same day.

Hardening log, 2026-08-03 (continued) — two independent review passes, both applied: a security red-team found and fixed a real PKI vulnerability in the CA quickstart (-copy_extensions copyall let a malicious certificate request self-declare CA:TRUE; fixed via explicit -extfile, verified against a deliberately malicious request both ways) and corrected test6 to flip the ciphertext specifically rather than byte 0 (the nonce), plus a real thread-safety fix and dependency pinning. A separate structural audit — reading the phases as a system across time, not a checklist — found and fixed: Phase 3's "few lines" headline hid that revocation/rotation aren't simple; CRL fail-open/fail-closed was never decided (now is, with a default and reasoning); certificate expiry was a new, unnamed outage mode (Phase 4 now carries Test 5's own safe-overlap lesson); Phase 3 silently blinds Phase 2's write-alerter (now named, with replacements suggested); NTP was an unstated prerequisite; CR 1.14 was framed as "not met" where "not applicable to the remediated design" is the accurate statement; and INTEGRATOR_CHECKLIST.md/GLOSSARY.md were orphaned documents nothing linked to (now linked from Phase 3 and the top of this plan). Every fix verified by re-running the affected tests, not just re-reading the diff. Pushed and live — all five phases of the rollout plan are done, reviewed twice over, and nothing from today is sitting local anymore.

Hardening log: Test 3 was strengthened to include the negative control (case 4, proving necessity not just firing), the reverse-direction case (case 5, mutual binding), and SAN-based identity (not CN), then re-verified by re-running all five cases; Test 4 (CRL revocation) was added. Test 1/2 captions were right-sized to keep the three failure classes distinct; Test 2 was demoted from "test" to narrative bridge; revocation and constant-time scope boundaries were added. The citation chain was retrieved from primary sources and stamped for re-pull at submission.

Download Tool