Mapped every skill to NIST CSF 2.0 subcategory IDs (GV/ID/PR/DE/RS/RC functions)
based on subdomain and content analysis. Restores 11 skills corrupted during
prior rebase, re-enriching with ATLAS, D3FEND, NIST AI RMF, and CSF 2.0 fields.
All 754 skills now carry structured mappings for all 5 security frameworks:
- MITRE ATT&CK (in tags)
- MITRE ATLAS v5.5 (atlas_techniques)
- MITRE D3FEND v1.3 (d3fend_techniques)
- NIST AI RMF 1.0 (nist_ai_rmf)
- NIST CSF 2.0 (nist_csf)
This skill covers deploying anomaly detection systems for industrial control environments using machine learning models trained on OT network baselines, physics-based process models, and behavioral analysis of industrial protocol communications. It addresses building normal behavior profiles for SCADA polling patterns, detecting deviations in Modbus/DNP3/OPC UA traffic, identifying rogue devices, and correlating network anomalies with physical process data from historians.
cybersecurity
ot-ics-security
ot-security
ics
scada
industrial-control
iec62443
anomaly-detection
machine-learning
1.0.0
mahipal
Apache-2.0
AML.T0043
AML.T0018
MEASURE-2.7
MEASURE-2.5
MAP-5.1
PR.IR-01
DE.CM-01
ID.AM-05
GV.OC-02
Detecting Anomalies in Industrial Control Systems
When to Use
When deploying continuous monitoring for OT environments that lack intrusion detection
When building behavior-based detection to complement signature-based IDS in OT networks
When establishing baselines for deterministic SCADA communications to detect deviations
When integrating machine learning anomaly detection with OT security monitoring platforms
When investigating alerts from Nozomi Guardian or Dragos Platform that require deeper analysis
Do not use for signature-based detection of known exploits (see detecting-attacks-on-scada-systems), for IT network anomaly detection without OT protocols, or as a replacement for process safety systems (SIS).
Prerequisites
Passive network monitoring sensors on OT network SPAN/TAP ports
Minimum 2-4 weeks of baseline traffic capture during normal operations
Python 3.9+ with scikit-learn, numpy, pandas for ML model training
Process historian access for physical process correlation data
Understanding of normal operational patterns including shift changes, batch processes, and maintenance windows
Workflow
Step 1: Build Multi-Dimensional Baseline Model
Capture and model the deterministic behavior of ICS communications across multiple dimensions: timing, protocol behavior, and network topology.
#!/usr/bin/env python3"""ICS Anomaly Detection System.
Builds multi-dimensional baselines from OT network traffic and
detects anomalies using statistical and machine learning methods.
Designed for deterministic SCADA communication patterns.
"""importjsonimportsysimporttimeimportwarningsfromcollectionsimportdefaultdictfromdatetimeimportdatetime,timedeltafromdataclassesimportdataclass,fieldimportnumpyasnpimportpandasaspdfromsklearn.ensembleimportIsolationForestfromsklearn.preprocessingimportStandardScalerwarnings.filterwarnings("ignore")@dataclassclassCommunicationProfile:"""Profile for a single master-slave communication pair."""src_ip:strdst_ip:strprotocol:strport:intavg_interval_ms:float=0.0std_interval_ms:float=0.0avg_payload_size:float=0.0function_codes:dict=field(default_factory=dict)packets_per_minute:float=0.0first_seen:str=""last_seen:str=""classICSAnomalyDetector:"""Multi-dimensional anomaly detection for ICS environments."""def__init__(self):self.profiles={}self.topology_baseline=set()self.timing_model=Noneself.isolation_forest=Noneself.scaler=StandardScaler()self.anomalies=[]self.training_data=[]defbuild_baseline_from_pcap(self,pcap_data):"""Build baselines from parsed pcap data (list of flow records)."""print("[*] Building ICS communication baselines...")forflowinpcap_data:key=f"{flow['src']}->{flow['dst']}:{flow['port']}"ifkeynotinself.profiles:self.profiles[key]=CommunicationProfile(src_ip=flow["src"],dst_ip=flow["dst"],protocol=flow.get("protocol","TCP"),port=flow["port"],first_seen=flow.get("timestamp",""),)profile=self.profiles[key]profile.last_seen=flow.get("timestamp","")# Track function codes for industrial protocolsfc=flow.get("function_code")iffcisnotNone:profile.function_codes[fc]=profile.function_codes.get(fc,0)+1# Add to topology baselineself.topology_baseline.add((flow["src"],flow["dst"],flow["port"]))# Calculate interval statisticsself._calculate_timing_stats(pcap_data)print(f" Communication pairs: {len(self.profiles)}")print(f" Topology entries: {len(self.topology_baseline)}")def_calculate_timing_stats(self,flows):"""Calculate packet timing statistics per communication pair."""timestamps=defaultdict(list)forflowinflows:key=f"{flow['src']}->{flow['dst']}:{flow['port']}"ts=flow.get("timestamp_epoch")ifts:timestamps[key].append(ts)forkey,ts_listintimestamps.items():ifkeyinself.profilesandlen(ts_list)>1:ts_sorted=sorted(ts_list)intervals=[(ts_sorted[i+1]-ts_sorted[i])*1000foriinrange(len(ts_sorted)-1)]self.profiles[key].avg_interval_ms=np.mean(intervals)self.profiles[key].std_interval_ms=np.std(intervals)duration_min=(ts_sorted[-1]-ts_sorted[0])/60ifduration_min>0:self.profiles[key].packets_per_minute=len(ts_list)/duration_mindeftrain_isolation_forest(self,features_df):"""Train Isolation Forest model on feature vectors from baseline traffic."""print("[*] Training Isolation Forest model...")feature_cols=["interval_ms","payload_size","packets_per_window","unique_func_codes","new_connection_flag",]available_cols=[cforcinfeature_colsifcinfeatures_df.columns]X=features_df[available_cols].fillna(0).valuesX_scaled=self.scaler.fit_transform(X)self.isolation_forest=IsolationForest(n_estimators=200,contamination=0.01,# Expect 1% anomaly rate in baselinerandom_state=42,n_jobs=-1,)self.isolation_forest.fit(X_scaled)scores=self.isolation_forest.decision_function(X_scaled)print(f" Model trained on {len(X)} samples")print(f" Anomaly score range: [{scores.min():.4f}, {scores.max():.4f}]")print(f" Threshold: {np.percentile(scores,1):.4f}")defdetect_topology_anomaly(self,src_ip,dst_ip,port):"""Detect new/unauthorized communication pairs."""if(src_ip,dst_ip,port)notinself.topology_baseline:return{"type":"NEW_COMMUNICATION_PAIR","severity":"high","detail":f"New connection: {src_ip} -> {dst_ip}:{port} not in baseline","recommendation":"Verify if this is an authorized new device or configuration change",}returnNonedefdetect_timing_anomaly(self,src_ip,dst_ip,port,interval_ms):"""Detect polling interval deviations."""key=f"{src_ip}->{dst_ip}:{port}"profile=self.profiles.get(key)ifprofileandprofile.std_interval_ms>0:z_score=abs(interval_ms-profile.avg_interval_ms)/profile.std_interval_msifz_score>4.0:return{"type":"TIMING_ANOMALY","severity":"medium","detail":(f"Interval {interval_ms:.1f}ms deviates from baseline "f"{profile.avg_interval_ms:.1f}ms (z-score: {z_score:.1f})"),"recommendation":"Check for network congestion, device malfunction, or MITM attack",}returnNonedefdetect_function_code_anomaly(self,src_ip,dst_ip,port,func_code):"""Detect unauthorized Modbus/DNP3 function codes."""key=f"{src_ip}->{dst_ip}:{port}"profile=self.profiles.get(key)ifprofileandfunc_codenotinprofile.function_codes:severity="critical"iffunc_codein{5,6,15,16,8}else"high"return{"type":"UNAUTHORIZED_FUNCTION_CODE","severity":severity,"detail":(f"Function code {func_code} from {src_ip} to {dst_ip}:{port} "f"not in baseline. Allowed: {list(profile.function_codes.keys())}"),"recommendation":"Investigate source - possible command injection attack",}returnNonedefanalyze_flow(self,flow):"""Analyze a single network flow against all detection models."""results=[]# Topology checktopo=self.detect_topology_anomaly(flow["src"],flow["dst"],flow["port"])iftopo:results.append(topo)# Timing checkif"interval_ms"inflow:timing=self.detect_timing_anomaly(flow["src"],flow["dst"],flow["port"],flow["interval_ms"])iftiming:results.append(timing)# Function code checkif"function_code"inflow:fc=self.detect_function_code_anomaly(flow["src"],flow["dst"],flow["port"],flow["function_code"])iffc:results.append(fc)self.anomalies.extend(results)returnresultsdefgenerate_report(self):"""Generate anomaly detection report."""print(f"\n{'='*60}")print(f"ICS ANOMALY DETECTION REPORT")print(f"{'='*60}")print(f"Baseline Profiles: {len(self.profiles)}")print(f"Anomalies Detected: {len(self.anomalies)}")severity_counts=defaultdict(int)forainself.anomalies:severity_counts[a["severity"]]+=1forsevin["critical","high","medium","low"]:ifseverity_counts[sev]:print(f" {sev.upper()}: {severity_counts[sev]}")forainself.anomalies[:20]:print(f"\n [{a['severity'].upper()}] {a['type']}")print(f" {a['detail']}")if__name__=="__main__":print("ICS Anomaly Detection System")print("Load baseline data and call analyze_flow() for real-time detection")
Key Concepts
Term
Definition
Deterministic Traffic
ICS networks exhibit highly predictable communication patterns where the same master polls the same slaves at fixed intervals with identical function codes
Isolation Forest
Unsupervised machine learning algorithm that isolates anomalies by randomly partitioning feature space, effective for OT traffic with low anomaly rates
Polling Interval
Time between consecutive SCADA master requests to a slave device, typically fixed and configurable (100ms to 10s)
Function Code Allowlist
Set of permitted industrial protocol operations for each communication pair, enforced by anomaly detection rules
Topology Baseline
Complete map of all authorized device-to-device communication paths in the OT network
Physics-Based Detection
Using physical process models (thermodynamics, fluid dynamics) to detect attacks that manipulate the process while spoofing sensor data
Tools & Systems
Nozomi Networks Guardian: OT anomaly detection with AI-powered baseline learning and industrial protocol analysis
Dragos Platform: Threat detection using behavioral analytics and threat intelligence specific to ICS environments
Scikit-learn: Python ML library with Isolation Forest, One-Class SVM, and Local Outlier Factor for anomaly detection
Zeek with OT plugins: Network security monitor with Modbus, DNP3, and BACnet protocol analyzers for baseline building