Challenge 9: Network segmentation (IEC 62443 zones)¶
Objective: Implement zone-based architecture isolating corporate IT, SCADA, control systems, and safety systems using runtime firewall rules and IDS/IPS prevention.
Category: Architecture & Defence in Depth
Difficulty: Intermediate
Time Required: 45-60 minutes
Learning outcomes¶
By completing this challenge, you will:
Understand IEC 62443 zone-based network architecture
Implement firewall policies between security zones
Use IDS/IPS for active threat blocking
Respond to incidents using Blue Team CLI
Understand config (persistent) vs runtime (temporary) changes
Test attacks against your defences to verify effectiveness
Background: IEC 62443 zone model¶
Industrial control systems use hierarchical zones based on the Purdue Model:
┌─────────────────────────────────────────────────────┐
│ ENTERPRISE ZONE (Level 4) │
│ • Historians, business systems, analytics │
│ • Security Level: Medium (IT systems) │
└──────────────────┬──────────────────────────────────┘
│ Conduit (restricted)
┌──────────────────▼──────────────────────────────────┐
│ DMZ (Level 3.5) │
│ • Jump servers, data diodes, protocol converters │
│ • Security Level: High (boundary protection) │
└──────────────────┬──────────────────────────────────┘
│ Conduit (restricted)
┌──────────────────▼──────────────────────────────────┐
│ OPERATIONS ZONE (Level 3) │
│ • SCADA servers, HMIs, engineering workstations │
│ • Security Level: High (supervisory control) │
└──────────────────┬──────────────────────────────────┘
│ Conduit (restricted)
┌──────────────────▼──────────────────────────────────┐
│ CONTROL ZONE (Levels 0-2) │
│ • PLCs, RTUs, safety systems, field devices │
│ • Security Level: Critical (process control) │
└─────────────────────────────────────────────────────┘
Key Principle: Traffic between zones must be explicitly allowed. Default deny.
Initial state (vulnerable)¶
Before hardening, the simulation has:
Zones defined in
config/network.yml❌ Segmentation DISABLED:
enabled: false❌ Firewall has NO RULES: Everything allowed
❌ IDS in detection-only mode: Alerts but doesn’t block
❌ All zones can reach all zones
Result: Attacker from enterprise zone can directly access control zone PLCs.
Part 1: Configuration changes (require restart)¶
Configuration changes establish baseline security that persists across restarts.
Step 1.1: Enable network segmentation¶
Edit config/network.yml:
segmentation:
enabled: true # ← Change from false
mode: strict # Full Purdue model enforcement
What this does:
Enables zone boundary checks in NetworkSimulator
Requires explicit inter-zone policies to allow traffic
Default deny for undefined zone-to-zone paths
Step 1.2: Configure baseline firewall rules¶
Edit config/firewall.yml:
default_action: deny # ← Change from allow
baseline_rules:
# Allow SCADA to Control Zone (normal operations)
- name: "Baseline: SCADA to Control"
action: ALLOW
priority: 50
source_zone: operations_zone
dest_zone: control_zone
enabled: true
description: "SCADA servers must poll PLCs"
# Allow Historian to SCADA (data collection)
- name: "Baseline: Historian to SCADA"
action: ALLOW
priority: 50
source_zone: enterprise_zone
dest_zone: operations_zone
enabled: true
description: "Historian collects SCADA data"
# Block Enterprise to Control (defence in depth)
- name: "Baseline: Block Enterprise to Control"
action: DROP
priority: 100
source_zone: enterprise_zone
dest_zone: control_zone
enabled: true
description: "No direct enterprise access to control systems"
Step 1.3: Configure IDS/IPS baseline¶
Edit config/ids_ips.yml:
prevention_mode: false # Keep false initially
auto_block_on_critical: true
permanent_blocked_ips: []
detection_thresholds:
scan_threshold: 5
scan_time_window: 60.0
Note: We start with IPS disabled so students see detection-only mode first.
Step 1.4: Restart simulator¶
python tools/simulator_manager.py
Baseline rules are now active.
Part 2: Verify baseline security¶
Step 2.1: Check security status¶
python tools/blue_team.py status
Expected output:
Firewall:
Default Action: deny
Active Rules: 3
Connections Checked: 0
Connections Blocked: 0
IDS/IPS:
Mode: IDS (Detection Only)
Blocked IPs: 0
Active Alerts: 0
Step 2.2: List firewall rules¶
python tools/blue_team.py firewall list-rules -v
Expected: Baseline rules loaded from config.
Step 2.3: Test legitimate traffic (should work)¶
# SCADA → Control Zone (allowed by baseline)
python scripts/assessment/scada_polling.py
# Expected: Success - baseline rule allows this
Step 2.4: Test unauthorised traffic (should fail)¶
# Enterprise → Control Zone (blocked by baseline)
# Simulate attack from enterprise workstation
python scripts/vulns/modbus_write.py --target hex_turbine_plc
# Expected: Connection denied by firewall
Verify in logs:
tail -f logs/simulation.log | grep "denied by firewall"
Part 3: Runtime incident response¶
Runtime changes are immediate but lost on restart. Used for incident response.
Scenario: Active attack detected¶
Context: IDS detects port scanning from IP 192.168.1.100
Step 3.1: Enable IPS mode (immediate)¶
python tools/blue_team.py ids enable-ips
Effect:
IDS switches to prevention mode
Future CRITICAL alerts will auto-block source IPs
Change is immediate (no restart)
Step 3.2: Block attacker IP (immediate)¶
python tools/blue_team.py ids block-ip 192.168.1.100 "Port scanning detected"
Effect:
All connections from
192.168.1.100are DROPPEDAttacker sees timeout or connection refused
Logged to audit trail
Step 3.3: Add emergency firewall rule (immediate)¶
python tools/blue_team.py firewall add-rule \
--name "Emergency: Block compromised segment" \
--action DROP \
--source-network engineering_network \
--dest-zone control_zone \
--priority 1 \
--reason "Suspected compromise in engineering network"
Effect:
Entire engineering network blocked from control zone
Rule has priority 1 (highest - checked first)
Immediate enforcement
Step 3.4: Verify blocks¶
# Check IPS blocks
python tools/blue_team.py ids list-blocked
# Check firewall rules
python tools/blue_team.py firewall list-rules
# Try attack again - should fail
python scripts/vulns/modbus_write.py --target hex_turbine_plc
# Expected: Connection refused
Step 3.5: Check statistics¶
python tools/blue_team.py status
Expected:
Firewall:
Active Rules: 4 (3 baseline + 1 runtime)
Connections Blocked: 5
IDS/IPS:
Mode: IPS (BLOCKING)
Blocked IPs: 1
Part 4: Zone isolation testing¶
Test matrix: Can zone A reach zone B?¶
From ↓ To → |
Enterprise |
Operations |
Control |
DMZ |
|---|---|---|---|---|
Enterprise |
✓ |
✓ (historian) |
✗ |
? |
Operations |
✗ |
✓ |
✓ (SCADA) |
? |
Control |
✗ |
✗ |
✓ |
✗ |
DMZ |
? |
? |
? |
? |
Test 1: Enterprise → Control (should fail)¶
# Attempt Modbus write from enterprise zone
python scripts/vulns/modbus_write.py \
--target hex_turbine_plc \
--register 0 \
--value 1234
# Expected: Connection denied by firewall
# Check logs:
tail -f logs/simulation.log | grep "firewall"
Test 2: Operations → Control (should work)¶
# SCADA polling (normal operations)
python scripts/assessment/scada_polling.py
# Expected: Success - baseline rule allows
Test 3: Control → Enterprise (should fail)¶
# PLCs should not initiate outbound connections
# (Normally PLCs don't initiate, but test the policy)
# Expected: Denied - no policy allows control → enterprise
Part 5: Make runtime changes permanent¶
After incident response, make temporary blocks permanent.
Step 5.1: Add to config files¶
For IP blocks:
Edit config/ids_ips.yml:
permanent_blocked_ips:
- "192.168.1.100" # ← Add attacker IP
For firewall rules:
Edit config/firewall.yml:
baseline_rules:
# ... existing rules ...
# Add emergency rule as baseline
- name: "Block compromised segment"
action: DROP
source_network: engineering_network
dest_zone: control_zone
priority: 1
enabled: true
description: "Permanent block after incident #2026-042"
Step 5.2: Restart and verify¶
# Restart simulator
python tools/simulator_manager.py
# In another terminal, check rules loaded
python tools/blue_team.py firewall list-rules
# Verify permanent block
python tools/blue_team.py ids list-blocked
Expected: Runtime rules now appear as baseline rules after restart.
Part 6: Advanced scenarios¶
Scenario A: Lateral movement prevention¶
Attack: Attacker compromises HMI workstation, attempts to reach PLCs.
Response:
# 1. Block compromised workstation
python tools/blue_team.py ids block-ip 10.20.2.15 "Compromised HMI"
# 2. Isolate entire HMI network temporarily
python tools/blue_team.py firewall add-rule \
--name "Isolate HMI network" \
--action DROP \
--source-network hmi_network \
--dest-zone control_zone \
--priority 1 \
--reason "HMI compromise containment"
# 3. Allow SCADA exception (keep operations running)
python tools/blue_team.py firewall add-rule \
--name "SCADA exception" \
--action ALLOW \
--source-ip 10.20.1.10 \
--dest-zone control_zone \
--priority 10 \
--reason "SCADA must continue operations"
# 4. Verify containment
python scripts/vulns/modbus_write.py --target hex_turbine_plc
# Expected: Connection denied
Scenario B: Emergency access¶
Need: Engineer needs temporary access from enterprise zone for emergency maintenance.
Response:
# 1. Create temporary allow rule (high priority)
python tools/blue_team.py firewall add-rule \
--name "TEMP: Emergency engineering access" \
--action ALLOW \
--source-ip 10.50.1.42 \
--dest-zone control_zone \
--priority 5 \
--user alice \
--reason "Emergency turbine maintenance - ticket INC-123"
# 2. After maintenance, remove rule
python tools/blue_team.py firewall list-rules
# Note rule ID (e.g., FW-00042)
python tools/blue_team.py firewall remove-rule FW-00042 \
--reason "Maintenance complete"
Scenario C: Progressive lockdown¶
Context: Suspected widespread compromise.
Response:
# Phase 1: Block known attackers
python tools/blue_team.py ids block-ip 192.168.1.100 "Attacker 1"
python tools/blue_team.py ids block-ip 192.168.1.101 "Attacker 2"
# Phase 2: Lockdown entire enterprise zone
python tools/blue_team.py firewall add-rule \
--name "LOCKDOWN: Block all enterprise" \
--action DROP \
--source-zone enterprise_zone \
--dest-zone control_zone \
--priority 1 \
--reason "Widespread compromise suspected"
# Phase 3: Enable IPS for auto-blocking
python tools/blue_team.py ids enable-ips
# Phase 4: Allow critical services only
python tools/blue_team.py firewall add-rule \
--name "Critical: Historian only" \
--action ALLOW \
--source-ip 10.50.1.10 \
--dest-zone operations_zone \
--priority 5 \
--reason "Historian must collect data"
# Verify lockdown
python tools/blue_team.py status
Part 7: Config vs runtime changes¶
Understanding the difference¶
Aspect |
Config Changes |
Runtime Changes |
|---|---|---|
File Location |
|
Command-line (CLI) |
Takes Effect |
After restart |
Immediately |
Persistence |
Permanent |
Lost on restart |
Use Case |
Baseline security |
Incident response |
Example |
Default deny policy |
Block attacker IP |
Audit Trail |
Config file changes |
ICSLogger events |
When to use what¶
Use Config (Baseline Security):
Default firewall policy (allow/deny)
Normal operational rules (SCADA → PLCs)
Permanent blocks (known bad IPs)
IPS mode default (detection vs prevention)
Network segmentation enabled/disabled
Use Runtime (Incident Response):
Block attacker during active attack
Emergency firewall rules
Temporary access grants
Quick containment actions
Testing security changes
Making runtime → permanent¶
Workflow:
Respond to incident using CLI (immediate)
Verify effectiveness
Add to config file (permanent)
Restart simulator
Verify config loaded
Example:
# Runtime (immediate)
python tools/blue_team.py ids block-ip 192.168.1.100 "Attacker"
# Later, make permanent:
# Edit config/ids_ips.yml, add to permanent_blocked_ips
# Restart: python tools/simulator_manager.py
Testing defences¶
Test 1: Unauthorised zone access¶
Objective: Verify enterprise zone cannot reach control zone.
# From enterprise zone perspective
python scripts/vulns/modbus_write.py --target hex_turbine_plc
# Expected: Connection denied
# Verify: grep "denied by firewall" logs/simulation.log
Test 2: IPS auto-block¶
Objective: Verify IPS auto-blocks on CRITICAL alerts.
# Enable IPS
python tools/blue_team.py ids enable-ips
# Trigger detection (port scan)
nmap -p 10502-10510 localhost
# Check if auto-blocked
python tools/blue_team.py ids list-blocked
# Try connecting again
python scripts/vulns/modbus_write.py --target hex_turbine_plc
# Expected: Connection refused (IP blocked)
Test 3: Firewall rule priority¶
Objective: Verify higher priority rules checked first.
# Add allow rule (priority 50)
python tools/blue_team.py firewall add-rule \
--name "Allow test" \
--action ALLOW \
--source-ip 127.0.0.1 \
--priority 50
# Add deny rule (priority 1 - higher)
python tools/blue_team.py firewall add-rule \
--name "Deny test" \
--action DROP \
--source-ip 127.0.0.1 \
--priority 1
# Test connection
python scripts/vulns/modbus_write.py --target hex_turbine_plc
# Expected: DROPPED (priority 1 rule wins)
Common issues and solutions¶
Issue 1: “Connection denied by firewall” but should be allowed¶
Cause: No rule allowing the traffic.
Solution:
# Check existing rules
python tools/blue_team.py firewall list-rules -v
# Add allow rule with correct zones
python tools/blue_team.py firewall add-rule \
--name "Allow legitimate traffic" \
--action ALLOW \
--source-zone operations_zone \
--dest-zone control_zone \
--priority 50
Issue 2: Runtime changes lost after restart¶
Cause: This is expected behaviour.
Solution: Add to config files for persistence.
Issue 3: Legitimate operations blocked¶
Cause: Default deny policy or overly broad block rule.
Solution:
# Add exception with high priority
python tools/blue_team.py firewall add-rule \
--name "SCADA exception" \
--action ALLOW \
--source-ip 10.20.1.10 \
--priority 5
TL;DR¶
Defence in Depth: Multiple layers (zones, firewall, IPS) provide redundancy
Default Deny: Explicitly allow only required traffic
Zone Isolation: Control zone is most critical, most isolated
Incident Response: Runtime changes provide immediate protection
Config Management: Baseline security persists across restarts
Audit Trail: All changes logged for forensics
Testing Required: Always verify defences actually block attacks
Assessment checklist¶
Network segmentation enabled in config
Firewall default action set to deny
Baseline rules configured for normal operations
IPS mode configurable via CLI
Runtime IP blocking works immediately
Emergency firewall rules can be added
Attacks from enterprise zone blocked
SCADA operations still functional
Runtime changes tested and verified
Permanent rules added to config files
Audit trail shows all security actions
Documentation written for incident response
Next steps¶
After completing this challenge:
Combine with Challenge 2 (RBAC) - Who can modify firewall rules?
Add Challenge 5 (Protocol Filtering) - Block specific Modbus function codes
Implement Challenge 6 (Dual Authorisation) - Critical changes need two approvals
Review audit logs for security events
Create incident response playbooks