GHSA-WXRW-GVG8-FQJP: Verified Repro With Script Download
GHSA-WXRW-GVG8-FQJP: Sliver has DNS C2 OTP Bypass that Allows Unauthenticated Session Flooding and Denial of Service
GHSA-WXRW-GVG8-FQJP is verified against the affected target. Vulnerability class: DoS. This security reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00088.
What Is GHSA-WXRW-GVG8-FQJP?
GHSA-WXRW-GVG8-FQJP describes a DNS C2 OTP bypass in Sliver that allows unauthenticated session flooding and denial of service. Pruva reproduced it (reproduction REPRO-2026-00088).
How to Reproduce GHSA-WXRW-GVG8-FQJP
pruva-verify REPRO-2026-00088 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00088/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for GHSA-WXRW-GVG8-FQJP
Reproduced by Pruva's autonomous agents — 242 tool calls over 13 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for GHSA-WXRW-GVG8-FQJP
Summary
The Sliver DNS C2 listener in the 1d50db698 (2023) code path accepts unauthenticated DNSMessageType_TOTP bootstrap requests and allocates a new DNSSession for each request even when EnforceOTP is enabled. The TOTP message is treated as a “hello” packet that bypasses any OTP validation, so an unauthenticated remote actor can repeatedly send a tiny protobuf payload and force session allocations without cleanup.
Impact
- Package/component affected: Sliver server DNS C2 listener (server/c2/dns.go).
- Affected versions: At least commit 1d50db6982880a1e538afac2ad8c5f268e62c51a (June 2023), prior to removal of TOTP bootstrap in Feb 2026.
- Risk level and consequences: High availability risk. A remote unauthenticated actor can flood the DNS listener with minimal TOTP bootstrap messages to create unbounded sessions and memory growth, leading to denial of service.
Root Cause
The DNS handler routes all DNSMessageType_TOTP messages directly to handleHello without checking the EnforceOTP flag or validating the OTP value. In handleHello, the server allocates a DNSSession and stores it in sessions with a newly generated DNS session ID, and there is no cleanup for this bootstrap path. The bug is visible in server/c2/dns.go: handleC2 checks for TOTP and calls handleHello, while handleHello simply stores a new session. A later fix removes TOTP from DNS C2 and adds pending message GC (commit 9b001ab88ea17b7247f2622c13003c4dcabe5bf3).
Reproduction Steps
- Run
repro/reproduction_steps.sh. - The script clones Sliver, checks out commit 1d50db698, adds placeholder assets required for build tags, and runs a minimal DNS listener using StartDNSListener with EnforceOTP=true. It then sends a single base32-encoded DNSMessageType_TOTP query to the listener.
- Expected evidence: the DNS response contains a non-zero session ID and the script prints “received session id …” followed by “Vulnerability reproduced…”.
Evidence
- Log file:
logs/dns_totp_poc.log - Key excerpt (example):
received session id 196.251.221.137 - Environment details: Go toolchain go1.20.14 (via GOTOOLCHAIN) with build tags
server go_sqliteon linux/arm64.
Recommendations / Next Steps
- Suggested fix: enforce OTP validation in the TOTP bootstrap handler (reject invalid OTPs when EnforceOTP is enabled) and add lifecycle cleanup for sessions created during bootstrap. Preferably remove unauthenticated TOTP bootstrap entirely, as in the Feb 2026 change.
- Upgrade guidance: move to a version that removes TOTP bootstrap and includes session/pending message GC.
- Testing recommendations: add unit/integration tests that send TOTP bootstrap messages with EnforceOTP enabled and assert the server rejects them without creating sessions; add load tests ensuring session tables remain bounded.
Additional Notes
- Idempotency:
repro/reproduction_steps.shwas run twice consecutively and succeeded both times. - Limitations: the script demonstrates session allocation for a single request; sustained flooding would exacerbate memory use but is not required to confirm the bug.
GHSA-WXRW-GVG8-FQJP Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
git clone https://github.com/BishopFox/sliver.git repoCloning into 'repo'... Updating files: 78% (12187/15574) Updating files: 79% (12304/15574) Updating files: 80% (12460/15574) Updating files: 81% (12615/15574) Updating files: 82% (12771/15574) Updating files: 83% (12927/15574) Updating files: 84% (13083/15574) Updating files: 85% (13238/15574) Updating files: 86% (13394/15574) Updating files: 87% (13550/15574) Updating files: 88% (13706/15574) Updating files: 89% (13861/15574) Updating files: 90% (14017/15574) Updating files: 91% (14173/15574) Updating files: 92% (14329/15574) Updating files: 93% (14484/15574) Updating files: 94% (14640/15574) Updating files: 95% (14796/15574) Updating files: 96% (14952/15574) Updating files: 97% (15107/15574) Updating files: 98% (15263/15574) Updating files: 99% (15419/15574) Updating files: 100% (15574/15574) Updating files: 100% (15574/15574), done.
rg -n "EnforceOTP|DNSMessageType_TOTP|handleHello" -S repo/server/c2/dns.go repo/implant/sliver/transports/dnsclient/dnsclient.go repo/protobuf/dnspb/dns.protowhich rg || true/usr/bin/rg
rg -n "EnforceOTP|DNSMessageType_TOTP|handleHello" -S repo/server/c2/dns.go repo/implant/sliver/transports/dnsclient/dnsclient.go repo/protobuf/dnspb/dns.protorg -n "EnforceOTP|TOTP|handleHello" -S repo/server/c2/dns.go repo/implant/sliver/transports/dnsclient/dnsclient.go repo/protobuf/dnspb/dns.protorepo/protobuf/dnspb/dns.proto:7: reserved 1; // formerly TOTP
rg -n "OTP|TOTP|bootstrap|Enforce" -S repo/server/c2/dns.go555: // Enforce a hard cap on pending INIT messages to prevent unbounded allocation.
rg -n "INIT|handleHello|bootstrap|session" -S repo/server/c2/dns.go{"cwd":"/data/pruva/runs/5940efdd-1d06-482c-88d0-5a6c3442d047","duration_ms":3,"exit_code":0,"stderr":"","stdout":"24:\tprotobuf and contain a 24-bit \"dns session ID\" (16777216 possible values), and a 8 bit\n28:\t\t1. Implant generates a random DNS Session ID and sends an INIT (Age key exchange)\n29:\t\t2. DNS server validates INIT and allocates session state\n30:\t\t3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses\n31:\t\t4. Implant establishes encrypted session\n64:\tsessionIDBitMask = 0x00ffffff // Bitwise mask to get the dns session ID\n69:\t// Upper bound on unauthenticated init reassembly state. INIT is split across multiple\n73:\t// Pending INIT messages are expected to complete quickly. If they don't, expire them.\n77:\t// INIT carries only key-exchange material and should remain small.\n95:\t\tsessions: &sync.Map{}, // DNS Session ID -> DNSSession\n110:// DNSSession - Holds DNS session information\n229:\tdnsLog.Debugf(\"[dns] dns session id: %d, msg id: %d completed message\", s.ID, msgID)\n276:// pendingInit tracks the reassembly state for an INIT message (which may arrive\n339:\tsessions *sync.Map\n476:\t// INIT can be ca… [truncated]rg -n "TOTP" -S repo{"cwd":"/data/pruva/runs/5940efdd-1d06-482c-88d0-5a6c3442d047","duration_ms":56,"exit_code":0,"stderr":"","stdout":"repo/client/credentials/credentials.go:556: 18100 | TOTP (HMAC-SHA1) | One-Time Password\nrepo/client/credentials/EXAMPLES.md:298:|18100|TOTP (HMAC-SHA1)|597056:3600|\nrepo/protobuf/dnspb/dns.proto:7: reserved 1; // formerly TOTP\nrepo/vendor/firebase.google.com/go/v4/auth/user_mgt.go:69:\tTOTPInfo *TOTPInfo `json:\"totpInfo,omitempty\"`\nrepo/vendor/firebase.google.com/go/v4/auth/user_mgt.go:73:// TOTPInfo describes a user enrolled second TOTP factor.\nrepo/vendor/firebase.google.com/go/v4/auth/user_mgt.go:74:type TOTPInfo struct{}\nrepo/vendor/firebase.google.com/go/v4/auth/user_mgt.go:81:// TOTPMultiFactorInfo describes a user enrolled in TOTP second factor.\nrepo/vendor/firebase.google.com/go/v4/auth/user_mgt.go:82:type TOTPMultiFactorInfo struct{}\nrepo/vendor/firebase.google.com/go/v4/auth/user_mgt.go:96:\tTOTP *TOTPMultiFactorInfo\nrepo/vendor/firebase.google.com/go/v4/auth/user_mgt.go:199:\t\tauthFactorInfo.TOTPInfo = (*TOTPInfo)(mfaInfo.TOTP)\nrepo/vendor/firebase.google.com/go/v4/auth/user_m… [truncated]rg -n "dns" -S repo/implant/sliver/transports/dnsclient/dnsclient.go{"cwd":"/data/pruva/runs/5940efdd-1d06-482c-88d0-5a6c3442d047","duration_ms":2,"exit_code":0,"stderr":"","stdout":"1:package dnsclient\n23:\tDNS domains are limited to 254 characters including '.' so that means\n42:// {{if .Config.IncludeDNS}}\n63:\t\"github.com/bishopfox/sliver/protobuf/dnspb\"\n65:\t\"github.com/miekg/dns\"\n71:\tsessionIDBitMask = 0x00ffffff // Bitwise mask to get the dns session ID\n78:\terrInvalidDNSSessionID = errors.New(\"{{if .Config.Debug}}Invalid dns session id{{end}}\")\n80:\tErrTimeout = errors.New(\"{{if .Config.Debug}}DNS Timeout{{end}}\")\n81:\tErrClosed = errors.New(\"dns session closed\")\n85:\tErrInvalidMsg = errors.New(\"invalid dns message\")\n88:// DNSOptions - c2 specific options\n89:type DNSOptions struct {\n100:// ParseDNSOptions - Parse c2 specific options\n101:func ParseDNSOptions(c2URI *url.URL) *DNSOptions {\n128:\treturn &DNSOptions{\n140:// DNSStartSession - Attempt to establish a connection to the DNS server of 'parent'\n141:func DNSStartSession(parent string, opts *DNSOptions) (*SliverDNSClient, error) {\n143:\tlog.Printf(\"DNS client connecting to '%s' (timeout: %s) ...\", parent, opts.QueryTimeout)… [truncated]rg -n "TOTP|OTP" -S repo/server/c2/dns.gogit -C repo log -S "TOTP" -- server/c2/dns.go{"cwd":"/data/pruva/runs/5940efdd-1d06-482c-88d0-5a6c3442d047","duration_ms":30,"exit_code":0,"stderr":"","stdout":"commit 9b001ab88ea17b7247f2622c13003c4dcabe5bf3\nAuthor: moloch-- <875022+moloch--@users.noreply.github.com>\nDate: Fri Feb 6 11:40:00 2026 -0800\n\n Remove totp from dns c2, implement pending msg gc\n\ncommit 1d50db6982880a1e538afac2ad8c5f268e62c51a\nAuthor: moloch-- <875022+moloch--@users.noreply.github.com>\nDate: Mon Jun 19 12:28:04 2023 -0700\n\n Unit tests pass\n\ncommit 9ab27d174610db90b108a69acca7741eaf8ef7c2\nAuthor: moloch-- <875022+moloch--@users.noreply.github.com>\nDate: Sun Jun 18 16:17:05 2023 -0700\n\n initial refactor, broken tests\n\ncommit 72065cd39eb6e549e2f0b4ee019547bf8e631226\nAuthor: moloch-- <875022+moloch--@users.noreply.github.com>\nDate: Wed Apr 13 11:29:56 2022 -0500\n\n Removed totp <-> dns session id\n\ncommit 15ca3871dd3f6f63d02b26f267193cac773be1f8\nAuthor: moloch-- <875022+moloch--@users.noreply.github.com>\nDate: Sun Oct 31 15:50:13 2021 -0500\n\n wip on dns c2 updates\n\ncommit 7463298de18f813cf563cdb19afab881fd5eacaa\nAuthor: moloch-- <875022+moloch--@users.noreply.github.com>\nDate: Sat Oct 30 09:45:19 2… [truncated]git -C repo show 15ca3871dd3f6f63d02b26f267193cac773be1f8 --statcommit 15ca3871dd3f6f63d02b26f267193cac773be1f8
Author: moloch-- <875022+moloch--@users.noreply.github.com>
Date: Sun Oct 31 15:50:13 2021 -0500
wip on dns c2 updates
implant/sliver/encoders/base32.go | 5 +-
implant/sliver/transports/dnsclient/dnsclient.go | 116 ++++++---git -C repo show 15ca3871dd3f6f63d02b26f267193cac773be1f8 -- server/c2/dns.go | head -n 120{"cwd":"/data/pruva/runs/5940efdd-1d06-482c-88d0-5a6c3442d047","duration_ms":2,"exit_code":0,"stderr":"","stdout":"commit 15ca3871dd3f6f63d02b26f267193cac773be1f8\nAuthor: moloch-- <875022+moloch--@users.noreply.github.com>\nDate: Sun Oct 31 15:50:13 2021 -0500\n\n wip on dns c2 updates\n\ndiff --git a/server/c2/dns.go b/server/c2/dns.go\nindex 8f7703cf7..6258fb064 100644\n--- a/server/c2/dns.go\n+++ b/server/c2/dns.go\n@@ -18,22 +18,28 @@ package c2\n \talong with this program. If not, see <https://www.gnu.org/licenses/>.\n \t------------------------------------------------------------------------\n \n-\tDNS command and control implementation:\n-\n-\t1. Implant sends TOTP encoded message to DNS server, server checks validity\n-\t2. DNS server responds with the \"DNS Session ID\" which is just some random value\n-\t3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses\n+\tI've put a little effort to making the server at least not super easily fingerprintable,\n+\tthough I'm guessing it's also still not super hard to do. The server must receive a valid\n+\tTOTP code before we start returning any non-error records. All requests must be formatt… [truncated]git -C repo checkout 1d50db6982880a1e538afac2ad8c5f268e62c51aNote: switching to '1d50db6982880a1e538afac2ad8c5f268e62c51a'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example:
rg -n "TOTP|totp|OTP" -S repo/server/c2/dns.go repo/protobuf/dnspb/dns.proto repo/implant/sliver/transports/dnsclient/dnsclient.gorepo/server/c2/dns.go:24: TOTP code before we start returning any non-error records. All requests must be formatted
repo/server/c2/dns.go:26: 8 bit "message ID." The server only responds to non-TOTP queries with valid dns session IDs
repo/server/c2/dns.go:30: 1. Implant sends TOTP encoded message to DNS server, server checks validity
repo/server/c2/dns.go:80:func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) *SliverDNSServer {
repo/server/c2/dns.go:88: EnforceOTP: enforceOTP,
repo/server/c2/dns.go:313: EnforceOTP bool
repo/server/c2/dns.go:381: // TOTP Handler can be called without dns session ID
repo/server/c2/dns.go:382: if msg.Type == dnspb.DNSMessageType_TOTP {Artifacts and Evidence for GHSA-WXRW-GVG8-FQJP
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix GHSA-WXRW-GVG8-FQJP
FAQ: GHSA-WXRW-GVG8-FQJP
How does the Sliver DNS C2 session-flooding attack work?
Which Sliver versions are affected?
How severe is this Sliver DNS C2 issue?
How can I reproduce the Sliver DNS C2 OTP bypass?
References for GHSA-WXRW-GVG8-FQJP
Authoritative sources for GHSA-WXRW-GVG8-FQJP — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.