Passive OT/ICS security: know every device on the plant network, without sending it a packet
Asset discovery · Vulnerability detection · Purdue zone mapping · Compliance assessment · Threat detection · Attack path analysis · SIEM integration
Start here
- Overview — what OTSec is
- How it runs — a fleet of sensors, or one PCAP at a time
- Why passive scanning? — the constraint everything else follows from
- Deployment: one server, many collectors — the Pi, the ring, the mirror port
Using it
What it finds
- Supported protocols — 21 analysers
- Deep asset profiling — make, model, firmware, OS version
- ICS project file analysis — ground truth from engineering files
- Vulnerability detection — 29 rules
- ICS CVE database — 92 CVEs, KEV and EPSS aware
- Enhanced risk scoring
- Threat detection & MITRE ATT&CK
- Attack path analysis
- Secure access audit
- Configuration snapshot engine — drift over time
- Network policy engine — generated firewall rules
- Network topology (Purdue model)
- Compliance assessment — 35 controls
- Integration ecosystem — 11 export formats
Design and internals
Deeper reading
| Document | What it answers |
|---|---|
| docs/ARCHITECTURE.md | How the collector, server and console divide the work |
| docs/DECISIONS.md | Every load-bearing decision, and what it cost |
| docs/BUILD_ORDER.md | What was built when, and what broke |
| deploy/README.md | Siting a collector; TLS termination; server sizing |
| docs/brand/README.md | Brand assets, and why the lockup keeps its field |
OTSec discovers what is on an industrial network, what version it runs, and what is wrong with it — without ever sending it a packet.
It identifies PLCs, RTUs, FRTUs, IEDs, HMIs, gateways and the ring switches carrying them; matches their firmware against an ICS CVE corpus; maps the estate to the Purdue model; and assesses it against NERC CIP, IEC 62443 and NIST 800-82. Nothing is probed, nothing is scanned, nothing is asked — every answer comes from traffic the plant was already producing.
The design principle underneath all of it is narrower than "find problems". It is that the tool must be able to tell "we looked and saw nothing" apart from "we did not look", and must never present the second as the first. Coverage travels with every number, and a window that could not be measured is reported as unknown rather than clean.
OTSec runs in two modes over one analysis core.
| Mode | Entry point | What it is |
|---|---|---|
| Sensor fleet | ot_collector.py → ot_server/ → console/ |
Raspberry Pi collectors capture continuously on SPAN ports, ship distilled observations to a central server, and an operator reads the estate in a browser. ~100 collectors per server. |
| Offline scanner | ot_scanner.py |
One PCAP in, reports out. 21 protocol analysers, 29 vuln rules, 92 CVEs, 9 analysis engines, 11 export formats. |
Two earlier single-purpose scanners remain in the tree and still work; both are superseded.
| Legacy scanner | Directory | Description |
|---|---|---|
| PLC Passive Scanner | plc_passive_scanner/ |
7 PLC protocols, vendor fingerprinting |
| RTU Passive Scanner | rtu_passive_scanner/ |
9 RTU/IED protocols, 21 vuln rules, GOOSE/MMS |
Active network scanners are dangerous in OT environments: unexpected packets can crash PLCs, trip protection relays, disrupt real-time control loops, and break single-master SCADA sessions. Passive scanning eliminates all of these risks.
Captures can be collected via network TAPs, port mirroring (SPAN), dedicated sensors, or existing IDS/NDR appliances.
OTSec runs as a hub and spoke. Raspberry Pi collectors sit on substation and ring-main-unit mirror ports; one server holds the estate and serves the console.
substation / RMU ring (fibre, ERPS-protected)
┌─────────┬─────────┬─────────┐
│ switch │ switch │ switch │ MPLS-TP transport
└────┬────┴────┬────┴────┬────┘
│ RTU │ FRTU │ IED
[ SPAN port ]
│
┌────▼─────────────────┐ tap NIC — promiscuous, no IP, never transmits
│ Raspberry Pi │ mgmt NIC — mTLS to the server
│ OTSec collector │
└────────┬─────────────┘
│ observation batches, not pcap
┌────────▼─────────────┐
│ OTSec server+console │ PostgreSQL, 13-month retention
└──────────────────────┘
Two NICs. The Pi's own management traffic is excluded from analysis by MAC and IP, and the number of excluded frames is recorded rather than assumed.
Observations, not packets. Collectors ship distilled asset, flow and detection records. Shipping pcap from a hundred collectors would be tens of MB/minute each across a utility WAN, and would centralise 13 months of plant process data for information the decoders have already extracted.
MPLS-TP. Where a mirror sits on an NNI rather than an access port, the substation LAN arrives inside a label stack and a pseudowire control word. The collector opens it — and when it cannot, it says so, because a tap on the wrong side of a pseudowire produces a perfectly quiet, entirely empty estate.
See docs/ARCHITECTURE.md and deploy/README.md.
cd ot_scanner
pip install -r requirements.txt
python ot_scanner.py capture.pcap -o reports/ -f all17 IP-layer protocols + 3 Layer-2 protocols, plus detection of 36+ IT/enterprise protocols for convergence risk assessment.
| Protocol | Transport | Port / EtherType | Vendor Coverage |
|---|---|---|---|
| Modbus/TCP | TCP | 502 | Multi-vendor (Schneider, Rockwell, ABB, Siemens, ...) |
| Siemens S7comm / S7comm+ | TCP | 102 | Siemens-exclusive (S7-300/400/1200/1500) |
| EtherNet/IP / CIP | TCP / UDP | 44818 / 2222 | Rockwell, Omron, Schneider, Siemens |
| DNP3 | TCP / UDP | 20000 | ABB, GE, Honeywell, Schneider, SEL |
| Omron FINS | UDP | 9600 | Omron-exclusive |
| MELSEC MC Protocol | TCP | 5006-5008 | Mitsubishi-exclusive |
| IEC 60870-5-104 | TCP | 2404 | ABB, Siemens, Schneider, GE |
| IEC 61850 MMS | TCP | 102 | ABB, Siemens, GE, Schneider |
| SEL Fast Message | TCP | 702 | SEL-exclusive |
| OPC-UA | TCP | 4840 / 4843 | Cross-vendor (IEC 62541) |
| BACnet/IP | UDP | 47808 | Building automation |
| MQTT | TCP | 1883 / 8883 | IIoT messaging |
| PROFINET RT | UDP | 34962-34964 | Siemens, multi-vendor |
| HART-IP | UDP / TCP | 5094 | Process instrumentation (Emerson, E+H, Yokogawa, Honeywell) |
| GE-SRTP | TCP | 18245 | GE Series 90 / RX3i / PACSystems (GE-exclusive) |
| Niagara Fox | TCP | 1911 / 4911 | Tridium Niagara building automation (BAS/BMS) |
| KNXnet/IP | UDP / TCP | 3671 | KNX building automation (lighting, HVAC, access) |
| IEC 61850 GOOSE | Ethernet | 0x88B8 | Protection signalling (L2) |
| IEC 61850 SV | Ethernet | 0x88BA | Sampled Values / merging units (L2) |
| PROFINET DCP | Ethernet | 0x8892 | Device discovery (L2) |
IT protocol detection (36+ protocols): HTTP/S, SSH, Telnet, RDP, VNC, TeamViewer, AnyDesk, Radmin, X11, IKE/IPsec, OpenVPN, PPTP, SMB, FTP, TFTP, DNS, DHCP, NTP, SNMP, Syslog, MSSQL, MySQL, PostgreSQL, Oracle, Redis, SMTP, POP3, IMAP, AMQP, and more.
Enhanced device identification beyond basic protocol detection:
- S7comm SZL parsing -- Structured block extraction from SZL ID 0x0011 (Module Identification) and 0x001C (Component Identification) for order numbers, firmware versions, serial numbers, CPU families, and I/O module inventories
- DNP3 Group 0 attributes -- Device description (var 241), product model (var 242), firmware version (var 243), hardware version (var 244), vendor name (var 245)
- Asset criticality inference -- Auto-classifies devices as safety_system, process_control, monitoring, or support based on CIP Safety types, GOOSE trip keywords, safety vendor names, protocol write/control ratios, and device role
- Communication profiles -- Per-device master/slave/peer classification with control ratios, read/write ratios, byte volumes, and peer counts
Offline asset discovery from engineering project files (no network traffic required):
| Format | Extension | Vendor | Parser |
|---|---|---|---|
| TIA Portal | .zap16 / .ap16 | Siemens | ZIP + XML extraction |
| Studio 5000 | .L5X | Rockwell / Allen-Bradley | XML export parsing |
| EcoStruxure | .XEF | Schneider Electric | XML export parsing |
| Generic CSV | .csv | Any CMDB | Header-row auto-mapping |
| Generic JSON | .json | Any CMDB | Array/object auto-mapping |
Project file devices receive vendor_confidence = "ground_truth" and bypass fingerprinting. Use --project-dir DIR to load.
29 behavioral vulnerability rules across 4 protocol-specific check modules, plus 5 zone violation rules.
| Category | Rules | Vuln ID Prefix | Examples |
|---|---|---|---|
| DNP3 Security | 7 | RTU-DNP3-* |
No Secure Authentication, unauthenticated control, Direct Operate bypass, restart commands, file transfer, multiple masters, UDP transport |
| IEC 60870-5-104 Security | 5 | RTU-104-* |
No TLS, multiple masters, unauthenticated commands, clock sync abuse, general interrogation flooding |
| IEC 61850 Security | 6 | RTU-61850-* |
GOOSE without IEC 62351-6, simulation flag abuse, low TTL, confRev drift, MMS without TLS |
| General / Cross-Protocol | 12 | OT-GEN-*, OT-OPCUA-*, OT-MQTT-* |
Cleartext protocols, OPC-UA without security, MQTT without TLS/auth |
| IT/OT Convergence | 5 | OT-ITOT-* |
Remote access in OT (RDP/VNC), databases in control zone, Telnet, SMB |
90 curated CVEs across 19 vendor groups with EPSS scores, CISA KEV flags, and exploit maturity ratings. Each matched CVE receives a Now / Next / Never priority classification.
| Vendor | CVEs | Key Products |
|---|---|---|
| Siemens | 16 | S7-1200, S7-1500, SIPROTEC, SICAM, SIMATIC RTLS |
| Rockwell Automation | 10 | ControlLogix, CompactLogix, MicroLogix |
| Schneider Electric | 10 | Modicon M340/M580, EcoStruxure, SCADAPack |
| ABB | 10 | RTU560, RTU500, REF615, REL670, RMC-100 |
| GE / GE Grid Solutions | 6 | D20MX, UR-series, Mark VIe |
| SEL (Schweitzer) | 5 | SEL-3505, SEL-651R, SEL-421 |
| Omron | 5 | CJ-series, NJ-series, CP-series |
| Mitsubishi Electric | 5 | MELSEC iQ-R, GX Works |
| Honeywell | 4 | RTU2020, Experion PKS |
| Yokogawa | 3 | CENTUM VP, ProSafe-RS |
| Trane | 2 | Tracer SC/SC+ (Building Automation) |
| Danfoss | 2 | AK-SM8xxA (Refrigeration) |
| Copeland | 2 | XWEB Pro 300D/500D (Refrigeration) |
| Johnson Controls | 2 | Frick Quantum HD (Industrial HVAC) |
| METZ CONNECT | 2 | EWIO2 (Industrial I/O Modules) |
| EnOcean | 1 | SmartServer IoT (BAS Gateway) |
| OPC Foundation | 3 | OPC-UA .NET Standard |
| Cross-vendor / Protocol | 5 | DNP3 SA bypass, Modbus/TCP, D-Link DCS |
Composite risk scoring (0-100) combining multiple intelligence sources:
| Component | Weight | Source |
|---|---|---|
| Base score | Vuln severity + CVE CVSS/10 | Vulnerability + CVE engines |
| Criticality multiplier | 1.5x safety, 1.3x process_control | Asset criticality inference |
| Exposure multiplier | 1.5x L0, 1.3x L1, 1.1x L2 | Purdue zone assignment |
| CISA KEV boost | +0.3 per KEV CVE | CVE database |
| EPSS boost | +max_epss x 0.4 | CVE database |
| Protocol penalties | +5 unauth DNP3, +5 GOOSE, etc. | Vulnerability findings |
| Compensating controls | -0.05 to -0.10 per mitigation | Protocol analysis |
10 ICS malware behavioral signatures matched against observed traffic patterns:
| Malware | Year | Target | MITRE Technique |
|---|---|---|---|
| Industroyer/CrashOverride | 2016 | IEC-104 breaker control | T0855, T0831 |
| TRITON/TRISIS | 2017 | Safety instrumented systems | T0839, T0836 |
| Havex | 2014 | OPC-UA reconnaissance | T0846 |
| BlackEnergy | 2015 | Multi-protocol + IT lateral | T0869, T0859 |
| Pipedream/Incontroller | 2022 | S7comm + Modbus multi-vector | T0836, T0855 |
| Stuxnet | 2010 | S7comm program injection | T0843, T0845 |
| FrostyGoop | 2024 | Modbus register manipulation | T0855 |
| Fuxnet | 2024 | Modbus flood writes + diagnostics (PLC bricking) | T0831 |
| IOControl | 2024 | MQTT C2 + IT protocols on IoT gateways | T0869 |
| CosmicEnergy | 2023 | IEC-104 breaker control + MSSQL C2 (PieHop) | T0855 |
4 detection modules: unauthorized command detection, malware signature matching, reconnaissance detection, behavioral baseline anomalies. 14 MITRE ATT&CK for ICS techniques mapped.
Multi-hop attack path discovery from IT entry points to crown jewel OT devices:
- Crown jewel identification -- safety systems, critical PLCs/RTUs, historians, KEV-exploitable devices
- Entry point identification -- remote access sessions, jump servers, gateways, IT/OT bridging devices
- BFS pathfinding -- multi-hop with 6-hop limit, bidirectional reachability graph
- Path scoring (0-100) -- hop count, auth gaps, encryption gaps, Purdue span, target value, CVE exploitability
- MITRE ATT&CK kill chain -- per-hop technique mapping (T0886, T0859, T0855, T0836, T0839, T0843)
- Remediation generation -- ordered mitigation steps per path (segmentation, auth, encryption, patching)
Remote access detection and NERC CIP-005-6 R2 compliance:
- Protocol detection: RDP, SSH, VNC, TeamViewer, AnyDesk, Radmin, X11, IKE/IPsec, OpenVPN, PPTP
- Jump server identification: Automatic from traffic pattern (inbound remote + outbound OT)
- Compliance classification: Each session rated compliant / non-compliant / review_required
- 5 compliance rules: encrypted transport, VPN DMZ termination, no direct L0-1 access, no safety system access, jump server required
Persistent device configuration tracking and drift detection:
- Snapshot capture: firmware, modules, function code profiles, program state, protocols, peers, risk
- Persistent storage: JSON files in
--snapshot-dirwith index metadata - Baseline management:
--set-baselinemarks scan as "last known good" (LKG) - 7 drift detection rules: firmware_change, module_change, program_event, function_code_shift, new_protocol, peer_change, risk_escalation
- MITRE ATT&CK mapping: T0839, T0843, T0836, T0855, T0869, T0886
Auto-generate firewall segmentation rules from observed traffic patterns:
| Export Format | Target Platform | File Type |
|---|---|---|
| Palo Alto PAN-OS XML | Panorama / PAN-OS API | XML |
| Fortinet FortiGate CLI | FortiGate console | .conf |
| Cisco IOS Extended ACL | IOS routers/switches | .acl (per zone) |
| Generic JSON | Custom integrations | JSON |
6 rule generation strategies: safety system isolation (P10-49), control traffic (P50-99), flow-based allows (P100-499), DMZ enforcement (P500-599), zone segmentation (P600-799), implicit deny (P9999). Each rule maps to IEC 62443-3-3, NERC CIP-005/007, and NIST 800-82.
Automatic Purdue model zone classification:
- Subnet inference -- groups devices into /24 zones
- Purdue level assignment -- Level 0 (Process) through Level 5 (Internet/Cloud)
- Criticality overrides -- safety systems forced to L0, process control to L1
- Edge aggregation -- directed graph with control/cross-zone annotations
- Zone violation detection -- 5 rules enforcing IEC 62443-3-3 SR 5.1 and NERC CIP-005
- GraphML export -- Gephi/yEd/Cytoscape with Purdue-level colour coding
35 controls across 3 frameworks:
| Framework | Controls | Coverage |
|---|---|---|
| NERC CIP (v5-7) | 15 | CIP-002 through CIP-013 |
| IEC 62443-3-3 | 12 | SR 1.1 through SR 7.7 |
| NIST SP 800-82 Rev 3 | 8 | Sections 5.1 through 6.3.4 |
11 export formats for SIEM, CMDB, threat intelligence, and notification platforms:
| Format | Flag | Target Platform |
|---|---|---|
| JSON | --json FILE |
Machine-readable full detail |
| CSV | --csv FILE |
Spreadsheet / data analysis |
| HTML | --html FILE |
Interactive browser report (Catppuccin Mocha) |
| GraphML | --graphml FILE |
Gephi / yEd / Cytoscape topology |
| CEF | --cef FILE |
Splunk, ArcSight, Elastic SIEM |
| LEEF | --leef FILE |
IBM QRadar |
| STIX 2.1 | --stix FILE |
ISACs, TAXII feeds |
| ServiceNow CMDB | --servicenow FILE |
ServiceNow Import Set API |
| Splunk HEC | --splunk-hec FILE |
Splunk HTTP Event Collector |
| Elastic ECS | --elastic-ecs FILE |
Elasticsearch / Kibana / Filebeat |
| Webhook | --webhook FILE |
Slack, Teams, PagerDuty, any HTTP |
Plus: --compliance FILE (NERC CIP + IEC 62443 + NIST 800-82), --delta FILE (baseline comparison), --policy DIR (firewall rules), --snapshot-dir DIR (config snapshots).
# Full scan with all outputs
python ot_scanner.py capture.pcap -o reports/ -f all
# With project file enrichment
python ot_scanner.py capture.pcap --project-dir /path/to/tia_projects/ -o reports/
# Generate firewall policies
python ot_scanner.py capture.pcap --policy policies/
# Configuration snapshot with baseline
python ot_scanner.py capture.pcap --snapshot-dir snapshots/ --set-baseline
# SIEM integration
python ot_scanner.py capture.pcap --splunk-hec events.ndjson --elastic-ecs ecs.ndjson
# ServiceNow CMDB export
python ot_scanner.py capture.pcap --servicenow cmdb_import.json
# Webhook notification
python ot_scanner.py capture.pcap --webhook alert_payload.json
# Delta analysis against baseline
python ot_scanner.py capture.pcap --delta baseline.json --json current.json
# Compliance audit
python ot_scanner.py capture.pcap --compliance audit.txt
# Verbose, filter high+ severity
python ot_scanner.py capture.pcap -v --severity highExit codes: 1 if CRITICAL or HIGH findings detected (CI/CD pipeline gating), 0 otherwise.
ot_scanner/
├── ot_scanner.py CLI entry point + argument parsing
├── requirements.txt scapy, dpkt, colorama
└── scanner/
├── core.py Unified PCAP analysis engine
├── models.py 20+ data types (dataclasses)
├── protocols/ 16 industrial protocol analyzers
│ ├── modbus.py, s7comm.py, enip.py, dnp3.py, fins.py, melsec.py
│ ├── iec104.py, iec61850_mms.py, sel_protocol.py
│ ├── opcua.py, bacnet.py, mqtt.py, profinet.py, goose.py
│ ├── it_detect.py IT protocol detector (36+ protocols)
│ └── behavior.py Deep packet inspection statistics
├── fingerprint/ 7-step vendor fingerprinting pipeline
├── vuln/ 29 vulnerability rules (4 check modules)
├── topology/ Purdue zones, violations, GraphML
├── cvedb/ 92 ICS CVEs (+ CISA KEV auto-refresh importer)
├── risk/ Composite risk scoring (0-100)
├── threat/ 10 ICS malware sigs + anomaly baselines
├── attack/ Multi-hop attack path analysis
├── access/ Secure access audit (CIP-005 R2)
├── config/ Configuration snapshots + drift detection
├── policy/ Firewall rule generation (4 formats)
├── project_files/ ICS project file parsers (5 formats)
├── export/ CEF, LEEF, STIX, ServiceNow, Splunk, Elastic, Webhook
├── compliance/ 35 controls (NERC CIP + IEC 62443 + NIST)
├── delta/ Baseline diff analysis
└── report/ JSON, CSV, HTML, GraphML reports
The unified OT scanner (v2.0) supersedes both legacy scanners. They remain in the repository for reference.
Device-identification-focused scanner for industrial PLCs (7 protocols). Outputs device inventory with vendor, model, firmware, and risk scoring.
Vulnerability-detection-focused scanner for RTUs, FRTUs, and IEDs (9 protocols, 21 vulnerability checks, Layer-2 GOOSE/SV).
791 tests. 762 run with nothing installed but pytest; the remaining 32 need a PostgreSQL store or Node for the console suite. The table below covers the standalone scanner only — the collector, server and console suites added by the sensor-fleet re-architecture are described in docs/BUILD_ORDER.md.
cd ot_scanner
pip install -r requirements-dev.txt
python -m pytest -q # 762 pass, 32 skip
# Everything. Both variables exist to stop tests SKIPPING silently — a skipped
# test on a CI summary page looks exactly like a passing one.
OT_TEST_DSN=postgresql://user:pw@127.0.0.1:5433/otsec \
OT_CONSOLE_REQUIRED=1 python -m pytest -q # 791 pass, 3 skip| Test File | Tests | Engine Covered |
|---|---|---|
test_models.py |
9 | All dataclass to_dict() + defaults |
test_risk_engine.py |
5 | Composite scoring, multipliers, controls |
test_threat_engine.py |
8 | All 10 malware sigs (incl. CosmicEnergy) + unauthorized commands |
test_attack_engine.py |
6 | Pathfinding, crown jewels, scoring |
test_access_engine.py |
4 | CIP-005 compliance, jump server detection |
test_config_engine.py |
8 | Snapshot capture/save/load, drift detection |
test_policy_engine.py |
5 | Rule generation, priorities, safety isolation |
test_cve_matcher.py |
11 | 92 CVEs, unique IDs, EPSS/KEV, matching pipeline |
test_cisa_importer.py |
8 | CISA KEV → CVE, ICS filter, EPSS merge, round-trip |
test_exporters.py |
4 | ServiceNow, Splunk, Elastic, Webhook |
test_protocols.py |
8 | HART-IP, GE-SRTP, Niagara Fox, KNXnet/IP analyzers |
GitHub Actions CI runs on every push and PR against Python 3.8, 3.10 and 3.12, with a PostgreSQL service for the store tests and a separate Node job for the console. Both jobs refuse to let their suite skip: a skipped test on a summary page is indistinguishable from a passing one.
- Python 3.8+
- scapy >= 2.5.0 (recommended) or dpkt >= 1.9.8 (fallback)
- Optional: colorama >= 0.4.6 (coloured terminal output)
- Dev: pytest >= 7.0 (testing only)
pip install scapy dpkt coloramaMIT -- see LICENSE for details.
