# node-opcua missing nonce verification in UserNameIdentityToken authentication

node-opcua server never verifies the session nonce in UserNameIdentityToken, letting an attacker forge an empty-password token or replay captured tokens.

- Payout: Research
- Severity: high
- Program: node-opcua
- Found by: stanleytobias
- Disclosed: 2026-08-20
- CVE: CVE-2026-54155
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-mq36-523m-x7vv
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-mq36-523m-x7vv
- Tags: area:devices domain:iot impact:ato impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:auth-bypass vuln:crypto vuln:signature-replay

**Target:** OPC UA server UserNameIdentityToken authentication handler (opcua_server.ts)
**Root cause:** After RSA-OAEP decrypting the password blob, the server reads a 4-byte little-endian length and extracts buff[4:4+length] without checking that trailing bytes equal session.nonce. SecurityMode=None also makes verifyClientSignature return true unconditionally.
**Steps:**
1. Unauthenticated `GetEndpoints` to retrieve server public key and RSA token policy
2. `OpenSecureChannel` with SecurityMode=None, then `CreateSession`
3. Craft plaintext `[0x20, 0x00, 0x00, 0x00]` so readUInt32LE = 32 = serverNonce.length
4. RSA-OAEP encrypt with server public key to 256-byte ciphertext
5. `ActivateSession` with crafted UserNameIdentityToken
6. Server decrypts, computes length = 32 - 32 = 0, password = "", calls isValidUser(username, "")
**How found:** Code reading of opcua_server.ts:1886-1887 revealed the decrypted blob's trailing bytes were never compared to session.nonce; dynamic test confirmed decryption yields empty password with no error.
**Exploitation:** Attacker needs only the public key from GetEndpoints. Forging a token with length field equal to nonce length yields an empty password, compromising any account accepting empty passwords. Separately, captured UserNameIdentityToken ciphertext can be replayed in any new session since nonce binding is absent; SecurityMode=None skips channel signature verification entirely.
**Escalation:** Chained with SecurityMode=None bypass of verifyClientSignature (security_policy.ts:697-700) to remove the channel-level signature check.
**Insight:** In OPC UA token handlers, check that the decrypted blob's trailing bytes match the session nonce — a length field derived from nonce length can be zeroed to force an empty password; also audit SecurityMode=None paths where signature verification short-circuits to true.

## Proof of concept (copied exactly from the original report)

**Crafted token plaintext with nonce-length field**

```
4. Craft plaintext: [0x20, 0x00, 0x00, 0x00]  (readUInt32LE = 32 = serverNonce.length)
5. RSA-OAEP encrypt with server public key → 256-byte ciphertext
6. ActivateSession with crafted UserNameIdentityToken
7. Server decrypts → length = 32 - 32 = 0 → password = ""
```

