claude-news
newsHacker News·26 Oct 2026·davikr

Steal My Bitcoin CTF PoC

Single-file Python proof of concept of the PHP2Shell request chain used to win the Steal My Bitcoin CTF.

Depth 3/5Score 4.29

Extract

sponsored with ❤️ by Steal My Bitcoin CTF proof of concept One live PHP callback against the Steal My Bitcoin challenge, start to finish, in a single standard-library Python file. This README explains stealmybitcoin_poc.py, a single-file Python reproduction of the request sequence used in my Steal My Bitcoin CTF run. It uses only Python's standard library. The script builds the requests itself, checks each leak before moving on, and records the HTTP bytes from a new run. This can probably be done easier without all the extra debug crap and as in my John Hammond video about the Steam Local Privilege Escalation I discovered, you could probably clean up this ugly PoC. Note⚠️ Honestly please clean it up or make your own better version that isn't crumpled by Sol. ⚠️ Everything below is written against the Docker lab, not the live challenge. The lab is a local Alpine PHP 8.4.26 CLI server behind nginx, which is the same build and worker model the offsets were calibrated on. Stand it up in a minute and the whole chain runs end to end on your own machine. The live run that actually solved the challenge is kept as a historical note at the bottom. stage what goes out what comes back ───────────────────────────────────────────────────────────────────────────── 0 health GET /php-version.php -> PHP 8.4.26 confirmed 1 heap leak 249,042 B producer + 8 x 6,672 B -> 256 B cookie (OOM poisons a worker) └─ Zend chunk base 2 code leak 251,557 B producer + 8 x 92,388 B -> 16 B cookie (OOM poisons a worker) └─ zval_ptr_dtor 3 callback 368,812 B POST -> forged HashTable runs (fake HashTable + command) └─ zif_system(cmd) ───────────────────────────────────────────────────────────────────────────── 20 HTTP requests at most. No looping, no retries. The short version A small multipart upload makes PHP chew through far more memory than the wire size suggests. When it runs out of memory partway through session upload-progress parsing, PHP leaves stale state behind in a worker that then gets reused. Shape the follow-up requests right and bytes fall out through session cookies. I read those as a heap address and a PHP code address. With both addresses, a final request drops a forged PHP structure where cleanup will find it, and cleanup calls zif_system, PHP's handler for system. That command sends back a fresh proof marker and one print-flag result over the connection that is still open. Why two addresses? The OS randomizes where PHP's code and heap land, independently. The …