Resizer
Author: 0xIDA
Summary
Hard HTB web challenge: a Flask image resizer with an unsanitized multipart filename. The blacklist only rejects .py/.pyc substrings and refuses to overwrite existing files. The chain is path-traversal write of a native extension module next to utils/resizer.py, then a gunicorn worker recycle so CPython loads the .so before the .py.
Solution
Write primitive
POST /resize joins the client-controlled filename under uploads/ without secure_filename, checks os.path.exists (no overwrite), and saves before Pillow resize. Path traversal works:
../utils/resizer.cpython-312-x86_64-linux-gnu.so
Extension-module shadowing
CPython FileFinder prefers extension suffixes over .py. A planted utils/resizer.cpython-312-x86_64-linux-gnu.so is chosen on a cold import of utils.resizer. The malicious module exports resizer(...) and writes /app/flag.txt into the expected *_resized.* path so send_file returns the flag.
Worker recycle
Live workers already have utils.resizer cached. Open several slow multipart uploads (drip-feed body past gunicorn’s default 30s timeout). The master kills stuck workers; fresh workers import the planted .so.
Trigger
Upload any PNG after recycle — response body is the flag.
Solvers
Flag
HTB{c7397d9400ca1e918e2e9334f643f990}