VYPR
High severityNVD Advisory· Published Oct 30, 2023· Updated Sep 9, 2024

CVE-2023-47090

CVE-2023-47090

Description

NATS nats-server before 2.9.23 and 2.10.x before 2.10.2 has an authentication bypass. An implicit $G user in an authorization block can sometimes be used for unauthenticated access, even when the intention of the configuration was for each user to have an account. The earliest affected version is 2.2.0.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

NATS nats-server authentication bypass via implicit $G user when $SYS account is defined, allowing unauthenticated access; fixed in 2.9.23/2.10.2.

Vulnerability

Overview

CVE-2023-47090 is an authentication bypass in NATS nats-server versions before 2.9.23 and 2.10.x before 2.10.2, affecting configurations that define an $SYS system account alongside a top-level authorization block. When such a configuration is active, the server's parsing logic incorrectly creates an implicit $G (global) user that bypasses the intended authentication requirement. This allows unauthenticated connections to be accepted and mapped to the $G user, even when the operator explicitly intended every user to authenticate with credentials [1][2].

Exploitation

Conditions

To exploit this vulnerability, an attacker only needs network access to the NATS server's port (default 4222). No prior authentication is required. The attack is triggered by simply connecting without providing any credentials. The server, misidentifying the connection as matching the implicit $G user, grants access. This behavior was observed when an $SYS account was defined in the accounts block, which altered the server's handling of the top-level authorization block [1].

Impact

An unauthenticated attacker who successfully exploits this flaw gains the permissions assigned to the default $G user. Depending on the server's configuration, this may allow publishing or subscribing to sensitive subjects, accessing JetStream streams, or interacting with system accounts. The impact ranges from unauthorized data access to potential denial-of-service if the attacker can exhaust resources via the misassigned user [1].

Mitigation

The issue is fixed in nats-server versions 2.9.23 and 2.10.2. The fix adds an internal authBlockDefined flag in the Options struct to properly track whether a top-level authorization block was explicitly defined, preventing the creation of an unintended $G user [2][3]. Users unable to upgrade should review their configuration to avoid mixing top-level authorization blocks with $SYS account definitions, or consider using explicit no_auth_user settings with restricted permissions [1]. The official release notes confirm the patch is included in v2.10.2 [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/nats-io/nats-server/v2Go
>= 2.2.0, < 2.9.232.9.23
github.com/nats-io/nats-server/v2Go
>= 2.10.0, < 2.10.22.10.2

Affected products

4

Patches

1
fa5b7afcb64e

[FIXED] Do not bypass authorization blocks when turning on $SYS account access (#4605)

https://github.com/nats-io/nats-serverDerek CollisonSep 28, 2023via ghsa
5 files changed · +45 11
  • server/opts.go+4 1 modified
    @@ -395,6 +395,9 @@ type Options struct {
     
     	// OCSP Cache config enables next-gen cache for OCSP features
     	OCSPCacheConfig *OCSPResponseCacheConfig
    +
    +	// Used to mark that we had a top level authorization block.
    +	authBlockDefined bool
     }
     
     // WebsocketOpts are options for websocket
    @@ -885,7 +888,7 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
     			*errors = append(*errors, err)
     			return
     		}
    -
    +		o.authBlockDefined = true
     		o.Username = auth.user
     		o.Password = auth.pass
     		o.Authorization = auth.token
    
  • server/opts_test.go+10 7 modified
    @@ -120,6 +120,7 @@ func TestConfigFile(t *testing.T) {
     		LameDuckDuration:      4 * time.Minute,
     		ConnectErrorReports:   86400,
     		ReconnectErrorReports: 5,
    +		authBlockDefined:      true,
     	}
     
     	opts, err := ProcessConfigFile("./configs/test.conf")
    @@ -132,13 +133,14 @@ func TestConfigFile(t *testing.T) {
     
     func TestTLSConfigFile(t *testing.T) {
     	golden := &Options{
    -		ConfigFile:  "./configs/tls.conf",
    -		Host:        "127.0.0.1",
    -		Port:        4443,
    -		Username:    "derek",
    -		Password:    "foo",
    -		AuthTimeout: 1.0,
    -		TLSTimeout:  2.0,
    +		ConfigFile:       "./configs/tls.conf",
    +		Host:             "127.0.0.1",
    +		Port:             4443,
    +		Username:         "derek",
    +		Password:         "foo",
    +		AuthTimeout:      1.0,
    +		TLSTimeout:       2.0,
    +		authBlockDefined: true,
     	}
     	opts, err := ProcessConfigFile("./configs/tls.conf")
     	if err != nil {
    @@ -283,6 +285,7 @@ func TestMergeOverrides(t *testing.T) {
     		LameDuckDuration:      4 * time.Minute,
     		ConnectErrorReports:   86400,
     		ReconnectErrorReports: 5,
    +		authBlockDefined:      true,
     	}
     	fopts, err := ProcessConfigFile("./configs/test.conf")
     	if err != nil {
    
  • server/routes_test.go+2 1 modified
    @@ -105,7 +105,8 @@ func TestRouteConfig(t *testing.T) {
     			NoAdvertise:    true,
     			ConnectRetries: 2,
     		},
    -		PidFile: "/tmp/nats-server/nats_cluster_test.pid",
    +		PidFile:          "/tmp/nats-server/nats_cluster_test.pid",
    +		authBlockDefined: true,
     	}
     
     	// Setup URLs
    
  • server/server.go+2 2 modified
    @@ -1239,8 +1239,8 @@ func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error)
     		// If we have defined a system account here check to see if its just us and the $G account.
     		// We would do this to add user/pass to the system account. If this is the case add in
     		// no-auth-user for $G.
    -		// Only do this if non-operator mode.
    -		if len(opts.TrustedOperators) == 0 && numAccounts == 2 && opts.NoAuthUser == _EMPTY_ {
    +		// Only do this if non-operator mode and we did not have an authorization block defined.
    +		if len(opts.TrustedOperators) == 0 && numAccounts == 2 && opts.NoAuthUser == _EMPTY_ && !opts.authBlockDefined {
     			// If we come here from config reload, let's not recreate the fake user name otherwise
     			// it will cause currently clients to be disconnected.
     			uname := s.sysAccOnlyNoAuthUser
    
  • server/server_test.go+27 0 modified
    @@ -19,6 +19,7 @@ import (
     	"context"
     	"crypto/tls"
     	"encoding/json"
    +	"errors"
     	"flag"
     	"fmt"
     	"io"
    @@ -2080,3 +2081,29 @@ func TestServerRateLimitLogging(t *testing.T) {
     
     	checkLog(c1, c2)
     }
    +
    +// https://github.com/nats-io/nats-server/discussions/4535
    +func TestServerAuthBlockAndSysAccounts(t *testing.T) {
    +	conf := createConfFile(t, []byte(`
    +		listen: 127.0.0.1:-1
    +		server_name: s-test
    +		authorization {
    +			users = [ { user: "u", password: "pass"} ]
    +		}
    +		accounts {
    +			$SYS: { users: [ { user: admin, password: pwd } ] }
    +		}
    +	`))
    +
    +	s, _ := RunServerWithConfig(conf)
    +	defer s.Shutdown()
    +
    +	// This should work of course.
    +	nc, err := nats.Connect(s.ClientURL(), nats.UserInfo("u", "pass"))
    +	require_NoError(t, err)
    +	defer nc.Close()
    +
    +	// This should not.
    +	_, err = nats.Connect(s.ClientURL())
    +	require_Error(t, err, nats.ErrAuthorization, errors.New("nats: Authorization Violation"))
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.