Challenge 7: Encrypt SCADA Communications¶
Objective¶
Deploy OPC UA encryption on SCADA communications. You will first observe cleartext OPC UA traffic, then generate certificates, enable encryption enforcement, and verify that unauthorised clients can no longer connect.
Background¶
OPC UA (OPC Unified Architecture) is the primary protocol used for SCADA data access in modern industrial control systems. By default, many OPC UA servers are configured with no encryption and anonymous access allowed - just like a real-world legacy deployment that nobody has bothered to secure.
In this challenge, you will:
Probe insecure OPC UA servers to see what an attacker sees
Generate X.509 certificates for each server
Enable global encryption enforcement
Verify that the attack no longer works
Prerequisites¶
Challenges 2 (RBAC) and 3 (Logging) completed (recommended but not required)
Familiarity with OPC UA concepts (endpoints, security policies, certificates)
The simulator is running or can be restarted
Important: Restart required¶
Unlike firewall rules (Challenge 9) or RBAC enforcement (Challenge 2), which can be toggled at runtime, OPC UA security requires a simulator restart. This is realistic: you cannot swap TLS certificates on a live OPC UA server without restarting the connection. Plan your maintenance window accordingly.
This is a deliberate design choice in the workshop. In production environments, you would schedule a maintenance window, notify operators, and coordinate the changeover.
Part 1: Reconnaissance (attack phase)¶
Step 1: Check current security status¶
python tools/blue_team.py opcua status
You should see:
Enforcement: DISABLED (VULNERABLE)
All servers using policy None
No certificates generated
Step 2: Probe the insecure SCADA server¶
The primary SCADA server runs OPC UA on port 4840 with no encryption:
# Scan for OPC UA services
nmap -p 4840,4841,4850 localhost
# Use the asyncua library to browse the server
python -c "
import asyncio
from asyncua import Client
async def probe():
client = Client('opc.tcp://localhost:4840/')
await client.connect()
root = client.nodes.root
children = await root.get_children()
print(f'Connected! Found {len(children)} root nodes')
print(f'Security policy: {client.security_policy}')
await client.disconnect()
asyncio.run(probe())
"
What you can observe¶
No authentication required - anonymous access works
No encryption - data travels in cleartext
Full access - can browse the entire address space
Read/write - can modify process variables
This is the state of many real SCADA systems. An attacker on the network can read sensor values, modify setpoints, and generally cause havoc.
Part 2: Generate Certificates¶
Step 1: List Available OPC UA Servers¶
python tools/blue_team.py opcua list-certs
or
python tools/generate_opcua_certificates.py --list
This shows all OPC UA servers discovered from config/devices.yml.
Step 2: Generate certificates for all Servers¶
python tools/blue_team.py opcua generate-certs
or equivalently:
python tools/generate_opcua_certificates.py
This generates self-signed X.509 certificates for each OPC UA server, stored in
the certs/ directory.
Step 3: Verify Certificates¶
# List all certificates
python tools/blue_team.py opcua list-certs
# Validate a specific certificate
python tools/blue_team.py opcua validate-cert scada_server_primary
Check that:
Each server has both a
.crtand.keyfileThe private key matches the certificate
The certificate validity period is correct
Part 3: Enable encryption¶
Step 1: Edit the configuration¶
Open config/opcua_security.yml and change:
enforcement_enabled: true
This tells the simulator to override per-device OPC UA settings with the global
security policy (Aes256_Sha256_RsaPss by default).
Step 2: Restart the simulator¶
# Stop the simulator (Ctrl+C if running)
# Restart with new configuration
python tools/simulator_manager.py
Step 3: verify enforcement¶
python tools/blue_team.py opcua status
You should now see:
Enforcement: ENABLED
All servers using policy Aes256_Sha256_RsaPss
Certificates found for each server
Part 4: Verify protection¶
Step 1: Try the same attack¶
python -c "
import asyncio
from asyncua import Client
async def probe():
client = Client('opc.tcp://localhost:4840/')
try:
await client.connect()
print('Connected (this should not happen)')
await client.disconnect()
except Exception as e:
print(f'Connection rejected: {e}')
print('Encryption is working!')
asyncio.run(probe())
"
The connection should be rejected because the client does not have a valid
certificate or is trying to use the None security policy.
Step 2: Check the audit log¶
python tools/blue_team.py audit query --category security --severity WARNING
Look for events related to rejected OPC UA connections.
Configuration reference¶
config/opcua_security.yml¶
Setting |
Default |
Description |
|---|---|---|
|
|
Enable global OPC UA security enforcement |
|
|
Policy applied when enforcement enabled |
|
|
Directory for certificate storage |
|
|
Certificate validity (1 year) |
|
|
RSA key size in bits |
|
|
Allow anonymous connections when enforcement enabled |
|
|
Per-server customisation |
Security policies¶
Policy |
Encryption |
Recommended |
|---|---|---|
|
No encryption |
No (insecure) |
|
AES-256 + SHA-256 |
Minimum |
|
AES-128 + RSA-OAEP |
Good |
|
AES-256 + RSA-PSS |
Best |
Per-server overrides¶
You can apply different policies to different servers:
server_overrides:
historian_primary:
security_policy: Aes128_Sha256_RsaOaep # Lower overhead for bulk data
scada_server_backup:
security_policy: Basic256Sha256
Blue Team CLI commands¶
# View security status
python tools/blue_team.py opcua status
# Generate certificates
python tools/blue_team.py opcua generate-certs
python tools/blue_team.py opcua generate-certs --server scada_server_primary
python tools/blue_team.py opcua generate-certs --force # Overwrite existing
# List certificates
python tools/blue_team.py opcua list-certs
# Validate specific certificate
python tools/blue_team.py opcua validate-cert scada_server_primary
# Overall security status (includes OPC UA)
python tools/blue_team.py status
Discussion¶
Why does OPC UA security require a restart? Unlike firewall rules that filter packets, TLS/certificate settings are established during the connection handshake. Changing them requires tearing down and re-establishing all connections.
Self-signed vs CA-signed certificates. Our workshop uses self-signed certificates, which means each client must explicitly trust each server certificate. In a production environment, you would use a Certificate Authority (CA) to issue certificates, allowing any client that trusts the CA to connect to any server with a CA-signed certificate.
Certificate lifecycle. Certificates expire. What happens when a certificate expires on a running SCADA system? How would you plan certificate renewal without disrupting operations?
Performance impact. Encryption adds computational overhead. For a SCADA system polling thousands of tags per second, what is the performance impact? Is it worth the trade-off? (Hint: modern CPUs with AES-NI make this negligible.)
Defence in depth. How does OPC UA encryption complement the other defences (firewall, RBAC, anomaly detection)? What attacks does encryption prevent that the others do not?
Trade-offs¶
Aspect |
Without Encryption |
With Encryption |
|---|---|---|
Setup complexity |
None |
Certificate generation and management |
Performance |
No overhead |
Minimal overhead (AES-NI) |
Eavesdropping |
Trivial |
Prevented |
Man-in-the-middle |
Trivial |
Prevented (with cert validation) |
Maintenance |
None |
Certificate renewal required |
Compatibility |
All clients work |
Clients need certificates |
Troubleshooting |
Easy (cleartext) |
Harder (encrypted traffic) |
The right answer is almost always “enable encryption”. The question is whether you can afford the operational complexity of certificate management in your specific environment.