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:

  1. Probe insecure OPC UA servers to see what an attacker sees

  2. Generate X.509 certificates for each server

  3. Enable global encryption enforcement

  4. 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 .crt and .key file

  • The 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

enforcement_enabled

false

Enable global OPC UA security enforcement

security_policy

Aes256_Sha256_RsaPss

Policy applied when enforcement enabled

cert_dir

certs

Directory for certificate storage

validity_hours

8760

Certificate validity (1 year)

key_size

2048

RSA key size in bits

allow_anonymous

false

Allow anonymous connections when enforcement enabled

server_overrides

{}

Per-server customisation

Security policies

Policy

Encryption

Recommended

None

No encryption

No (insecure)

Basic256Sha256

AES-256 + SHA-256

Minimum

Aes128_Sha256_RsaOaep

AES-128 + RSA-OAEP

Good

Aes256_Sha256_RsaPss

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

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

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

  3. Certificate lifecycle. Certificates expire. What happens when a certificate expires on a running SCADA system? How would you plan certificate renewal without disrupting operations?

  4. 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.)

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