Imagine the emergency stop button on a factory floor is connected to a network service that accepts any certificate you generate on your laptop. You do not need a password. You do not need to be on the same physical plant network. You just need the right node ID and a willingness to set a few values to false.
That is the essence of insecure OPC-UA write access. The protocol itself is solid. The way many servers are configured is not. This article walks through the concept from first principles, shows a realistic (but synthetic) attack path, and ends with concrete defenses that actually work.
What Is OPC-UA, Anyway?
OPC Unified Architecture is the modern standard for industrial data exchange. SCADA systems, PLCs, historians, and HMIs use it to read process values and, when allowed, write setpoints and commands.
Unlike older protocols (Modbus, for example), OPC-UA was designed with security in mind: certificates, encryption, signing, and role-based access. In theory you get mutual authentication, confidentiality, and integrity. In practice many deployments still ship with:
- Self-signed or poorly validated client certificates accepted by default
- Broad write permissions on nodes that should be read-only
- No application-level authorization beyond “the certificate is present”
- Safety-critical parameters exposed as ordinary writable variables
The protocol supports several security policies. None is the obvious bad idea. Basic256Sha256 with SignAndEncrypt looks strong on paper, yet if the server does not enforce certificate trust or map certificates to limited roles, an attacker with any valid-looking certificate can still write to every node the server exposes.
Why Should You Care?
- A single writable boolean for an emergency cooling system or trip interlock can disable the last line of defense.
- Industrial networks are increasingly connected to engineering workstations and even corporate networks. Reachability is higher than most plant managers assume.
- The impact is physical: overheating, pressure excursions, lost production, or worse.
- Many “secure” OPC-UA deployments still treat the certificate handshake as sufficient authorization. That is the gap.
Worked Example: The Cooling Control Server
Consider a synthetic chemical reactor cooling system. The plant uses OPC-UA for the following nodes (namespace 2 for illustration):
| Node | Meaning | Intended Access |
|---|---|---|
ns=2;i=11 | Control valve position (%) | Read/Write (operator) |
ns=2;i=26 | Primary circulation pump | Read/Write (operator) |
ns=2;i=27 | Secondary circulation pump | Read/Write (operator) |
ns=2;i=38 | Emergency cooling enabled | Read only (safety) |
ns=2;i=41 | Trip interlock armed | Read only (safety) |
In a correctly configured system the last two nodes would have AccessLevel set to CurrentRead only, and writes would be rejected even for authenticated clients. In our broken example the server marks them writable and accepts any client certificate that matches the expected security policy.
Discovery
An engineer (or an attacker) connects with a library such as Python’s opcua package, presents a self-signed certificate, and browses the address space. The AccessLevel attribute on each node immediately reveals which variables accept writes. Safety nodes that should be locked down show the same access mask as ordinary setpoints.
Exploitation Logic
-
Disarm the trip interlock (
ns=2;i=41→false). Automatic shutdown is now disabled. -
Disable emergency cooling (
ns=2;i=38→false). The backup cooling path is gone. -
Stop both circulation pumps (
ns=2;i=26andns=2;i=27→false). Heat removal ceases. -
Drive the control valve to the extreme position that increases reaction rate or heat generation (
ns=2;i=11→0or100, depending on the process).
Because the safety logic lives on the same server and trusts the incoming writes, the process drifts into an unsafe state. Persistent writes every few seconds overcome any soft reset the HMI might attempt.
The same pattern appears whenever a safety interlock, permissive, or protective function is modeled as a simple Boolean or numeric node with write permission granted too broadly.
Vulnerable vs. Fixed Configuration
Vulnerable (Python freeopcua-style server fragment)
# Dangerous: safety nodes marked writable, no role check
from opcua import ua, Server
server = Server()
server.set_security_policy([ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt])
# Accept any client cert that presents the right policy
# (no trust-store validation, no user mapping)
ns = server.register_namespace("urn:example:cooling")
safety = objects.add_object(ns, "Safety")
trip = safety.add_variable(ns, "TripArmed", True)
trip.set_writable() # <-- should never be writable from the network
eccs = safety.add_variable(ns, "EmergencyCooling", True)
eccs.set_writable() # <-- same problem
A client that can complete the certificate handshake can now call set_value(False) on both nodes.
Hardened version
# Safer: safety nodes read-only, certificate mapped to role, write rejected
trip = safety.add_variable(ns, "TripArmed", True)
trip.set_read_only() # AccessLevel = CurrentRead only
eccs = safety.add_variable(ns, "EmergencyCooling", True)
eccs.set_read_only()
# Additionally: enforce a trust list and map certificates to roles
# that never include the "SafetyWriter" permission for external clients.
Even better, keep the protective functions entirely outside the network-writable address space. Let the safety PLC own the interlocks and expose only status (read-only) to the supervisory layer.
Defense / How to Fix
- Never expose safety interlocks as network-writable nodes. Model them as read-only status. The actual trip logic belongs in a safety-rated controller that does not accept remote writes.
- Enforce a certificate trust store. Reject self-signed or unknown client certificates. Rotate and revoke as you would any other credential.
- Map certificates (or usernames) to roles with least privilege. An engineering workstation may need write access to setpoints; it should never receive write access to protective functions.
- Audit AccessLevel and UserAccessLevel on every node during commissioning and after every firmware or configuration change.
- Segment the network. OPC-UA servers that talk to safety systems should not be reachable from general engineering or corporate networks without a jump host and additional authentication.
- Monitor for unexpected writes. Log every write to critical nodes and alert on changes to safety-related variables outside maintenance windows.
- Prefer SignAndEncrypt, but treat it as necessary, not sufficient. Encryption does not equal authorization.
Final Thoughts
OPC-UA gave industry a protocol that can be secured properly. Too many deployments still treat “the client showed up with a certificate” as proof that the client is allowed to turn off the emergency stop. The interlock is just a Boolean on a node someone forgot to lock. Fix the access model, keep protective functions off the writable address space, and the remote kill switch disappears.
The safest safety system is the one that does not listen for network writes in the first place.