Pillow 发 13.0.0 了。这不是例行更新——翻它的发布说明和安全公告,这一次一口气修了三十来个 CVE,光 9.2 分的 CRITICAL 就有四个,全在 13.0.0 里解决。

Python 圈里处理图片基本绕不开 Pillow:后台生成缩略图、用户上传头像、爬虫存图,都在调它。而图片这东西天然不受信任——你让用户传,就等于让攻击者往你的进程里塞字节。

四个满分的洞,共同点都是"越界写":

  • CVE-2026-112393,9.2:往一个没有真实文件句柄的目标(比如 BytesIO)保存压缩 TIFF 时,_tiffWriteProc 给回调分配的大小是按上一块缓冲区算的,写的时候越界,顺带还能把堆上的旧数据泄漏出去。从 Pillow 2.0.0 起一直到 13.0.0 之前都在。
  • CVE-2026-112398,9.2:FlashPix(.fpx)的 JPEG 瓦片解码,把整行扫描线写到了给瓦片分配的缓冲区之外。
  • CVE-2026-112403,9.2:JPEG 解码器按请求的 draft 缩放算缓冲区,却没核对 libjpeg 实际输出的宽度,一张 8 位无损 SOF3 JPEG 就能写满一整行、越出去。
  • CVE-2026-112900,9.2:这个路子不同,是参数注入——EPS 输入交给 Ghostscript 处理时,EpsImagePlugin.Ghostscript 把调用方给的输入文件名直接接在 -f 后面,文件名以 - 开头就会被当成选项。

后面还跟着一堆 HIGH/MEDIUM:往内存里编码 TIFF 时 libtiff 拒绝某个 tag 触发的堆 use-after-free(CVE-2026-112396)、ImagingScaleAffine 分配失败时对图像内存做了 double free(CVE-2026-112402)、libtiff 短解码时把未初始化的内存当像素返回(CVE-2026-112897),再加上几个能把进程拖死的 DoS——GIF 注释的平方级复制、循环 PDF 页树等等。

这些洞有个共同的触发条件:你打开或保存了一张别人给的图。

先断自己有没有事,一行就够:

python -c "import PIL; print(PIL.__version__)"

低于 13.0.0 就得动。升级本身很短,pip install -U Pillow,或者跟着你项目里的依赖锁一起走。跑在容器里的,记得镜像里的 Pillow 也要跟着升,别只升了宿主机的 venv。

还有一个容易漏的点:Pillow 是把解码器编译进二进制里的。不少项目连同底下一堆系统库一起打包进镜像,你把 requirements 改了、镜像却没重建,跑的还是旧的 .so——这时候 PIL.__version__ 打印的是新版本号,实际干活的解码器还是老的。所以升完最好重建镜像,再验一遍。

Pillow 版本自查:本机实测输出 10.2.0,低于修复版本 13.0.0 即需升级

前阵子写 Node 那个 Brotli 解码器的洞 时聊过一句:这类"解析不受信任数据"的地方,出事基本都出在解码器里,而且往往是越界读写。Pillow 这次是同一个剧本,只是换成了图片格式的解析器。升级命令很短,代价是重启一次服务;至于排在哪个窗口升,我一般挑业务最闲的那天,免得手抖牵连别的。