
Eine öffentliche Veröffentlichung der Steal My Bitcoin Challenge
Ein Live-PHP-Callback gegen die Steal My Bitcoin Challenge, von Anfang bis Ende, in einer einzigen Python-Datei mit Standardbibliothek.
Diese README erklärt stealmybitcoin_poc.py, eine einteilige Python-Reproduktion der Anfragesequenz, die in meinem Steal My Bitcoin CTF-Lauf verwendet wurde. Sie nutzt ausschließlich Pythons Standardbibliothek. Das Skript baut die Anfragen selbst auf, prüft jedes Leck, bevor es weitergeht, und zeichnet die HTTP-Bytes eines neuen Laufs auf.
Das lässt sich wahrscheinlich einfacher ohne den ganzen zusätzlichen Debug-Kram erledigen, und wie ich in meinem John Hammond-Video über die Steam Local Privilege Escalation entdeckt habe, könnte man diesen hässlichen PoC wohl aufräumen.
[!NOTE] ⚠️ Bitte räumt ihn ehrlich gesagt auf oder erstellt eure eigene bessere Version, die nicht von Sol zerknüllt wurde. ⚠️
Alles unten ist gegen das Docker-Lab geschrieben, nicht gegen die Live-Challenge. Das Lab ist ein lokaler Alpine PHP 8.4.26 CLI-Server hinter nginx, also derselbe Build und dasselbe Worker-Modell, auf dem die Offsets kalibriert wurden. Richtet es in einer Minute ein und die gesamte Kette läuft Ende-zu-Ende auf eurer eigenen Maschine. Der Live-Lauf, der die Challenge tatsächlich gelöst hat, ist als historische Notiz unten festgehalten.
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.
zif_system auf, PHPs Handler für system.print-flag-Ergebnis über die Verbindung zurück, die noch offen ist.Warum zwei Adressen? Das Betriebssystem randomisiert unabhängig voneinander, wo PHPs Code und Heap landen. Das Heap-Leak verrät dem Skript, wohin die gefälschte Struktur gehört, das Code-Leak verrät ihm, wo die Funktion, die es aufrufen will, tatsächlich liegt. Adressen aus einem früheren Worker oder einem früheren Lauf sind kein sicherer Ersatz, und jedes Mal, wenn ich es versucht habe, ist der Versuch einfach fehlgeschlagen.
Die Offsets in diesem PoC sind für Alpine PHP 8.4.26 mit dem eingebauten CLI-Server und vier Workern kalibriert. Das Labor reproduziert genau das, hinter nginx, auf Loopback.
[!IMPORTANT] Der Build ist wichtiger als der Versionsstring. Ein Debian-
php:8.4.26-fpm-Stack meldet dasselbePHP/8.4.26, hat aber ein anderes Binärlayout, sodass der Code-Pointer-Fingerprint niemals übereinstimmen wird und der Lauf in der Code-Leak-Phase abbricht. Verwende das untenstehende Alpine-Image.
Drei kleine Dateien. Lege sie in einem leeren Verzeichnis ab:
public/php-version.php (der Health-Endpoint und die einzige Seite, die der PoC benötigt)
<?php
header('Content-Type: text/plain');
echo 'PHP ', PHP_VERSION, "\n";
echo 'PID: ', getmypid(), "\n";
print-flag (Platzhalter für die Challenge-Binärdatei, muss ausführbar sein)
#!/bin/sh
printf '%s\n' 'LOCAL_DUMMY_WIF_NEVER_USE'
nginx.conf
events {}
http {
server {
listen 80;
server_name _;
location / {
proxy_pass http://ctf-cross-php:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
}
Dann starten Sie es:
docker network create ctf-cross-net
docker run -d --name ctf-cross-php --network ctf-cross-net \
--read-only --tmpfs /tmp:rw,size=32m,mode=1777 --memory 1g \
-v "$PWD/public:/app:ro" \
-v "$PWD/print-flag:/usr/local/bin/print-flag:ro" \
-e PHP_CLI_SERVER_WORKERS=4 \
php:8.4.26-fpm-alpine3.23 php -S 0.0.0.0:8000 -t /app
docker run -d --name ctf-cross-nginx --network ctf-cross-net \
-p 127.0.0.1:18166:80 \
-v "$PWD/nginx.conf:/etc/nginx/nginx.conf:ro" \
nginx:1.27-alpine
Prüfen Sie es:
$ curl -s http://127.0.0.1:18166/php-version.php
PHP 8.4.26
PID: 1
Die PID variiert je nachdem, welcher Worker antwortet, und das ist in Ordnung. Die Versionszeile ist der Teil, den der PoC prüft.
Bauen Sie es mit docker rm -f ctf-cross-nginx ctf-cross-php && docker network rm ctf-cross-net ab.
[!TIP] Führen Sie unter Windows diese
docker run-Befehle in PowerShell aus oder ersetzen Sie$PWDdurch einen vollständigen Laufwerkspfad wieC:/lab. In Git Bash wird$PWDzu einem MSYS-Pfad wie/tmp/...erweitert, den Docker Desktop innerhalb seiner eigenen VM auflöst. Die Bind-Mounts erstellen dann stillschweigend leere Verzeichnisse dort, wonginx.confundprint-flagsein sollten, und die Container starten fehlerhaft.