Wiki / Protocols

OPC-UA

The modern secure-by-design industrial protocol (IEC 62541). The stack is strong; the field deployments turn the awkward parts off, so a None endpoint and a trust-any cert list undo all of it.

OPC-UA connection stages
TCP :4840
OpenSecureChannel (wire)
None / Sign / SignAndEncrypt
ActivateSession (user)
Anonymous / UserName / Certificate
Services
Browse / Read / Write / Call
An OPC-UA connection is built in stages, and two of them carry independent security decisions: the SecureChannel protects the wire (None, Sign, or SignAndEncrypt) and the Session authenticates the user (Anonymous, UserName, or Certificate). They do not constrain each other, so a None channel with an Anonymous token is no crypto and no identity at all.

OPC-UA (OPC Unified Architecture, IEC 62541) is the modern, platform-neutral industrial protocol meant to replace the old, unauthenticated field protocols like Modbus. It carries a full security stack: signed and encrypted channels, X.509 mutual authentication, user tokens, and a per-node permission model. The catch is that every one of those layers is optional, and field integrators routinely turn the awkward ones off to make an HMI connect.

What it is

OPC-UA is a service-oriented client/server protocol (it also has a pub/sub mode over UDP or MQTT, less common). Over TCP it registers on port 4840 and speaks a binary encoding wrapped in a Hello/Acknowledge handshake. Above that, a connection is built in stages:

Stage Decision it carries
OpenSecureChannel SecurityMode: None / Sign / SignAndEncrypt, plus a SecurityPolicy (crypto suite)
CreateSession + ActivateSession user token: Anonymous / UserName / Certificate
Service calls Browse, Read, Write, Call, CreateSubscription, and the rest

The server exposes an address space: a graph of nodes, each with a NodeId written ns=<namespace>;<id>, where the id is numeric (i=85, the Objects folder), a string (s=Tank.Setpoint), a GUID, or a bytestring. ns=0 is the standard, identical-everywhere OPC-UA base; a plant's own tags usually live in a higher namespace such as ns=2.

Why it matters

OPC-UA sits at Level 2/3 of an OT stack, brokering between HMIs, historians, and the controllers below. Because the security is layered and optional, the same four field misconfigurations recur:

  • A SecurityMode=None endpoint left up next to the secure one, so the whole SecureChannel can be skipped.
  • A trust list set to accept any self-signed client certificate ("trust-any" for onboarding, never reverted).
  • Anonymous access enabled "for diagnostics" and never turned off.
  • Flat permissions where every session can Write every Variable and Call every Method, because no role model was ever configured.

None of these is a crypto break. Every one is a deployment mistake, and together they turn a "secure" server into a Modbus-grade open door.

How to work it

Discovery is free: GetEndpoints is unauthenticated by design, so a server tells anyone who asks how to connect.

  1. Find servers by port, then confirm with a real handshake (they also bind 48010, 49320, and high ports):
nmap -p 4840,4843,48010,49320 --open <subnet>
  1. Pull the endpoint inventory and read SecurityMode / SecurityPolicy / accepted user tokens per endpoint:
uadiscover opc.tcp://<ip>:4840
  1. Browse and read the address space over the weakest endpoint (asyncua ships the ua* CLI: uabrowse, uals, uaread):
uabrowse -u opc.tcp://<ip>:4840/plant/none -n "ns=0;i=85"
  1. Where permissions are flat, a uawrite to a Variable moves a setpoint and a uacall invokes a Method that should have required authorization. Everything the CLI does you can script with the python asyncua Client.

Pitfalls

  • SecurityMode and the user token are orthogonal. A None channel can still ask for a username (sent in cleartext); an encrypted channel can still allow anonymous. Read both fields, not just one.
  • NodeId types are strict: ns=2;s=Tank.Setpoint (string) is not ns=2;i=... (numeric). Copying the wrong form gets you a BadNodeIdUnknown, not the value.
  • Sign is not SignAndEncrypt. A signed channel is tamper-evident but still cleartext on the wire, so it is readable to a sniffer.
  • These are real processes. A Write or a Method Call moves pumps and valves on a live plant; stay on the lab or a system you own.

Further reading