A crash bug in a reference EtherNet/IP stack that almost nobody runs in production unmodified
CVE-2026-51221 is a buffer overflow in Get_Attribute_List inside EIPStackGroup OpENer at commit 76b95c. An attacker who can reach TCP/UDP 44818 (EtherNet/IP) sends a malformed Common Packet Format (CPF) frame and the device crashes — confidentiality and integrity are untouched (C:N/I:N), only availability (A:H). OpENer is an open-source ENIP adapter reference implementation primarily embedded into PLCs, remote I/O, drives, and ICS gateways by OEMs that don't license the ODVA stack. Affected: the 76b95c commit and any downstream firmware that vendored OpENer without applying upstream CPF length-validation patches.
The vendor 7.5 (AV:N/AC:L/PR:N/UI:N/A:H) reflects a pure-DoS unauth network bug — that's a *fair textbook score* but it overstates real-world risk in two directions and understates it in one. Overstates: OpENer is a niche reference stack; most OT operators don't deploy it directly, and ENIP/44818 should never be reachable from corporate or internet (and is largely not — Shodan typically shows <10k ENIP devices internet-exposed globally, of which OpENer-derived firmware is a sliver). Understates: in the OT contexts where it IS deployed, a crash on a controller can equal a process trip, a safety event, or a line stop. Net: MEDIUM for the IT defender population, HIGH for any operator who can confirm OpENer-derived firmware in the safety/control loop.
4 steps from start to impact.
Reach the EtherNet/IP service
- TCP/UDP 44818 reachable
- Device running OpENer 76b95c or earlier without backport
- ENIP is rarely internet-exposed
- Properly segmented OT zones block IT→OT 44818 traffic
- DMZ/data-diode designs make this unreachable from enterprise
Craft malformed CPF frame
Get_Attribute_List buffer. PoC patterns are trivially derived from prior ENIP fuzzing work — Defensics, boofuzz with the enip module, and Claroty's published OpENer fuzzers all produce this class of payload.- Knowledge of CIS/CPF wire format (public spec)
- A fuzzer or hand-built frame
- None meaningful — protocol is well-documented
Send and crash the target
- TCP or UDP path to 44818
- Many embedded RTOS watchdogs auto-restart the stack, limiting outage to seconds
Sustain the DoS / pivot to safety impact
- Persistent network position
- Target lacks IPS or rate-limit upstream
- Modern safety designs use isolated SIS; engineering workstations alarm immediately
- Host-based AV/EDR — irrelevant on embedded RTOS controllers; they don't run EDR.
- Patching the upstream OpENer repo on your build server — the running risk is in shipped firmware, not your source tree, unless you own the OEM build.
- WAF rules — ENIP is not HTTP; WAFs do not parse CIP/CPF.
- MFA / identity hardening — the bug is pre-auth; identity controls are irrelevant.
The supporting signals.
| In-the-wild exploitation | None observed. Not in CISA KEV. No public reports of weaponization. |
|---|---|
| Public PoC | No CVE-specific PoC published yet; OpENer CPF parser bugs have prior PoCs by Claroty Team82 (issue #558 lineage) that adapt trivially. |
| EPSS | 0.00172 (~0.17%) — bottom-tier exploitation probability per FIRST. |
| KEV status | Not listed as of 2026-07-01. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — network, unauth, no UI, availability-only impact. |
| Affected versions | OpENer at commit 76b95c and earlier; any downstream firmware that vendored OpENer without upstream CPF length validation. |
| Fixed version | Upstream patch on EIPStackGroup/OpENer main branch post-76b95c; OEM backports vary — check vendor PSIRTs (Rockwell, Schneider, ABB, Phoenix Contact, WAGO if applicable). |
| Internet exposure | Shodan typically reports <10k ENIP devices globally on 44818; OpENer-derived firmware is a small subset. Most are ICS honeypots or misconfigured field devices. |
| Disclosure date | 2026-06-29 |
| Reporter | Independent researcher via MITRE; lineage traces to Claroty Team82 prior OpENer ENIP stack research. |
Sources.
- Claroty Team82 — OpENer ENIP stack vulnerability research
- EIPStackGroup/OpENer GitHub — Issue #558 (CPF parsing OOB read lineage)
- EIPStackGroup/OpENer repository
- CISA Known Exploited Vulnerabilities Catalog
- FIRST EPSS model documentation
- ODVA — EtherNet/IP / CIP specification reference
- NIST NVD — CVE record lookup
- Tenable CVE database
Why this verdict
- Impact is DoS-only —
C:N/I:N/A:Hmeans no code execution, no data exfil; floor of harm is a controller crash, not domain takeover. - Exposure population is small — OpENer is a reference implementation; mainstream PLC vendors (Rockwell, Siemens) use their own ENIP stacks. Real footprint sits in niche OEM gateways and remote I/O.
- ENIP is OT-segment-only — port 44818 reachable from corporate or internet implies a segmentation failure that is itself the bigger finding. Properly Purdue-segmented sites are unreachable.
- Role multiplier: on a workstation or HMI — N/A (OpENer doesn't run there). On a typical line-of-business server — N/A. On a PLC / remote I/O / safety-adjacent controller, a sustained crash loop can cascade to process trip or batch loss; this is the floor-setting role and it keeps the verdict from collapsing to LOW.
- No KEV, EPSS 0.0017 — no active campaigns, exploitation probability is bottom decile.
- No RCE primitive published — the bug is a parser overflow into a DoS pattern, not a documented control-flow hijack on embedded RTOS without ASLR/canaries; weaponization to RCE is plausible but unproven.
Why not higher?
Not HIGH because the CVSS impact triplet caps at A:H with no C or I. There is no public RCE PoC, no KEV listing, no active exploitation, and the affected component is a niche open-source stack with low installed base relative to mainstream ENIP implementations. Pushing to HIGH would require either evidence of weaponization or confirmation that a widely-deployed safety-critical OEM ships unpatched OpENer.
Why not lower?
Not LOW because the bug is unauthenticated, network-reachable, single-packet with availability impact on devices that may sit in control or safety loops. In OT contexts a DoS on a controller is not a nuisance — it is a process event. The floor is set by the worst plausible deployment role (a vendored OpENer in a PLC), and that role keeps this above LOW.
Crowdsourced verification payload.
Run this on an auditor workstation that has L3 reach to the OT LAN, e.g. python3 check_opener.py 10.20.30.40. It sends an ENIP List Identity request (UDP 44818) and parses the Product Name string to flag OpENer-derived firmware. Requires no privileges beyond outbound UDP. Do NOT run during production-critical operations without change approval.
#!/usr/bin/env python3
# noisgate verifier — CVE-2026-51221 / OpENer ENIP fingerprint
# Usage: python3 check_opener.py <target_ip> [timeout_seconds]
# Exit: 0 PATCHED-or-not-OpENer, 1 VULNERABLE-candidate, 2 UNKNOWN
import socket, struct, sys
def list_identity(ip, timeout=3.0):
# ENIP encapsulation header: command=0x0063 (ListIdentity), len=0, session=0, status=0, ctx=0, opt=0
pkt = struct.pack('<HHIIQI', 0x0063, 0, 0, 0, 0, 0)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(timeout)
try:
s.sendto(pkt, (ip, 44818))
data, _ = s.recvfrom(4096)
return data
except socket.timeout:
return None
finally:
s.close()
def parse_product_name(reply):
# Skip 24-byte ENIP header, then CPF: item_count(2), type(2), len(2), then identity payload
try:
body = reply[24:]
# item_count + type_id + length
type_id, length = struct.unpack('<HH', body[2:6])
ident = body[6:6+length]
# identity: encap_proto(2) sock(16) vendor(2) device_type(2) product_code(2) rev(2) status(2) serial(4) name_len(1) name(N)
name_len = ident[32]
name = ident[33:33+name_len].decode('ascii', errors='replace')
return name
except Exception:
return None
def main():
if len(sys.argv) < 2:
print('usage: check_opener.py <ip> [timeout]')
sys.exit(2)
ip = sys.argv[1]
t = float(sys.argv[2]) if len(sys.argv) > 2 else 3.0
reply = list_identity(ip, t)
if reply is None:
print(f'UNKNOWN no ENIP response from {ip}:44818')
sys.exit(2)
name = parse_product_name(reply) or ''
print(f'product_name={name!r}')
lower = name.lower()
if 'opener' in lower or 'eipstackgroup' in lower:
print(f'VULNERABLE candidate OpENer-derived firmware on {ip} — verify build against commit 76b95c+ patch')
sys.exit(1)
print(f'PATCHED no OpENer fingerprint on {ip} (still confirm via OEM SBOM)')
sys.exit(0)
if __name__ == '__main__':
main()