Skip to content
Learni
View all tutorials
Sécurité OT/ICS

How to Secure OT/ICS Systems in 2026

Lire en français

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

install-ot-tools.sh
#!/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

nmap-ics-scan.sh
#!/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

iptables-ot-segment.sh
#!/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

snort-ics.rules
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.rules

Custom 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

ics-monitor.py
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

suricata-ics.yaml
%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: yes

Suricata 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.