
إصدار عام لتحدي Steal My Bitcoin
استدعاء PHP حي واحد ضد تحدي Steal My Bitcoin، من البداية إلى النهاية، في ملف Python واحد يعتمد على المكتبة القياسية فقط.
يشرح هذا الملف stealmybitcoin_poc.py، وهو إعادة إنتاج بملف Python واحد لتسلسل الطلبات المستخدم في تشغيلي لتحدي Steal My Bitcoin CTF. يستخدم فقط مكتبة Python القياسية. يبني السكربت الطلبات بنفسه، ويتحقق من كل تسريب قبل الانتقال، ويسجل بايتات HTTP من تشغيل جديد.
يمكن على الأرجح القيام بذلك بسهولة أكبر دون كل هذه الأمور الزائدة الخاصة بالتصحيح، وكما اكتشفت في فيديو John Hammond الخاص بي حول Steam Local Privilege Escalation، يمكنك على الأرجح تنظيف هذا الـ PoC القبيح.
[!NOTE] ⚠️ بصراحة، أرجوك نظّفه أو اصنع نسختك الأفضل الخاصة التي لم تُشوّهها Sol. ⚠️
كل ما ورد أدناه مكتوب مقابل مختبر Docker، وليس التحدي الحي. المختبر هو خادم Alpine PHP 8.4.26 CLI محلي خلف nginx، وهو نفس البناء ونموذج العامل الذي تمت معايرة الإزاحات عليه. قم بتشغيله في دقيقة وسيعمل التسلسل بأكمله من البداية إلى النهاية على جهازك. التشغيل الحي الذي حل التحدي فعليًا محفوظ كملاحظة تاريخية في الأسفل.
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، وهو معالج PHP الخاص بـ system.print-flag واحدة عبر الاتصال الذي لا يزال مفتوحًا.لماذا عنوانان؟ يُعشِّئ نظام التشغيل موضع شيفرة PHP وكومتها بشكل مستقل. يكشف تسرّب الكومة للسكربت أين يضع البنية المزوّرة، ويكشف تسرّب الشيفرة أين تقع الدالة التي يريد استدعاءها فعليًا. العناوين من عامل سابق أو من تشغيل سابق ليست بدائل آمنة، وفي كل مرة جرّبت ذلك فشلت المحاولة فحسب.
الإزاحات (offsets) في هذا PoC معايَرة لـ Alpine PHP 8.4.26 يعمل بخادم CLI المدمج مع أربعة عمال. يعيد المختبر إنتاج ذلك بالضبط، خلف nginx، على واجهة loopback.
[!IMPORTANT] البناء أهم من سلسلة الإصدار. حزمة
php:8.4.26-fpmعلى Debian تُبلّغ عن نفسPHP/8.4.26لكن تخطيطها الثنائي مختلف، لذا لن تتطابق بصمة مؤشر الشيفرة أبدًا وسيتوقف التشغيل عند مرحلة تسرّب الشيفرة. استخدم صورة Alpine أدناه.
ثلاثة ملفات صغيرة. ضعها في دليل فارغ:
public/php-version.php (نقطة فحص السلامة، والصفحة الوحيدة التي يحتاجها PoC)
<?php
header('Content-Type: text/plain');
echo 'PHP ', PHP_VERSION, "\n";
echo 'PID: ', getmypid(), "\n";
print-flag (بديل عن ملف التحدي الثنائي، يجب أن يكون قابلاً للتنفيذ)
#!/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;
}
}
}
ثم شغّله:
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
تحقق منه:
$ curl -s http://127.0.0.1:18166/php-version.php
PHP 8.4.26
PID: 1
يختلف PID حسب أي عامل يستجيب، وهذا أمر مقبول. سطر الإصدار هو الجزء الذي يتحقق منه PoC.
أزل الحاويات بالأمر docker rm -f ctf-cross-nginx ctf-cross-php && docker network rm ctf-cross-net.
[!TIP] على Windows، شغّل أوامر
docker runتلك من PowerShell، أو استبدل$PWDبمسار قرص كامل مثلC:/lab. في Git Bash يتم توسيع$PWDإلى مسار MSYS مثل/tmp/...، والذي يحلّه Docker Desktop داخل الآلة الافتراضية الخاصة به. عندئذٍ تنشئ عمليات الربط (bind mounts) بصمت أدلة فارغة حيث يجب أن يكونnginx.confوprint-flag، وتظهر الحاويات معطلة.
ملاحظتان على تلك الوصفة. الصورة هي وسم fpm لكن الحاوية تشغّل php -S، لذا فإن SAPI هو cli-server، وهو ما تمت معايرة القياس عليه. PHP_CLI_SERVER_WORKERS=4 هو ما يمنح مراحل التسريب أكثر من عامل واحد للتنافس عليه. كما مرّر أداة المختبر الخاصة بي --cap-add SYS_PTRACE --security-opt seccomp=unconfined حتى يتمكن من إرفاق متتبع (tracer) بالعوامل؛ لكن PoC نفسه لا يفعل ذلك أبدًا، لذا تُركت هذه الخيارات خارج الأوامر أعلاه.
Python 3.11 أو أحدث، المكتبة القياسية فقط، لا شيء لتثبيته:
python .\stealmybitcoin_poc.py --url http://127.0.0.1:18166/php-version.php
شغّله بدون وسائط فيطبع تعليماته ويخرج دون لمس الشبكة.
يبدو التشغيل الجيد هكذا:
[health] PHP 8.4.26 confirmed
[heap] strict Zend base 0x78113e800000
[code] strict zval_ptr_dtor 0x615b1f512930
{"stage": "final", "nonce": "1ef0e73d8615157a", "execution_proof_verified": true, "response_path": "..."}
لاحظ البتات الـ 21 المنخفضة من قيمة zval_ptr_dtor تلك: 0x112930، وهي بالضبط البصمة التي يتوقعها السكربت من بناء Alpine هذا. هذا هو الفحص الذي يرفض صورة غير مطابقة.
جسم الاستجابة الذي يعود: