Introduction
OT (Operational Technology) and ICS (Industrial Control Systems) like SCADA, PLCs, and RTUs control critical infrastructure: energy, water, manufacturing. In 2026, attacks like Triton and Industroyer highlight the need for dedicated cybersecurity distinct from traditional IT. Unlike IT networks, OT prioritizes availability (99.999% uptime) over confidentiality, using legacy unencrypted protocols (Modbus, DNP3, Profibus).
This advanced tutorial guides you step-by-step through defense-in-depth: asset identification, Purdue segmentation, anomaly detection via IDS/IPS, and real-time monitoring. Each step includes complete, functional code for Linux (e.g., Ubuntu Server as an OT bastion). By the end, you'll have a secure stack compliant with NIST 800-82 and IEC 62443, ready for production. Ideal for industrial cybersecurity engineers bookmarking actionable references. (128 words)
Prerequisites
- Linux (Ubuntu 22.04+ LTS) with root privileges
- Advanced knowledge: industrial networks (Purdue Model), ICS protocols (Modbus TCP, DNP3), Python 3.10+
- Installed tools: Nmap 7.9+, Wireshark 4.0+, Scapy 2.5+
- Virtual lab access (VMware/Proxmox) simulating PLCs (e.g., OpenPLC)
- Reading: NIST SP 800-82r3
Installing Essential Tools
#!/bin/bash
apt update && apt upgrade -y
apt install -y nmap wireshark sniffer tcpdump python3 python3-pip snort iptables-persistent netfilter-persistent
pip3 install scapy pyshark
systemctl enable snort
ufw enable
echo 'Installation terminée. Vérifiez avec: nmap --version'This Bash script streamlines installation of key OT/ICS tools: Nmap for passive/aggressive scanning, Wireshark/Scapy for protocol analysis, Snort for IDS, and iptables for segmentation. Run it on a dedicated bastion (Purdue level 3). Tip: Enable 'sniffer' for non-root Wireshark; iptables persistence survives reboots.
Purdue Model and Initial Scanning
Adopt the Purdue Model for segmentation: levels 0-2 (pure OT), 3 (DMZ), 4-5 (IT). Start with passive scanning to avoid DoS on sensitive PLCs. Identify assets: PLCs (port 502 Modbus), HMIs (80/443), RTUs (20000 DNP3).
Nmap Passive and Active ICS Scans
#!/bin/bash
NETWORK="192.168.1.0/24" # Remplacez par votre réseau OT
# Scan passif (TCP SYN stealth)
nmap -sS -T2 --top-ports 100 -O -sV $NETWORK -oN nmap-passif.txt
# Scan UDP pour DNP3/Modbus (agressif, lab only)
nmap -sU --top-ports 50 -p 102,502,20000 --script=modbus-discover $NETWORK -oN nmap-udp.txt
# Scan spécifique ICS
nmap -p 44818,2222,4480 --script enip-info $NETWORK -oN nmap-ics.txt
echo 'Rapports générés. grep "Modbus" nmap-*.txt'OT-adapted Nmap script: -T2 slow timing avoids overloading PLCs; NSE scripts (modbus-discover, enip-info) detect vendors (Schneider, Rockwell). Outputs to files for auditing. Tip: UDP scans are slow (30min+), test in lab only; integrate into cron for periodic monitoring.
Network Segmentation with iptables
Segment Purdue levels: block all IT-to-OT except authorized flows (e.g., encrypted OPC UA). Use VLANs + stateful firewalls. Golden rule: Data Diodes for unidirectional flows (OT-to-IT monitoring only).
iptables Configuration for OT Segmentation
#!/bin/bash
iptables -F
iptables -t nat -F
# Variables
OT_NET="192.168.10.0/24" # Niv 0-2
DMZ_NET="192.168.20.0/24" # Niv 3
IT_NET="192.168.1.0/24" # Niv 4-5
# Politique par défaut: DROP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Autoriser loopback et established
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# OT->DMZ: monitoring SNMP/OPC UA (ports 161,4840)
iptables -A FORWARD -s $OT_NET -d $DMZ_NET -p udp --dport 161 -j ACCEPT
iptables -A FORWARD -s $OT_NET -d $DMZ_NET -p tcp --dport 4840 -j ACCEPT
# Bloquer tout IT->OT
iptables -A FORWARD -s $IT_NET -d $OT_NET -j DROP
netfilter-persistent save
echo 'Segmentation appliquée. iptables -L -v -n'This script enforces zero-trust: default DROP, whitelisted OT-to-DMZ flows only. Conntrack handles replies. Persistent save. Tip: Test with tcpdump before production; adjust ports (Modbus 502 blocked by default for security).
Snort Rules for ICS Detection
alert tcp $OT_NET any -> $OT_NET 502 (msg:"Modbus TCP Scan"; flow:to_server,established; content:"|00 00|"; depth:2; content:"|00 01 00 00|"; distance:2; sid:1000001; rev:1;)
alert udp $OT_NET any -> $OT_NET 20000 (msg:"DNP3 Anomalie"; content:"|05 64|"; sid:1000002; rev:1; classtype:protocol-command-decode;)
alert tcp any any -> $OT_NET 44818 (msg:"EtherNet/IP Unauthorized"; content:"|65 00|"; sid:1000003; rev:1;)
alert icmp $EXTERNAL_NET any -> $OT_NET any (msg:"ICMP Ping Sweep OT"; itype:8; sid:1000004; rev:1; droprule;)
# Activation dans /etc/snort/snort.conf: include $RULE_PATH/ics.rulesCustom Snort rules for ICS protocols: detects Modbus/DNP3 scans, unauthorized EtherNet/IP access. Classtype and droprule for IPS mode. Add to local.rules. Tip: Shallow signatures (no deep payload inspection for sensitive data); test with Scapy replay.
Deploying IDS/IPS and Monitoring
Configure Snort in IDS mode (SPAN port) or IPS (inline). Integrate with ELK for SIEM. For OT, prioritize behavioral anomalies: traffic volume spikes signal potential DoS.
Python Script for ICS Traffic Monitoring with Scapy
from scapy.all import *
import sys
import json
from collections import defaultdict
OT_NET = "192.168.10.0/24"
stats = defaultdict(int)
def packet_handler(pkt):
if IP in pkt and pkt[IP].src.startswith('192.168.10.'):
proto = pkt[IP].proto
sport = pkt[TCP].sport if TCP in pkt else pkt[UDP].sport
stats[f'{proto}:{sport}'] += 1
if stats[f'{proto}:{sport}'] > 100: # Anomalie: >100 pkts/sec
print(f'ALERTE: Flood {proto}:{sport} from {pkt[IP].src}')
print('Monitoring OT démarré. Ctrl+C pour arrêter.')
sniff(filter=f'net {OT_NET}', prn=packet_handler, store=0)
# Exemple sortie JSON: with open('stats.json', 'w') as f: json.dump(stats, f)Real-time Scapy script: sniffs OT traffic, counts by proto/port, alerts on floods (>100 pkts). Stateful without heavy DB. Run: python3 ics-monitor.py. Tip: Requires root; BPF filter optimizes performance (10Gbps+ capable).
Suricata YAML Configuration for ICS
%YAML 1.1
---
vars:
address-groups:
OT_NET: "[192.168.10.0/24]"
EXTERNAL_NET: "any"
rule-files:
- /etc/suricata/rules/ics.rules
outputs:
- eve-log:
enabled: yes
filetype: regular
filename: eve.json
types:
- alert:
payload: yes
packet: yes
- anomaly:
enabled: yes
af-packet:
- interface: eth1 # SPAN OT
threads: auto
cluster-id: 99
cluster-type: cluster_flow
defrag: yesSuricata config (Snort alternative): network groups, EVE JSON for ELK, af-packet for multithreaded OT performance. Includes ics.rules (like Snort). Tip: defrag=yes for Modbus fragments; integrate ICS-specific Kibana dashboard.
Best Practices
- Patch management: Air-gapped, lab-tested (e.g., PLC snapshots)
- Zero Trust: MFA on HMIs, mTLS certs for OPC UA
- Offline backups: 3-2-1 rule, tested quarterly
- Incident Response: IEC 62443 playbook, tabletop exercises
- Compliance: Audit NIST 800-82, integrate OT Purple Team
Common Mistakes to Avoid
- Aggressive scanning in production: causes PLC shutdown (use -T1/-sn)
- Ignoring legacy protocols: Cleartext Modbus = easy MITM (enforce IPsec VPN)
- No centralized logging: Lost Snort alerts (ELK mandatory)
- Supply chain neglect: Verify PLC firmware (SBOM + signatures)
Next Steps
Dive into our Learni trainings on OT/ICS cybersecurity. Resources: S4x25 conference, MITRE ICS ATT&CK, GrassMarlin (asset mapping), Nozomi Guardian (OT SIEM). Free lab: ICS Village GitHub.