CVE-2026-54155 Overview
CVE-2026-54155 affects node-opcua, an OPC UA implementation for TypeScript and Node.js. Versions prior to 2.166.0 contain a missing signature verification flaw [CWE-347] in the UserNameIdentityToken authentication handler located in packages/node-opcua-server/source/opcua_server.ts. The handler decrypts an RSA-OAEP password blob but fails to verify that the trailing bytes match the current session serverNonce. An unauthenticated remote attacker can forge a token that yields an empty password, replay a captured ciphertext across sessions, or bypass client-signature safeguards when SecurityMode=None is negotiated. The issue is fixed in version 2.166.0.
Critical Impact
Unauthenticated remote attackers can authenticate as any account that accepts an empty password and replay captured UserNameIdentityToken ciphertexts across sessions.
Affected Products
- node-opcua versions prior to 2.166.0
- OPC UA servers built on the node-opcua-server package
- Industrial control and IoT deployments exposing OPC UA endpoints via node-opcua
Discovery Timeline
- 2026-09-14 - CVE-2026-54155 published to NVD
- 2026-09-14 - Last updated in NVD database
Technical Details for CVE-2026-54155
Vulnerability Analysis
The OPC UA specification (Part 4 §7.36.3) defines the UserNameIdentityToken plaintext layout as a little-endian UInt32 length, the password bytes, and the trailing serverNonce (32 bytes). The length field must equal password.length + serverNonce.length. The server is expected to strip the trailing nonce and verify it matches the nonce issued during CreateSession or the most recent ActivateSession.
In vulnerable versions of node-opcua, the server decrypts the RSA-OAEP ciphertext, trusts the attacker-supplied length field, and passes the extracted password directly to isValidUser without verifying the trailing nonce. This omission collapses three protections: nonce binding, session freshness, and replay resistance.
Root Cause
The root cause is missing cryptographic verification of the decrypted plaintext structure. The handler assumes the decrypted blob is well-formed and authentic, but the RSA-OAEP encryption only provides confidentiality and integrity of the ciphertext, not proof that the sender knows the current serverNonce. Without binding the plaintext to session state, any well-formed blob passes validation.
Attack Vector
An unauthenticated remote attacker obtains the server public key by calling GetEndpoints, which is unauthenticated by design. The attacker then crafts a blob whose little-endian length value produces an empty password after slicing. Any account configured to accept an empty password is compromised. A captured ciphertext from a legitimate session can also be replayed in a new session because no nonce binding ties it to a specific session context. When the channel is negotiated with SecurityMode=None, the separate client-signature safeguard that would otherwise deter tampering is not applied.
/**
* @module node-opcua-server
*/
import type { Nonce } from "node-opcua-crypto/web";
export interface ExtractPasswordResult {
valid: boolean;
password: string;
}
/**
* Extract the password from a decrypted UserNameIdentityToken
* password blob.
*
* The plaintext layout (OPC UA Part 4 §7.36.3) is:
*
* ┌─────────────────┬──────────────┬──────────────┐
* │ UInt32 length │ password │ serverNonce │
* │ (LE, 4 bytes) │ (variable) │ (32 bytes) │
* └─────────────────┴──────────────┴──────────────┘
*
* `length` = password.length + serverNonce.length
*
* After extracting the password, the trailing bytes **must**
* equal the session's `serverNonce` to confirm nonce binding.
*/
Source: GitHub Commit c6b0573 — patch introducing extractPasswordFromDecryptedBlob with explicit nonce verification.
Detection Methods for CVE-2026-54155
Indicators of Compromise
- Successful ActivateSession events on accounts that should require a password but were authenticated with an empty credential.
- Repeated GetEndpoints calls from a single remote address followed by CreateSession and ActivateSession in rapid succession.
- Identical UserNameIdentityToken ciphertext bytes observed across distinct sessions or channels, indicating replay.
- OPC UA sessions established with SecurityMode=None that then submit encrypted UserNameIdentityToken payloads.
Detection Strategies
- Inspect OPC UA server logs for authentications where the derived password length is zero and correlate with the source IP and account name.
- Alert on repeated authentication attempts across many accounts from the same client endpoint, indicative of empty-password enumeration.
- Compare the trailing 32 bytes of decrypted UserNameIdentityToken blobs against the issued serverNonce and flag mismatches.
Monitoring Recommendations
- Ingest OPC UA server audit logs into a centralized SIEM and normalize session, endpoint, and authentication events.
- Monitor package inventories for node-opcua versions below 2.166.0 across production and OT environments.
- Track network exposure of OPC UA TCP ports (default 4840) to the internet or untrusted network segments.
How to Mitigate CVE-2026-54155
Immediate Actions Required
- Upgrade node-opcua to version 2.166.0 or later across all server-side deployments.
- Audit all OPC UA user accounts and disable any that permit empty or blank passwords.
- Restrict OPC UA endpoint reachability to trusted management networks only.
- Rotate credentials for accounts that may have been exposed to unauthenticated remote callers.
Patch Information
The fix is available in node-opcua version 2.166.0. See the GitHub Release v2.166.0, the GitHub Security Advisory GHSA-mq36-523m-x7vv, and the patch commit c6b0573, which introduces the extractPasswordFromDecryptedBlob helper and enforces serverNonce verification.
Workarounds
- Configure OPC UA endpoints to require SecurityMode=SignAndEncrypt and reject SecurityMode=None.
- Enforce non-empty password policies for every user account defined in the server's authentication backend.
- Place OPC UA servers behind a firewall or VPN that restricts access to authorized industrial clients.
# Upgrade node-opcua to the patched release
npm install node-opcua@2.166.0
# Verify the installed version
npm ls node-opcua | grep node-opcua@
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.