mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-03 09:20:18 +03:00
Initial commit - 611 cybersecurity skills across all subdomains
This commit is contained in:
@@ -0,0 +1,583 @@
|
||||
---
|
||||
name: performing-ot-network-security-assessment
|
||||
description: >
|
||||
This skill covers conducting comprehensive security assessments of Operational
|
||||
Technology (OT) networks including SCADA systems, DCS architectures, and industrial
|
||||
control system communication paths. It addresses the Purdue Reference Model layers,
|
||||
identifies IT/OT convergence risks, evaluates firewall rules between zones, and
|
||||
maps industrial protocol traffic (Modbus, DNP3, OPC UA, EtherNet/IP) to detect
|
||||
misconfigurations, unauthorized connections, and attack surfaces in critical infrastructure.
|
||||
domain: cybersecurity
|
||||
subdomain: ot-ics-security
|
||||
tags: [ot-security, ics, scada, industrial-control, iec62443, network-assessment]
|
||||
version: 1.0.0
|
||||
author: mahipal
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Performing OT Network Security Assessment
|
||||
|
||||
## When to Use
|
||||
|
||||
- When conducting an initial security baseline of an OT/ICS environment for a new client
|
||||
- When evaluating the security posture of a facility after an IT/OT convergence initiative
|
||||
- When preparing for IEC 62443 or NERC CIP compliance audits
|
||||
- When assessing risk following a merger or acquisition involving industrial facilities
|
||||
- When investigating whether an OT network has been compromised or has unmonitored pathways to corporate IT
|
||||
|
||||
**Do not use** for IT-only network assessments without OT components, for application-layer vulnerability scanning of IT web applications (see performing-web-app-penetration-test), or for active exploitation of live OT systems without explicit authorization and safety controls in place.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Written authorization from the asset owner and operations management for all assessment activities
|
||||
- Understanding of the Purdue Reference Model and IEC 62443 zone/conduit architecture
|
||||
- Passive network monitoring tools (Nozomi Guardian, Dragos Platform, or Wireshark with industrial protocol dissectors)
|
||||
- Access to network diagrams, firewall rule sets, and asset inventories (or the ability to perform passive discovery)
|
||||
- Safety briefing on the physical processes controlled by the OT systems under assessment
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Establish Assessment Scope and Safety Boundaries
|
||||
|
||||
Define the scope based on the Purdue Reference Model levels and identify safety-critical systems that must not be actively scanned. OT assessments differ fundamentally from IT assessments because active scanning can crash PLCs, disrupt safety instrumented systems (SIS), and cause physical harm.
|
||||
|
||||
```yaml
|
||||
# OT Assessment Scope Definition
|
||||
assessment:
|
||||
facility: "Chemical Processing Plant - Site Alpha"
|
||||
purdue_levels_in_scope:
|
||||
- level_0: "Physical process sensors and actuators (passive observation only)"
|
||||
- level_1: "PLCs, RTUs, safety controllers (passive only, no active scanning)"
|
||||
- level_2: "HMI stations, engineering workstations, historian (limited active with approval)"
|
||||
- level_3: "Site operations - OPC servers, application servers (active scanning permitted)"
|
||||
- level_3_5: "DMZ - data diodes, jump servers (active scanning permitted)"
|
||||
- level_4: "Enterprise IT connecting to OT (active scanning permitted)"
|
||||
|
||||
safety_exclusions:
|
||||
- "Safety Instrumented Systems (SIS) - Triconex controllers"
|
||||
- "Emergency Shutdown (ESD) systems"
|
||||
- "Fire and Gas detection systems"
|
||||
- "Any Level 0/1 device during active production"
|
||||
|
||||
authorized_activities:
|
||||
passive:
|
||||
- "Network traffic capture and analysis via SPAN ports"
|
||||
- "Industrial protocol deep packet inspection"
|
||||
- "Wireless spectrum analysis"
|
||||
- "Physical walkthrough and visual inspection"
|
||||
active_with_approval:
|
||||
- "Targeted Nmap scans of Level 2-4 systems during maintenance windows"
|
||||
- "Authentication testing on HMI and engineering workstations"
|
||||
- "Firewall rule verification between zones"
|
||||
prohibited:
|
||||
- "Active scanning of PLCs, RTUs, or SIS controllers"
|
||||
- "Fuzzing industrial protocols on live systems"
|
||||
- "Modifying PLC logic or firmware"
|
||||
```
|
||||
|
||||
### Step 2: Perform Passive Network Discovery and Asset Inventory
|
||||
|
||||
Deploy passive monitoring to map all devices, communication flows, and protocols on the OT network without sending any traffic that could disrupt operations.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""OT Network Passive Discovery and Asset Inventory Builder.
|
||||
|
||||
Uses pcap captures from SPAN ports to identify OT assets, protocols,
|
||||
and communication patterns without active scanning.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
from scapy.all import rdpcap, IP, TCP, UDP
|
||||
from scapy.contrib.modbus import ModbusADURequest, ModbusADUResponse
|
||||
except ImportError:
|
||||
print("Install scapy: pip install scapy")
|
||||
sys.exit(1)
|
||||
|
||||
# Industrial protocol port mappings
|
||||
OT_PROTOCOL_PORTS = {
|
||||
502: "Modbus/TCP",
|
||||
102: "S7comm (Siemens)",
|
||||
44818: "EtherNet/IP (CIP)",
|
||||
2222: "EtherNet/IP (implicit)",
|
||||
4840: "OPC UA",
|
||||
20000: "DNP3",
|
||||
47808: "BACnet",
|
||||
1911: "Niagara Fox",
|
||||
789: "Crimson v3 (Red Lion)",
|
||||
2404: "IEC 60870-5-104",
|
||||
18245: "GE SRTP",
|
||||
5094: "HART-IP",
|
||||
}
|
||||
|
||||
PURDUE_LEVEL_RANGES = {
|
||||
"Level 0-1 (Field Devices)": ["10.10.0.0/16", "192.168.10.0/24"],
|
||||
"Level 2 (Control Systems)": ["10.20.0.0/16", "192.168.20.0/24"],
|
||||
"Level 3 (Site Operations)": ["10.30.0.0/16", "192.168.30.0/24"],
|
||||
"Level 3.5 (DMZ)": ["172.16.0.0/16"],
|
||||
"Level 4 (Enterprise)": ["10.0.0.0/16"],
|
||||
}
|
||||
|
||||
|
||||
def classify_purdue_level(ip_addr):
|
||||
"""Classify an IP address to its Purdue Reference Model level."""
|
||||
from ipaddress import ip_address, ip_network
|
||||
|
||||
addr = ip_address(ip_addr)
|
||||
for level, subnets in PURDUE_LEVEL_RANGES.items():
|
||||
for subnet in subnets:
|
||||
if addr in ip_network(subnet):
|
||||
return level
|
||||
return "Unknown"
|
||||
|
||||
|
||||
def analyze_ot_pcap(pcap_file):
|
||||
"""Analyze pcap file to discover OT assets and communication patterns."""
|
||||
packets = rdpcap(pcap_file)
|
||||
|
||||
assets = {}
|
||||
connections = defaultdict(lambda: {"count": 0, "protocols": set(), "ports": set()})
|
||||
protocol_stats = defaultdict(int)
|
||||
cross_zone_flows = []
|
||||
|
||||
for pkt in packets:
|
||||
if not pkt.haslayer(IP):
|
||||
continue
|
||||
|
||||
src_ip = pkt[IP].src
|
||||
dst_ip = pkt[IP].dst
|
||||
|
||||
# Track assets
|
||||
for ip in (src_ip, dst_ip):
|
||||
if ip not in assets:
|
||||
assets[ip] = {
|
||||
"ip": ip,
|
||||
"purdue_level": classify_purdue_level(ip),
|
||||
"protocols_observed": set(),
|
||||
"roles": set(),
|
||||
"first_seen": str(pkt.time),
|
||||
"last_seen": str(pkt.time),
|
||||
"mac": None,
|
||||
}
|
||||
assets[ip]["last_seen"] = str(pkt.time)
|
||||
|
||||
# Identify OT protocols by port
|
||||
dst_port = None
|
||||
if pkt.haslayer(TCP):
|
||||
dst_port = pkt[TCP].dport
|
||||
elif pkt.haslayer(UDP):
|
||||
dst_port = pkt[UDP].dport
|
||||
|
||||
if dst_port and dst_port in OT_PROTOCOL_PORTS:
|
||||
protocol_name = OT_PROTOCOL_PORTS[dst_port]
|
||||
protocol_stats[protocol_name] += 1
|
||||
assets[src_ip]["protocols_observed"].add(protocol_name)
|
||||
assets[dst_ip]["protocols_observed"].add(protocol_name)
|
||||
|
||||
# Determine roles
|
||||
assets[src_ip]["roles"].add("client/master")
|
||||
assets[dst_ip]["roles"].add("server/slave")
|
||||
|
||||
# Track connections
|
||||
conn_key = (src_ip, dst_ip)
|
||||
connections[conn_key]["count"] += 1
|
||||
if dst_port:
|
||||
connections[conn_key]["ports"].add(dst_port)
|
||||
if dst_port in OT_PROTOCOL_PORTS:
|
||||
connections[conn_key]["protocols"].add(OT_PROTOCOL_PORTS[dst_port])
|
||||
|
||||
# Detect cross-zone communication
|
||||
src_level = classify_purdue_level(src_ip)
|
||||
dst_level = classify_purdue_level(dst_ip)
|
||||
if src_level != dst_level and src_level != "Unknown" and dst_level != "Unknown":
|
||||
cross_zone_flows.append({
|
||||
"src": src_ip,
|
||||
"src_level": src_level,
|
||||
"dst": dst_ip,
|
||||
"dst_level": dst_level,
|
||||
"protocol": OT_PROTOCOL_PORTS.get(dst_port, f"port/{dst_port}"),
|
||||
})
|
||||
|
||||
return {
|
||||
"asset_count": len(assets),
|
||||
"assets": {ip: {k: list(v) if isinstance(v, set) else v for k, v in info.items()} for ip, info in assets.items()},
|
||||
"protocol_distribution": dict(protocol_stats),
|
||||
"total_connections": len(connections),
|
||||
"cross_zone_flows": cross_zone_flows[:50],
|
||||
}
|
||||
|
||||
|
||||
def generate_assessment_report(results):
|
||||
"""Generate the OT network assessment findings report."""
|
||||
report = []
|
||||
report.append("=" * 70)
|
||||
report.append("OT NETWORK PASSIVE DISCOVERY REPORT")
|
||||
report.append(f"Generated: {datetime.now().isoformat()}")
|
||||
report.append("=" * 70)
|
||||
|
||||
report.append(f"\nTotal Assets Discovered: {results['asset_count']}")
|
||||
report.append(f"Total Unique Connections: {results['total_connections']}")
|
||||
|
||||
report.append("\n--- INDUSTRIAL PROTOCOL DISTRIBUTION ---")
|
||||
for proto, count in sorted(results["protocol_distribution"].items(), key=lambda x: -x[1]):
|
||||
report.append(f" {proto}: {count} packets")
|
||||
|
||||
report.append("\n--- CROSS-ZONE COMMUNICATION FLOWS ---")
|
||||
if results["cross_zone_flows"]:
|
||||
for flow in results["cross_zone_flows"][:20]:
|
||||
report.append(
|
||||
f" {flow['src']} ({flow['src_level']}) -> "
|
||||
f"{flow['dst']} ({flow['dst_level']}) via {flow['protocol']}"
|
||||
)
|
||||
else:
|
||||
report.append(" No cross-zone flows detected (check subnet classifications)")
|
||||
|
||||
report.append("\n--- FINDINGS ---")
|
||||
# Check for Level 4 to Level 0-1 direct connections (critical finding)
|
||||
for flow in results["cross_zone_flows"]:
|
||||
if "Level 4" in flow["src_level"] and "Level 0-1" in flow["dst_level"]:
|
||||
report.append(
|
||||
f" [CRITICAL] Direct Enterprise-to-Field traffic: "
|
||||
f"{flow['src']} -> {flow['dst']} via {flow['protocol']}"
|
||||
)
|
||||
elif "Level 4" in flow["src_level"] and "Level 2" in flow["dst_level"]:
|
||||
report.append(
|
||||
f" [HIGH] Enterprise-to-Control traffic bypassing DMZ: "
|
||||
f"{flow['src']} -> {flow['dst']} via {flow['protocol']}"
|
||||
)
|
||||
|
||||
return "\n".join(report)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python ot_network_discovery.py <pcap_file>")
|
||||
sys.exit(1)
|
||||
|
||||
results = analyze_ot_pcap(sys.argv[1])
|
||||
print(generate_assessment_report(results))
|
||||
|
||||
# Save detailed JSON
|
||||
output_file = sys.argv[1].replace(".pcap", "_inventory.json")
|
||||
with open(output_file, "w") as f:
|
||||
json.dump(results, f, indent=2, default=str)
|
||||
print(f"\nDetailed inventory saved to: {output_file}")
|
||||
```
|
||||
|
||||
### Step 3: Evaluate Firewall Rules Between Purdue Zones
|
||||
|
||||
Analyze firewall configurations between OT zones to identify overly permissive rules, missing deny defaults, and unauthorized conduits that violate the IEC 62443 zone model.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""OT Zone Firewall Rule Analyzer.
|
||||
|
||||
Parses firewall rule exports (CSV format) and evaluates them against
|
||||
IEC 62443 zone/conduit model requirements.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class FirewallRule:
|
||||
rule_id: str
|
||||
source_zone: str
|
||||
source_ip: str
|
||||
dest_zone: str
|
||||
dest_ip: str
|
||||
service: str
|
||||
port: str
|
||||
action: str
|
||||
enabled: bool
|
||||
comment: str = ""
|
||||
|
||||
|
||||
# IEC 62443 zone communication policy
|
||||
# Defines which zone pairs are allowed to communicate and through what conduit
|
||||
ALLOWED_CONDUITS = {
|
||||
("Level 4", "Level 3.5"): {
|
||||
"allowed_ports": [443, 3389, 22],
|
||||
"description": "Enterprise to DMZ - web services, jump hosts",
|
||||
"requires_inspection": True,
|
||||
},
|
||||
("Level 3.5", "Level 3"): {
|
||||
"allowed_ports": [443, 1433, 5432, 8080],
|
||||
"description": "DMZ to Site Ops - historian mirror, OPC relay",
|
||||
"requires_inspection": True,
|
||||
},
|
||||
("Level 3", "Level 2"): {
|
||||
"allowed_ports": [502, 44818, 4840, 102],
|
||||
"description": "Site Ops to Control - OPC UA, Modbus, S7comm",
|
||||
"requires_inspection": True,
|
||||
},
|
||||
("Level 2", "Level 1"): {
|
||||
"allowed_ports": [502, 44818, 102, 2222],
|
||||
"description": "Control to Field - direct industrial protocols",
|
||||
"requires_inspection": False,
|
||||
},
|
||||
}
|
||||
|
||||
# Prohibited direct connections
|
||||
PROHIBITED_CONDUITS = [
|
||||
("Level 4", "Level 3"),
|
||||
("Level 4", "Level 2"),
|
||||
("Level 4", "Level 1"),
|
||||
("Level 4", "Level 0"),
|
||||
("Level 3", "Level 1"),
|
||||
("Level 3", "Level 0"),
|
||||
("Internet", "Level 3.5"),
|
||||
("Internet", "Level 3"),
|
||||
("Internet", "Level 2"),
|
||||
("Internet", "Level 1"),
|
||||
]
|
||||
|
||||
|
||||
def parse_firewall_rules(csv_file):
|
||||
"""Parse firewall rules from CSV export."""
|
||||
rules = []
|
||||
with open(csv_file, "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
rules.append(FirewallRule(
|
||||
rule_id=row.get("rule_id", ""),
|
||||
source_zone=row.get("source_zone", ""),
|
||||
source_ip=row.get("source_ip", ""),
|
||||
dest_zone=row.get("dest_zone", ""),
|
||||
dest_ip=row.get("dest_ip", ""),
|
||||
service=row.get("service", ""),
|
||||
port=row.get("port", ""),
|
||||
action=row.get("action", ""),
|
||||
enabled=row.get("enabled", "true").lower() == "true",
|
||||
comment=row.get("comment", ""),
|
||||
))
|
||||
return rules
|
||||
|
||||
|
||||
def analyze_rules(rules):
|
||||
"""Analyze firewall rules against IEC 62443 zone model."""
|
||||
findings = {"critical": [], "high": [], "medium": [], "low": [], "info": []}
|
||||
|
||||
for rule in rules:
|
||||
if not rule.enabled or rule.action.lower() != "allow":
|
||||
continue
|
||||
|
||||
zone_pair = (rule.source_zone, rule.dest_zone)
|
||||
port = int(rule.port) if rule.port.isdigit() else 0
|
||||
|
||||
# Check for prohibited conduits
|
||||
if zone_pair in PROHIBITED_CONDUITS:
|
||||
findings["critical"].append({
|
||||
"rule_id": rule.rule_id,
|
||||
"finding": f"Prohibited direct connection: {rule.source_zone} -> {rule.dest_zone}",
|
||||
"detail": f"Rule allows {rule.source_ip} to reach {rule.dest_ip}:{rule.port} ({rule.service})",
|
||||
"remediation": "Remove rule. Route traffic through DMZ (Level 3.5) with application-layer inspection.",
|
||||
})
|
||||
|
||||
# Check for overly broad rules (any/any)
|
||||
elif rule.source_ip in ("any", "0.0.0.0/0") or rule.dest_ip in ("any", "0.0.0.0/0"):
|
||||
findings["high"].append({
|
||||
"rule_id": rule.rule_id,
|
||||
"finding": f"Overly permissive rule with 'any' address",
|
||||
"detail": f"{rule.source_ip} -> {rule.dest_ip}:{rule.port} in {zone_pair}",
|
||||
"remediation": "Restrict to specific host IPs per IEC 62443 least-privilege conduit policy.",
|
||||
})
|
||||
|
||||
# Check allowed conduits for port violations
|
||||
elif zone_pair in ALLOWED_CONDUITS:
|
||||
conduit = ALLOWED_CONDUITS[zone_pair]
|
||||
if port and port not in conduit["allowed_ports"]:
|
||||
findings["medium"].append({
|
||||
"rule_id": rule.rule_id,
|
||||
"finding": f"Unauthorized port in conduit {zone_pair}",
|
||||
"detail": f"Port {port} ({rule.service}) not in allowed list {conduit['allowed_ports']}",
|
||||
"remediation": f"Remove port {port} from conduit or justify in risk assessment.",
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python ot_firewall_analyzer.py <rules.csv>")
|
||||
sys.exit(1)
|
||||
|
||||
rules = parse_firewall_rules(sys.argv[1])
|
||||
findings = analyze_rules(rules)
|
||||
|
||||
print("=" * 70)
|
||||
print("OT ZONE FIREWALL RULE ANALYSIS")
|
||||
print("=" * 70)
|
||||
for severity in ["critical", "high", "medium", "low"]:
|
||||
if findings[severity]:
|
||||
print(f"\n--- {severity.upper()} FINDINGS ({len(findings[severity])}) ---")
|
||||
for f in findings[severity]:
|
||||
print(f" [{f['rule_id']}] {f['finding']}")
|
||||
print(f" Detail: {f['detail']}")
|
||||
print(f" Fix: {f['remediation']}")
|
||||
```
|
||||
|
||||
### Step 4: Assess Industrial Protocol Security
|
||||
|
||||
Evaluate the security configuration of industrial protocols in use, checking for authentication, encryption, and access controls.
|
||||
|
||||
```bash
|
||||
# Capture Modbus/TCP traffic for analysis
|
||||
tcpdump -i eth0 -w ot_capture.pcap 'port 502 or port 44818 or port 4840 or port 102 or port 20000' -c 100000
|
||||
|
||||
# Use Wireshark with OT protocol dissectors for deep inspection
|
||||
tshark -r ot_capture.pcap -Y "modbus" -T fields \
|
||||
-e ip.src -e ip.dst -e modbus.func_code -e modbus.reference_num \
|
||||
-e modbus.word_cnt > modbus_analysis.csv
|
||||
|
||||
# Check for unauthenticated Modbus write operations (function codes 5,6,15,16)
|
||||
tshark -r ot_capture.pcap -Y "modbus.func_code >= 5 && modbus.func_code <= 16" \
|
||||
-T fields -e ip.src -e ip.dst -e modbus.func_code -e frame.time
|
||||
|
||||
# Scan for OPC UA servers and check security policies
|
||||
# Only run against Level 3+ systems with explicit authorization
|
||||
python3 -c "
|
||||
from opcua import Client
|
||||
server_url = 'opc.tcp://10.30.1.50:4840'
|
||||
client = Client(server_url)
|
||||
endpoints = client.connect_and_get_server_endpoints()
|
||||
for ep in endpoints:
|
||||
print(f'Endpoint: {ep.EndpointUrl}')
|
||||
print(f' Security Mode: {ep.SecurityMode}')
|
||||
print(f' Security Policy: {ep.SecurityPolicyUri}')
|
||||
print(f' Auth Tokens: {[t.TokenType for t in ep.UserIdentityTokens]}')
|
||||
"
|
||||
```
|
||||
|
||||
### Step 5: Generate Assessment Report
|
||||
|
||||
Compile findings into a structured report aligned with IEC 62443 and NIST SP 800-82 Rev.3.
|
||||
|
||||
```
|
||||
OT Network Security Assessment Report
|
||||
=======================================
|
||||
Facility: Chemical Processing Plant - Site Alpha
|
||||
Assessment Date: 2026-02-23
|
||||
Standard: IEC 62443-3-3 / NIST SP 800-82r3
|
||||
Assessor: [Assessor Name]
|
||||
|
||||
EXECUTIVE SUMMARY:
|
||||
The OT network assessment identified 47 assets across Purdue levels 0-4.
|
||||
12 critical and 23 high-severity findings were identified, primarily
|
||||
related to insufficient network segmentation, unauthenticated industrial
|
||||
protocols, and unauthorized cross-zone communication paths.
|
||||
|
||||
ASSET INVENTORY SUMMARY:
|
||||
Level 0-1 (Field): 18 devices (PLCs, RTUs, I/O modules)
|
||||
Level 2 (Control): 9 devices (HMIs, engineering workstations)
|
||||
Level 3 (Operations): 12 devices (historians, OPC servers, app servers)
|
||||
Level 3.5 (DMZ): 3 devices (data diode, jump server, patch server)
|
||||
Level 4 (Enterprise): 5 devices (domain controllers, file servers)
|
||||
|
||||
CRITICAL FINDINGS:
|
||||
[OT-001] Direct Enterprise-to-PLC communication detected
|
||||
Source: 10.0.5.22 (Level 4 - IT workstation)
|
||||
Dest: 10.10.1.15 (Level 1 - Allen-Bradley PLC)
|
||||
Protocol: EtherNet/IP (port 44818)
|
||||
Impact: An attacker on the corporate network could directly modify PLC logic
|
||||
Remediation: Block direct L4-L1 traffic; route through DMZ proxy
|
||||
|
||||
[OT-002] Modbus/TCP write commands without authentication
|
||||
Affected: 8 PLCs accepting unauthenticated FC6 (Write Single Register)
|
||||
Impact: Any device on the OT network can modify process setpoints
|
||||
Remediation: Deploy Modbus-aware firewall; restrict write-capable sources
|
||||
|
||||
[OT-003] Flat network - no segmentation between Purdue levels
|
||||
Detail: All OT devices share VLAN 100 (10.10.0.0/16)
|
||||
Impact: Compromised HMI has direct access to all PLCs and SIS
|
||||
Remediation: Implement zone-based segmentation per IEC 62443-3-2
|
||||
|
||||
RISK MATRIX:
|
||||
Critical: 12 findings (immediate remediation required)
|
||||
High: 23 findings (remediate within 30 days)
|
||||
Medium: 15 findings (remediate within 90 days)
|
||||
Low: 8 findings (remediate in next maintenance cycle)
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| Purdue Reference Model | Hierarchical architecture model (Levels 0-5) for organizing industrial control systems, defining security zones from physical process to enterprise IT |
|
||||
| IEC 62443 | International standard series for industrial automation and control systems (IACS) security, defining security levels, zones, conduits, and security requirements |
|
||||
| Zone | A grouping of logical or physical assets that share common security requirements, defined by IEC 62443-3-2 |
|
||||
| Conduit | A logical grouping of communication channels connecting two or more zones, subject to common security policies |
|
||||
| SCADA | Supervisory Control and Data Acquisition - system architecture for high-level process supervisory management of industrial processes |
|
||||
| DCS | Distributed Control System - control system architecture where control elements are distributed throughout the system |
|
||||
| Air Gap | Physical isolation of OT networks from IT/internet, increasingly replaced by managed conduits with firewalls and data diodes |
|
||||
| Safety Instrumented System (SIS) | Independent system designed to bring a process to a safe state when a hazardous condition is detected |
|
||||
|
||||
## Tools & Systems
|
||||
|
||||
- **Nozomi Networks Guardian**: Passive OT network monitoring platform providing asset discovery, vulnerability assessment, and anomaly detection for industrial environments
|
||||
- **Dragos Platform**: OT cybersecurity platform with asset visibility, threat detection, and vulnerability management designed for critical infrastructure
|
||||
- **Claroty xDome**: Cyber-physical systems protection platform providing comprehensive asset inventory and risk scoring across OT, IoT, and IIoT
|
||||
- **Wireshark/tshark**: Network protocol analyzer with industrial protocol dissectors for Modbus, DNP3, S7comm, EtherNet/IP, OPC UA, and BACnet
|
||||
- **Nmap with OT scripts**: Network scanner with NSE scripts for OT protocol enumeration (use only on Level 2+ with authorization)
|
||||
- **Grassmarlin**: NSA-developed passive OT network mapping tool for identifying SCADA/ICS network topology
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario: Flat OT Network with No Segmentation
|
||||
|
||||
**Context**: A water utility has all OT devices on a single VLAN. Passive network monitoring reveals HMIs, PLCs, historians, and a domain controller all sharing the same Layer 2 broadcast domain. There is no DMZ between the corporate network and the OT environment.
|
||||
|
||||
**Approach**:
|
||||
1. Deploy passive monitoring on the SPAN port to capture a complete communication baseline over 2-4 weeks
|
||||
2. Map all device-to-device communication flows with protocols and data volumes
|
||||
3. Classify assets into Purdue levels based on their function and communication patterns
|
||||
4. Design zone architecture with VLANs and inter-zone firewalls per IEC 62443-3-2
|
||||
5. Prioritize DMZ creation between Level 3 and Level 4 as the highest-impact segmentation
|
||||
6. Present segmentation plan with migration phases that avoid production disruption
|
||||
|
||||
**Pitfalls**: Active scanning PLCs during production can cause communication timeouts and process disruptions. Implementing segmentation without a complete traffic baseline will break legitimate control system communications. Relying solely on network-layer firewalls without industrial protocol inspection leaves Modbus/TCP and EtherNet/IP write commands unchecked.
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
OT Network Security Assessment Report
|
||||
=======================================
|
||||
Facility: [Facility Name]
|
||||
Assessment Date: YYYY-MM-DD
|
||||
Standard: IEC 62443-3-3 / NIST SP 800-82r3
|
||||
|
||||
EXECUTIVE SUMMARY:
|
||||
[2-3 sentence overview of findings and risk level]
|
||||
|
||||
ASSET INVENTORY:
|
||||
Level 0-1: [count] field devices
|
||||
Level 2: [count] control systems
|
||||
Level 3: [count] operations systems
|
||||
Level 3.5: [count] DMZ systems
|
||||
Level 4: [count] enterprise systems
|
||||
|
||||
FINDINGS BY SEVERITY:
|
||||
Critical: [count] (immediate action required)
|
||||
High: [count] (30-day remediation)
|
||||
Medium: [count] (90-day remediation)
|
||||
Low: [count] (next maintenance window)
|
||||
|
||||
DETAILED FINDINGS:
|
||||
[OT-NNN] Finding Title
|
||||
Severity: Critical|High|Medium|Low
|
||||
Affected Assets: [list]
|
||||
IEC 62443 Reference: [section]
|
||||
NIST 800-82r3 Reference: [section]
|
||||
Description: [technical detail]
|
||||
Impact: [operational and safety impact]
|
||||
Remediation: [specific technical remediation steps]
|
||||
```
|
||||
@@ -0,0 +1,169 @@
|
||||
# OT Network Security Assessment Report Template
|
||||
|
||||
## Document Information
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Facility | [Facility Name and Location] |
|
||||
| Assessment Date | YYYY-MM-DD to YYYY-MM-DD |
|
||||
| Lead Assessor | [Name, Certification] |
|
||||
| Standard | IEC 62443-3-3 / NIST SP 800-82r3 |
|
||||
| Classification | [Confidential / Internal Use Only] |
|
||||
| Report Version | 1.0 |
|
||||
|
||||
## Executive Summary
|
||||
|
||||
[2-3 paragraphs summarizing the assessment scope, methodology, key findings, and overall risk rating. Written for C-level and operations management audience.]
|
||||
|
||||
**Overall Risk Rating**: [Critical / High / Moderate / Low]
|
||||
|
||||
**Key Statistics**:
|
||||
- Total OT assets discovered: [N]
|
||||
- Critical findings: [N]
|
||||
- High findings: [N]
|
||||
- Unauthenticated protocol exposures: [N]
|
||||
- Cross-zone violations: [N]
|
||||
|
||||
## 1. Scope and Methodology
|
||||
|
||||
### 1.1 Assessment Scope
|
||||
|
||||
| Purdue Level | In Scope | Activity Type |
|
||||
|-------------|----------|---------------|
|
||||
| Level 0-1 (Field) | Yes/No | Passive Only |
|
||||
| Level 2 (Control) | Yes/No | Passive + Limited Active |
|
||||
| Level 3 (Operations) | Yes/No | Active Permitted |
|
||||
| Level 3.5 (DMZ) | Yes/No | Active Permitted |
|
||||
| Level 4 (Enterprise) | Yes/No | Active Permitted |
|
||||
|
||||
### 1.2 Exclusions
|
||||
- [List safety-critical systems excluded from assessment]
|
||||
- [List maintenance windows utilized for active testing]
|
||||
|
||||
### 1.3 Methodology
|
||||
- Passive network monitoring via SPAN ports ([duration])
|
||||
- Industrial protocol deep packet inspection
|
||||
- Firewall rule analysis
|
||||
- [Other assessment activities performed]
|
||||
|
||||
### 1.4 Tools Used
|
||||
- [Tool 1]: [Purpose]
|
||||
- [Tool 2]: [Purpose]
|
||||
|
||||
## 2. Asset Inventory
|
||||
|
||||
### 2.1 Asset Summary by Purdue Level
|
||||
|
||||
| Level | Device Type | Count | Key Vendors |
|
||||
|-------|------------|-------|-------------|
|
||||
| Level 0-1 | PLCs, RTUs, I/O | [N] | [Vendors] |
|
||||
| Level 2 | HMI, EWS | [N] | [Vendors] |
|
||||
| Level 3 | Historian, OPC | [N] | [Vendors] |
|
||||
| Level 3.5 | DMZ systems | [N] | [Vendors] |
|
||||
| Level 4 | Enterprise | [N] | [Vendors] |
|
||||
|
||||
### 2.2 Industrial Protocol Distribution
|
||||
|
||||
| Protocol | Port | Packet Count | Device Count | Auth Support |
|
||||
|----------|------|-------------|-------------|-------------|
|
||||
| Modbus/TCP | 502 | [N] | [N] | No |
|
||||
| EtherNet/IP | 44818 | [N] | [N] | No |
|
||||
| OPC UA | 4840 | [N] | [N] | Yes |
|
||||
| S7comm | 102 | [N] | [N] | No |
|
||||
| DNP3 | 20000 | [N] | [N] | Optional |
|
||||
|
||||
## 3. Network Architecture Assessment
|
||||
|
||||
### 3.1 Zone Architecture Evaluation
|
||||
[Assessment of current zone/conduit architecture against IEC 62443-3-2]
|
||||
|
||||
### 3.2 Cross-Zone Communication Analysis
|
||||
[Summary of authorized and unauthorized cross-zone communication flows]
|
||||
|
||||
### 3.3 Firewall Rule Analysis
|
||||
[Summary of firewall rule review findings]
|
||||
|
||||
## 4. Findings
|
||||
|
||||
### 4.1 Finding Template
|
||||
|
||||
```
|
||||
Finding ID: OT-[NNN]
|
||||
Severity: [Critical / High / Medium / Low]
|
||||
Title: [Finding Title]
|
||||
Affected Assets: [Asset list]
|
||||
IEC 62443 Reference: [Section reference]
|
||||
NIST 800-82r3 Reference: [Section reference]
|
||||
|
||||
Description:
|
||||
[Detailed technical description of the finding]
|
||||
|
||||
Evidence:
|
||||
[Screenshots, packet captures, or tool output demonstrating the finding]
|
||||
|
||||
Operational Impact:
|
||||
[Impact on process safety, availability, and operations]
|
||||
|
||||
Remediation:
|
||||
[Specific technical steps to remediate the finding]
|
||||
|
||||
Compensating Controls:
|
||||
[Interim measures if immediate remediation is not feasible]
|
||||
```
|
||||
|
||||
### 4.2 Critical Findings
|
||||
[List all critical findings using template above]
|
||||
|
||||
### 4.3 High Findings
|
||||
[List all high findings]
|
||||
|
||||
### 4.4 Medium Findings
|
||||
[List all medium findings]
|
||||
|
||||
### 4.5 Low Findings
|
||||
[List all low findings]
|
||||
|
||||
## 5. Risk Matrix
|
||||
|
||||
| Finding ID | Likelihood | Safety Impact | Operational Impact | Overall Risk |
|
||||
|-----------|-----------|---------------|-------------------|-------------|
|
||||
| OT-001 | [H/M/L] | [H/M/L] | [H/M/L] | [Critical/High/Medium/Low] |
|
||||
|
||||
## 6. Remediation Roadmap
|
||||
|
||||
### Phase 1: Immediate (0-30 days)
|
||||
- [Critical finding remediation items]
|
||||
|
||||
### Phase 2: Short-term (30-90 days)
|
||||
- [High finding remediation items]
|
||||
|
||||
### Phase 3: Medium-term (90-180 days)
|
||||
- [Medium finding remediation items]
|
||||
|
||||
### Phase 4: Long-term (6-12 months)
|
||||
- [Architecture improvements and low findings]
|
||||
|
||||
## 7. Compliance Gap Analysis
|
||||
|
||||
### IEC 62443-3-3 Compliance
|
||||
|
||||
| Requirement | Status | Gap Description |
|
||||
|------------|--------|----------------|
|
||||
| SR 1.1 Human User IAC | [Met/Partial/Not Met] | [Gap detail] |
|
||||
| SR 2.1 Authorization Enforcement | [Met/Partial/Not Met] | [Gap detail] |
|
||||
| SR 3.1 Communication Integrity | [Met/Partial/Not Met] | [Gap detail] |
|
||||
| SR 5.1 Network Segmentation | [Met/Partial/Not Met] | [Gap detail] |
|
||||
|
||||
## Appendices
|
||||
|
||||
### Appendix A: Complete Asset Inventory
|
||||
[Detailed asset list with IPs, MACs, firmware versions, protocols]
|
||||
|
||||
### Appendix B: Network Diagrams
|
||||
[Updated network topology diagrams showing discovered assets and flows]
|
||||
|
||||
### Appendix C: Firewall Rule Analysis Detail
|
||||
[Complete firewall rule review with per-rule assessment]
|
||||
|
||||
### Appendix D: Tool Output
|
||||
[Relevant tool output and packet captures]
|
||||
@@ -0,0 +1,87 @@
|
||||
# Standards Reference - OT Network Security Assessment
|
||||
|
||||
## IEC 62443 (ISA/IEC 62443)
|
||||
|
||||
### IEC 62443-1-1: Terminology, Concepts, and Models
|
||||
- Defines foundational concepts including zones, conduits, security levels, and the IACS reference architecture
|
||||
- Establishes the vocabulary used across all parts of the standard
|
||||
|
||||
### IEC 62443-2-1: Security Management System Requirements
|
||||
- Requirements for an IACS security management system (SMS)
|
||||
- Covers risk assessment, security policies, organization, staff competence, and awareness
|
||||
|
||||
### IEC 62443-3-2: Security Risk Assessment for System Design
|
||||
- Defines the process for partitioning an IACS into zones and conduits
|
||||
- Establishes Security Level targets (SL-T) for each zone based on risk assessment
|
||||
- Zone characteristics: assets, access points, communication channels, data flows
|
||||
- Conduit characteristics: connected zones, protocols, security countermeasures
|
||||
|
||||
### IEC 62443-3-3: System Security Requirements and Security Levels
|
||||
- Foundational requirements (FR) mapped to security levels (SL 1-4):
|
||||
- FR 1: Identification and Authentication Control (IAC)
|
||||
- FR 2: Use Control (UC)
|
||||
- FR 3: System Integrity (SI)
|
||||
- FR 4: Data Confidentiality (DC)
|
||||
- FR 5: Restricted Data Flow (RDF)
|
||||
- FR 6: Timely Response to Events (TRE)
|
||||
- FR 7: Resource Availability (RA)
|
||||
|
||||
### Security Levels
|
||||
| Level | Description |
|
||||
|-------|-------------|
|
||||
| SL 1 | Protection against casual or coincidental violation |
|
||||
| SL 2 | Protection against intentional violation using simple means |
|
||||
| SL 3 | Protection against sophisticated attack with moderate resources |
|
||||
| SL 4 | Protection against state-sponsored attack with extensive resources |
|
||||
|
||||
## NIST SP 800-82 Revision 3
|
||||
|
||||
### Guide to Operational Technology (OT) Security
|
||||
- Published September 2023, replacing the ICS-focused Rev. 2
|
||||
- Expanded scope covers: ICS/SCADA, building automation, transportation, physical access control, physical environment monitoring
|
||||
- Provides OT overlay for NIST SP 800-53r5 security controls
|
||||
- Tailored security control baselines for low/moderate/high-impact OT systems
|
||||
|
||||
### Key Sections for Network Assessment
|
||||
- Section 3: OT Overview and Architectures (Purdue Model reference)
|
||||
- Section 4: OT Risk Management
|
||||
- Section 5: OT Security Architecture (network segmentation, DMZ design)
|
||||
- Section 6: Applying Security Controls to OT (overlay guidance)
|
||||
|
||||
### OT-Specific Control Tailoring
|
||||
- AC-4 (Information Flow Enforcement): Implement data diodes for unidirectional flows from Level 3 to Level 3.5
|
||||
- SC-7 (Boundary Protection): Industrial firewalls with protocol-aware deep packet inspection
|
||||
- AU-6 (Audit Record Review): Correlate OT network events with process alarms
|
||||
|
||||
## NERC CIP Standards (Power Sector)
|
||||
|
||||
### CIP-002-5.1a: BES Cyber System Categorization
|
||||
- Identify and categorize Bulk Electric System (BES) cyber systems as high, medium, or low impact
|
||||
|
||||
### CIP-005-7: Electronic Security Perimeters
|
||||
- Define Electronic Security Perimeters (ESP) around BES cyber systems
|
||||
- Require Electronic Access Points (EAP) at all ESP boundaries
|
||||
- 2025 update requires MFA for remote access to medium and high-impact systems
|
||||
|
||||
### CIP-007-6: System Security Management
|
||||
- Port and service management, security patch management
|
||||
- Malicious code prevention, security event monitoring
|
||||
- System access controls and authentication
|
||||
|
||||
### CIP-010-4: Configuration Change Management and Vulnerability Assessments
|
||||
- Baseline configurations for BES cyber systems
|
||||
- Transient cyber asset and removable media management
|
||||
- Active vulnerability assessments at least every 15 months
|
||||
|
||||
## Purdue Reference Model (ISA-95)
|
||||
|
||||
### Level Architecture
|
||||
| Level | Name | Examples |
|
||||
|-------|------|----------|
|
||||
| Level 0 | Physical Process | Sensors, actuators, analyzers |
|
||||
| Level 1 | Basic Control | PLCs, RTUs, safety controllers |
|
||||
| Level 2 | Area Supervisory Control | HMI, engineering workstations, local historian |
|
||||
| Level 3 | Site Operations | OPC servers, site historian, MES |
|
||||
| Level 3.5 | DMZ | Data diodes, jump servers, patch servers |
|
||||
| Level 4 | Enterprise | ERP, email, corporate IT |
|
||||
| Level 5 | Internet/Cloud | Remote access, cloud services |
|
||||
@@ -0,0 +1,119 @@
|
||||
# Workflows - OT Network Security Assessment
|
||||
|
||||
## Assessment Lifecycle
|
||||
|
||||
```
|
||||
Phase 1: Scoping Phase 2: Discovery Phase 3: Analysis
|
||||
+-----------------+ +-----------------+ +-----------------+
|
||||
| Define scope | | Passive capture | | Protocol review |
|
||||
| Safety limits | ----> | Asset inventory | ---> | Zone evaluation |
|
||||
| Authorization | | Traffic mapping | | Firewall audit |
|
||||
+-----------------+ +-----------------+ +-----------------+
|
||||
|
|
||||
Phase 6: Verify Phase 5: Remediate Phase 4: Report
|
||||
+-----------------+ +-----------------+ +-----------------+
|
||||
| Validate fixes | | Segmentation | | Risk scoring |
|
||||
| Re-assessment | <---- | FW rule changes | <--- | Finding detail |
|
||||
| Compliance map | | Protocol harden | | Prioritization |
|
||||
+-----------------+ +-----------------+ +-----------------+
|
||||
```
|
||||
|
||||
## Phase 1: Scoping and Authorization
|
||||
|
||||
### Inputs
|
||||
- Facility type and industry vertical
|
||||
- Regulatory requirements (NERC CIP, IEC 62443, NIST CSF)
|
||||
- Existing network diagrams and asset inventories
|
||||
- Maintenance window schedules
|
||||
|
||||
### Activities
|
||||
1. Meet with operations, engineering, and IT security teams
|
||||
2. Define Purdue levels in scope and safety-critical exclusions
|
||||
3. Obtain written authorization specifying permitted assessment activities
|
||||
4. Identify SPAN/TAP points for passive monitoring deployment
|
||||
5. Review prior assessment reports and known issues
|
||||
|
||||
### Outputs
|
||||
- Signed Rules of Engagement document
|
||||
- Assessment scope matrix (Purdue levels vs. activity types)
|
||||
- SPAN/TAP deployment plan
|
||||
- Emergency contact list and escalation procedures
|
||||
|
||||
## Phase 2: Passive Network Discovery
|
||||
|
||||
### Inputs
|
||||
- SPAN port access on OT network switches
|
||||
- Assessment scope document
|
||||
|
||||
### Activities
|
||||
1. Deploy passive monitoring sensors on SPAN ports at each Purdue level boundary
|
||||
2. Capture network traffic for minimum 2 weeks to observe full operational cycle
|
||||
3. Build asset inventory from observed traffic (MAC, IP, protocols, firmware versions)
|
||||
4. Map all communication flows with source, destination, protocol, and frequency
|
||||
5. Identify industrial protocols in use (Modbus, DNP3, OPC UA, EtherNet/IP, S7comm)
|
||||
6. Detect unauthorized devices and rogue connections
|
||||
|
||||
### Activities - Wireless Assessment
|
||||
1. Scan for wireless access points in OT areas using spectrum analyzer
|
||||
2. Identify wireless industrial protocols (WirelessHART, ISA100.11a, Zigbee)
|
||||
3. Check for unauthorized Wi-Fi networks bridging IT and OT
|
||||
|
||||
### Outputs
|
||||
- Complete asset inventory with Purdue level classification
|
||||
- Network communication flow map
|
||||
- Protocol distribution analysis
|
||||
- Unauthorized device/connection list
|
||||
|
||||
## Phase 3: Analysis and Evaluation
|
||||
|
||||
### Inputs
|
||||
- Asset inventory and traffic capture data
|
||||
- Firewall rule exports
|
||||
- Network architecture diagrams
|
||||
|
||||
### Activities
|
||||
1. Evaluate zone architecture against IEC 62443-3-2 requirements
|
||||
2. Analyze firewall rules for overly permissive or prohibited conduits
|
||||
3. Assess industrial protocol security (authentication, encryption, access controls)
|
||||
4. Review remote access architecture and authentication mechanisms
|
||||
5. Evaluate patch levels of HMI, engineering workstations, and servers
|
||||
6. Check for known vulnerabilities in discovered OT firmware versions
|
||||
7. Assess physical security of network equipment in field locations
|
||||
|
||||
### Outputs
|
||||
- Finding list with severity ratings
|
||||
- Gap analysis against applicable standards
|
||||
- Risk matrix mapping findings to operational/safety impact
|
||||
|
||||
## Phase 4: Reporting
|
||||
|
||||
### Report Structure
|
||||
1. Executive Summary (1 page)
|
||||
2. Scope and Methodology
|
||||
3. Asset Inventory Summary
|
||||
4. Network Architecture Assessment
|
||||
5. Detailed Findings (Critical/High/Medium/Low)
|
||||
6. Compliance Gap Analysis
|
||||
7. Remediation Roadmap with Prioritization
|
||||
8. Appendices (asset inventory, network diagrams, tool output)
|
||||
|
||||
## Phase 5: Remediation Support
|
||||
|
||||
### Priority Order
|
||||
1. **Immediate**: Block unauthorized cross-zone paths (enterprise to field devices)
|
||||
2. **30-day**: Implement DMZ between corporate IT and OT operations
|
||||
3. **60-day**: Deploy industrial protocol-aware firewalls between zones
|
||||
4. **90-day**: Harden remote access with MFA and jump servers
|
||||
5. **6-month**: Full zone/conduit segmentation per IEC 62443 design
|
||||
|
||||
## Risk Scoring for OT Environments
|
||||
|
||||
OT risk scoring must account for safety impact beyond traditional CIA triad:
|
||||
|
||||
| Factor | Weight | Description |
|
||||
|--------|--------|-------------|
|
||||
| Safety Impact | 30% | Potential for physical harm to personnel or public |
|
||||
| Operational Impact | 25% | Production disruption or equipment damage |
|
||||
| Environmental Impact | 15% | Release of hazardous materials |
|
||||
| Financial Impact | 15% | Direct costs and regulatory penalties |
|
||||
| Reputational Impact | 15% | Public trust and regulatory scrutiny |
|
||||
@@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env python3
|
||||
"""OT Network Security Assessment - Automated Discovery and Analysis.
|
||||
|
||||
This script performs passive OT network discovery from pcap captures,
|
||||
classifies assets by Purdue level, maps industrial protocol usage,
|
||||
and identifies security findings.
|
||||
|
||||
Usage:
|
||||
python process.py --pcap <capture.pcap> [--firewall-rules <rules.csv>]
|
||||
python process.py --live-capture --interface <iface> --duration <seconds>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime
|
||||
from ipaddress import ip_address, ip_network
|
||||
from typing import Optional
|
||||
|
||||
try:
|
||||
from scapy.all import rdpcap, sniff, IP, TCP, UDP, Ether, ARP
|
||||
except ImportError:
|
||||
print("[ERROR] scapy is required: pip install scapy")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# ── Industrial Protocol Definitions ──────────────────────────────────
|
||||
|
||||
OT_PROTOCOLS = {
|
||||
502: {"name": "Modbus/TCP", "vendor": "Open", "risk": "high", "auth": False},
|
||||
102: {"name": "S7comm", "vendor": "Siemens", "risk": "high", "auth": False},
|
||||
44818: {"name": "EtherNet/IP", "vendor": "ODVA/Rockwell", "risk": "high", "auth": False},
|
||||
2222: {"name": "EtherNet/IP Implicit", "vendor": "ODVA", "risk": "medium", "auth": False},
|
||||
4840: {"name": "OPC UA", "vendor": "OPC Foundation", "risk": "low", "auth": True},
|
||||
20000: {"name": "DNP3", "vendor": "IEEE", "risk": "high", "auth": False},
|
||||
47808: {"name": "BACnet/IP", "vendor": "ASHRAE", "risk": "high", "auth": False},
|
||||
1911: {"name": "Niagara Fox", "vendor": "Tridium", "risk": "high", "auth": False},
|
||||
2404: {"name": "IEC 60870-5-104", "vendor": "IEC", "risk": "high", "auth": False},
|
||||
18245: {"name": "GE SRTP", "vendor": "GE", "risk": "high", "auth": False},
|
||||
5094: {"name": "HART-IP", "vendor": "FieldComm", "risk": "medium", "auth": False},
|
||||
789: {"name": "Crimson v3", "vendor": "Red Lion", "risk": "high", "auth": False},
|
||||
1089: {"name": "FF HSE", "vendor": "Fieldbus Foundation", "risk": "medium", "auth": False},
|
||||
9600: {"name": "OMRON FINS", "vendor": "OMRON", "risk": "high", "auth": False},
|
||||
5007: {"name": "Mitsubishi MELSEC", "vendor": "Mitsubishi", "risk": "high", "auth": False},
|
||||
}
|
||||
|
||||
# Modbus function codes with write capability
|
||||
MODBUS_WRITE_FUNCS = {5, 6, 15, 16, 22, 23}
|
||||
MODBUS_DIAGNOSTIC_FUNCS = {8, 17, 43}
|
||||
|
||||
|
||||
# ── Purdue Level Classification ─────────────────────────────────────
|
||||
|
||||
@dataclass
|
||||
class PurdueConfig:
|
||||
"""Configuration for Purdue level subnet mappings."""
|
||||
level_0_1: list = field(default_factory=lambda: ["10.10.0.0/16", "192.168.10.0/24"])
|
||||
level_2: list = field(default_factory=lambda: ["10.20.0.0/16", "192.168.20.0/24"])
|
||||
level_3: list = field(default_factory=lambda: ["10.30.0.0/16", "192.168.30.0/24"])
|
||||
level_3_5: list = field(default_factory=lambda: ["172.16.0.0/16"])
|
||||
level_4: list = field(default_factory=lambda: ["10.0.0.0/8"])
|
||||
|
||||
|
||||
def classify_purdue_level(ip_str, config=None):
|
||||
"""Classify IP to Purdue level based on subnet mappings."""
|
||||
if config is None:
|
||||
config = PurdueConfig()
|
||||
|
||||
try:
|
||||
addr = ip_address(ip_str)
|
||||
except ValueError:
|
||||
return "Unknown"
|
||||
|
||||
level_map = [
|
||||
("Level 0-1", config.level_0_1),
|
||||
("Level 2", config.level_2),
|
||||
("Level 3", config.level_3),
|
||||
("Level 3.5", config.level_3_5),
|
||||
("Level 4", config.level_4),
|
||||
]
|
||||
|
||||
for level_name, subnets in level_map:
|
||||
for subnet in subnets:
|
||||
if addr in ip_network(subnet, strict=False):
|
||||
return level_name
|
||||
return "Unknown"
|
||||
|
||||
|
||||
# ── Asset and Finding Models ─────────────────────────────────────────
|
||||
|
||||
@dataclass
|
||||
class OTAsset:
|
||||
ip: str
|
||||
mac: str = ""
|
||||
purdue_level: str = ""
|
||||
protocols: list = field(default_factory=list)
|
||||
roles: list = field(default_factory=list)
|
||||
vendor_hint: str = ""
|
||||
first_seen: str = ""
|
||||
last_seen: str = ""
|
||||
ports_open: list = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Finding:
|
||||
finding_id: str
|
||||
severity: str # critical, high, medium, low
|
||||
title: str
|
||||
description: str
|
||||
affected_assets: list = field(default_factory=list)
|
||||
iec_62443_ref: str = ""
|
||||
nist_800_82_ref: str = ""
|
||||
remediation: str = ""
|
||||
|
||||
|
||||
# ── Passive Discovery Engine ─────────────────────────────────────────
|
||||
|
||||
class OTNetworkDiscovery:
|
||||
"""Passive OT network discovery and analysis engine."""
|
||||
|
||||
def __init__(self, purdue_config=None):
|
||||
self.config = purdue_config or PurdueConfig()
|
||||
self.assets = {}
|
||||
self.connections = defaultdict(lambda: {
|
||||
"count": 0, "protocols": set(), "ports": set(),
|
||||
"first_seen": None, "last_seen": None
|
||||
})
|
||||
self.protocol_stats = defaultdict(int)
|
||||
self.findings = []
|
||||
self.modbus_writes = []
|
||||
self.cross_zone_flows = []
|
||||
|
||||
def process_packet(self, pkt):
|
||||
"""Process a single packet for OT asset and protocol discovery."""
|
||||
if not pkt.haslayer(IP):
|
||||
return
|
||||
|
||||
src_ip = pkt[IP].src
|
||||
dst_ip = pkt[IP].dst
|
||||
timestamp = str(datetime.fromtimestamp(float(pkt.time)))
|
||||
|
||||
# Discover or update assets
|
||||
for ip_addr in (src_ip, dst_ip):
|
||||
if ip_addr not in self.assets:
|
||||
mac = ""
|
||||
if pkt.haslayer(Ether):
|
||||
mac = pkt[Ether].src if ip_addr == src_ip else pkt[Ether].dst
|
||||
self.assets[ip_addr] = OTAsset(
|
||||
ip=ip_addr,
|
||||
mac=mac,
|
||||
purdue_level=classify_purdue_level(ip_addr, self.config),
|
||||
first_seen=timestamp,
|
||||
last_seen=timestamp,
|
||||
)
|
||||
self.assets[ip_addr].last_seen = timestamp
|
||||
|
||||
# Extract port information
|
||||
dst_port = src_port = None
|
||||
if pkt.haslayer(TCP):
|
||||
dst_port = pkt[TCP].dport
|
||||
src_port = pkt[TCP].sport
|
||||
elif pkt.haslayer(UDP):
|
||||
dst_port = pkt[UDP].dport
|
||||
src_port = pkt[UDP].sport
|
||||
|
||||
# Identify OT protocols
|
||||
if dst_port in OT_PROTOCOLS:
|
||||
proto_info = OT_PROTOCOLS[dst_port]
|
||||
proto_name = proto_info["name"]
|
||||
self.protocol_stats[proto_name] += 1
|
||||
|
||||
asset_src = self.assets[src_ip]
|
||||
asset_dst = self.assets[dst_ip]
|
||||
|
||||
if proto_name not in asset_src.protocols:
|
||||
asset_src.protocols.append(proto_name)
|
||||
if proto_name not in asset_dst.protocols:
|
||||
asset_dst.protocols.append(proto_name)
|
||||
|
||||
if "master/client" not in asset_src.roles:
|
||||
asset_src.roles.append("master/client")
|
||||
if "slave/server" not in asset_dst.roles:
|
||||
asset_dst.roles.append("slave/server")
|
||||
|
||||
if dst_port not in asset_dst.ports_open:
|
||||
asset_dst.ports_open.append(dst_port)
|
||||
|
||||
if proto_info["vendor"] != "Open":
|
||||
asset_dst.vendor_hint = proto_info["vendor"]
|
||||
|
||||
# Track Modbus write operations
|
||||
if dst_port == 502 and pkt.haslayer(TCP):
|
||||
payload = bytes(pkt[TCP].payload)
|
||||
if len(payload) > 7:
|
||||
func_code = payload[7]
|
||||
if func_code in MODBUS_WRITE_FUNCS:
|
||||
self.modbus_writes.append({
|
||||
"timestamp": timestamp,
|
||||
"src": src_ip,
|
||||
"dst": dst_ip,
|
||||
"function_code": func_code,
|
||||
})
|
||||
|
||||
# Track connections
|
||||
conn_key = f"{src_ip}->{dst_ip}"
|
||||
conn = self.connections[conn_key]
|
||||
conn["count"] += 1
|
||||
if conn["first_seen"] is None:
|
||||
conn["first_seen"] = timestamp
|
||||
conn["last_seen"] = timestamp
|
||||
if dst_port:
|
||||
conn["ports"].add(dst_port)
|
||||
if dst_port in OT_PROTOCOLS:
|
||||
conn["protocols"].add(OT_PROTOCOLS[dst_port]["name"])
|
||||
|
||||
# Detect cross-zone communication
|
||||
src_level = self.assets[src_ip].purdue_level
|
||||
dst_level = self.assets[dst_ip].purdue_level
|
||||
if src_level != dst_level and "Unknown" not in (src_level, dst_level):
|
||||
self.cross_zone_flows.append({
|
||||
"src": src_ip, "src_level": src_level,
|
||||
"dst": dst_ip, "dst_level": dst_level,
|
||||
"port": dst_port,
|
||||
"protocol": OT_PROTOCOLS.get(dst_port, {}).get("name", f"port/{dst_port}"),
|
||||
"timestamp": timestamp,
|
||||
})
|
||||
|
||||
def analyze_pcap(self, pcap_file):
|
||||
"""Analyze a pcap file for OT network discovery."""
|
||||
print(f"[*] Loading pcap: {pcap_file}")
|
||||
packets = rdpcap(pcap_file)
|
||||
print(f"[*] Processing {len(packets)} packets...")
|
||||
for pkt in packets:
|
||||
self.process_packet(pkt)
|
||||
print(f"[*] Discovery complete: {len(self.assets)} assets found")
|
||||
|
||||
def live_capture(self, interface, duration):
|
||||
"""Perform live passive capture on a network interface."""
|
||||
print(f"[*] Starting live capture on {interface} for {duration}s...")
|
||||
sniff(iface=interface, timeout=duration, prn=self.process_packet, store=0)
|
||||
print(f"[*] Capture complete: {len(self.assets)} assets found")
|
||||
|
||||
def generate_findings(self):
|
||||
"""Analyze discovered data and generate security findings."""
|
||||
finding_counter = 1
|
||||
|
||||
# Finding: Direct enterprise-to-field communication
|
||||
for flow in self.cross_zone_flows:
|
||||
if "Level 4" in flow["src_level"] and "Level 0-1" in flow["dst_level"]:
|
||||
self.findings.append(Finding(
|
||||
finding_id=f"OT-{finding_counter:03d}",
|
||||
severity="critical",
|
||||
title="Direct Enterprise-to-Field Device Communication",
|
||||
description=(
|
||||
f"Traffic observed from {flow['src']} ({flow['src_level']}) "
|
||||
f"to {flow['dst']} ({flow['dst_level']}) via {flow['protocol']}. "
|
||||
"This bypasses all intermediate security zones."
|
||||
),
|
||||
affected_assets=[flow["src"], flow["dst"]],
|
||||
iec_62443_ref="IEC 62443-3-3 SR 5.1 - Network Segmentation",
|
||||
nist_800_82_ref="NIST SP 800-82r3 Section 5.3 - Network Architecture",
|
||||
remediation="Block direct L4-to-L0/1 traffic. Route through DMZ and OT firewall.",
|
||||
))
|
||||
finding_counter += 1
|
||||
|
||||
# Finding: Enterprise bypassing DMZ to reach control
|
||||
for flow in self.cross_zone_flows:
|
||||
if "Level 4" in flow["src_level"] and "Level 2" in flow["dst_level"]:
|
||||
self.findings.append(Finding(
|
||||
finding_id=f"OT-{finding_counter:03d}",
|
||||
severity="critical",
|
||||
title="Enterprise-to-Control Bypass of DMZ",
|
||||
description=(
|
||||
f"Traffic from {flow['src']} ({flow['src_level']}) reaches "
|
||||
f"{flow['dst']} ({flow['dst_level']}) without traversing DMZ."
|
||||
),
|
||||
affected_assets=[flow["src"], flow["dst"]],
|
||||
iec_62443_ref="IEC 62443-3-3 SR 5.2 - Zone Boundary Protection",
|
||||
nist_800_82_ref="NIST SP 800-82r3 Section 5.4 - DMZ Architecture",
|
||||
remediation="Deploy DMZ between enterprise and control zones with bidirectional firewall.",
|
||||
))
|
||||
finding_counter += 1
|
||||
|
||||
# Finding: Unauthenticated Modbus write operations
|
||||
if self.modbus_writes:
|
||||
unique_targets = set(w["dst"] for w in self.modbus_writes)
|
||||
self.findings.append(Finding(
|
||||
finding_id=f"OT-{finding_counter:03d}",
|
||||
severity="critical",
|
||||
title="Unauthenticated Modbus/TCP Write Commands Detected",
|
||||
description=(
|
||||
f"{len(self.modbus_writes)} Modbus write operations observed "
|
||||
f"targeting {len(unique_targets)} devices. Modbus/TCP has no "
|
||||
"native authentication; any network-connected device can modify registers."
|
||||
),
|
||||
affected_assets=list(unique_targets),
|
||||
iec_62443_ref="IEC 62443-3-3 SR 1.1 - Human User Identification and Authentication",
|
||||
nist_800_82_ref="NIST SP 800-82r3 Section 6.2 - Access Control",
|
||||
remediation=(
|
||||
"Deploy Modbus-aware firewall (e.g., Tofino, Claroty Edge) to restrict "
|
||||
"write-capable source addresses. Implement allowlisting for Modbus function codes."
|
||||
),
|
||||
))
|
||||
finding_counter += 1
|
||||
|
||||
# Finding: Unauthenticated industrial protocols
|
||||
for port, info in OT_PROTOCOLS.items():
|
||||
if not info["auth"] and info["name"] in self.protocol_stats:
|
||||
exposed_assets = [
|
||||
ip for ip, asset in self.assets.items()
|
||||
if port in asset.ports_open
|
||||
]
|
||||
if exposed_assets:
|
||||
self.findings.append(Finding(
|
||||
finding_id=f"OT-{finding_counter:03d}",
|
||||
severity="high",
|
||||
title=f"Unauthenticated {info['name']} Protocol in Use",
|
||||
description=(
|
||||
f"{len(exposed_assets)} devices expose {info['name']} (port {port}) "
|
||||
f"which lacks native authentication. Vendor: {info['vendor']}."
|
||||
),
|
||||
affected_assets=exposed_assets,
|
||||
iec_62443_ref="IEC 62443-3-3 SR 1.2 - Software Process Identification",
|
||||
nist_800_82_ref="NIST SP 800-82r3 Section 6.2.1 - Protocol Security",
|
||||
remediation=f"Deploy protocol-aware firewall for {info['name']} traffic inspection.",
|
||||
))
|
||||
finding_counter += 1
|
||||
|
||||
return self.findings
|
||||
|
||||
def export_report(self, output_file):
|
||||
"""Export full assessment results to JSON."""
|
||||
report = {
|
||||
"assessment_date": datetime.now().isoformat(),
|
||||
"summary": {
|
||||
"total_assets": len(self.assets),
|
||||
"total_connections": len(self.connections),
|
||||
"protocols_detected": dict(self.protocol_stats),
|
||||
"cross_zone_flows": len(self.cross_zone_flows),
|
||||
"modbus_write_operations": len(self.modbus_writes),
|
||||
"findings_critical": sum(1 for f in self.findings if f.severity == "critical"),
|
||||
"findings_high": sum(1 for f in self.findings if f.severity == "high"),
|
||||
"findings_medium": sum(1 for f in self.findings if f.severity == "medium"),
|
||||
"findings_low": sum(1 for f in self.findings if f.severity == "low"),
|
||||
},
|
||||
"assets": {ip: asdict(asset) for ip, asset in self.assets.items()},
|
||||
"findings": [asdict(f) for f in self.findings],
|
||||
"cross_zone_flows": self.cross_zone_flows[:100],
|
||||
}
|
||||
|
||||
with open(output_file, "w") as f:
|
||||
json.dump(report, f, indent=2, default=str)
|
||||
print(f"[*] Report saved to: {output_file}")
|
||||
|
||||
def print_summary(self):
|
||||
"""Print assessment summary to console."""
|
||||
print("\n" + "=" * 70)
|
||||
print("OT NETWORK SECURITY ASSESSMENT - SUMMARY")
|
||||
print("=" * 70)
|
||||
|
||||
print(f"\nAssets Discovered: {len(self.assets)}")
|
||||
level_counts = defaultdict(int)
|
||||
for asset in self.assets.values():
|
||||
level_counts[asset.purdue_level] += 1
|
||||
for level in sorted(level_counts.keys()):
|
||||
print(f" {level}: {level_counts[level]} devices")
|
||||
|
||||
print(f"\nProtocol Distribution:")
|
||||
for proto, count in sorted(self.protocol_stats.items(), key=lambda x: -x[1]):
|
||||
print(f" {proto}: {count} packets")
|
||||
|
||||
print(f"\nCross-Zone Flows: {len(self.cross_zone_flows)}")
|
||||
print(f"Modbus Write Operations: {len(self.modbus_writes)}")
|
||||
|
||||
print(f"\nFindings:")
|
||||
severity_counts = defaultdict(int)
|
||||
for f in self.findings:
|
||||
severity_counts[f.severity] += 1
|
||||
for sev in ["critical", "high", "medium", "low"]:
|
||||
if severity_counts[sev]:
|
||||
print(f" {sev.upper()}: {severity_counts[sev]}")
|
||||
|
||||
if self.findings:
|
||||
print(f"\nTop Findings:")
|
||||
for f in self.findings[:10]:
|
||||
print(f" [{f.finding_id}] [{f.severity.upper()}] {f.title}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="OT Network Security Assessment Tool")
|
||||
parser.add_argument("--pcap", help="Path to pcap file for analysis")
|
||||
parser.add_argument("--live-capture", action="store_true", help="Perform live capture")
|
||||
parser.add_argument("--interface", help="Network interface for live capture")
|
||||
parser.add_argument("--duration", type=int, default=300, help="Capture duration in seconds")
|
||||
parser.add_argument("--output", default="ot_assessment_report.json", help="Output report file")
|
||||
args = parser.parse_args()
|
||||
|
||||
discovery = OTNetworkDiscovery()
|
||||
|
||||
if args.pcap:
|
||||
discovery.analyze_pcap(args.pcap)
|
||||
elif args.live_capture and args.interface:
|
||||
discovery.live_capture(args.interface, args.duration)
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
discovery.generate_findings()
|
||||
discovery.print_summary()
|
||||
discovery.export_report(args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user