Có một repo vừa lên GitHub hôm 21/10, hiện được 605 sao, tên là 9bie/php2shell-bypass-aslr-exp. Nội dung của nó gọn trong một câu: chứng minh có thể RCE PHP 8.4.26 + Apache prefork + mod_php chỉ bằng một file exploit.py, hoàn toàn qua HTTP, không cần target có session_start(), không cần ghi file PHP nào lên server.
Tác giả tự ghi trong README là đã test 3/3 lần thành công trên image chính thức php:8.4.26-apache, bằng chứng là dòng PROOF: uid=33(www-data)... chạy ra từ container. Mình đọc qua phần kỹ thuật thì đây là nghiên cứu lỗi thật, không phải demo cho vui.
Nó làm được gì, cụ thể
Vấn đề gốc là một lỗi trong session handling của PHP 8.4.26. Khi request multipart được xử lý, PHP có thể khởi tạo session ở giai đoạn RINIT thông qua callback php_session_rfc1867_update(), tức là trước khi script chạy. Nghĩa là target không cần gọi session_start() thì session vẫn được khởi tạo. Tác giả lợi dụng điểm này để làm request A gây OOM (out of memory) bên trong php_session_initialize(), làm cho con trỏ PS(http_session_vars) và PS(id) bị treo lơ lửng. Request B sau đó tái chiếm vùng nhớ cũ bằng dữ liệu multipart có kích thước được căn chính xác (56B block, 55B reference, 167B string), dựng fake HashTable, ghi đè pDestructor thành zif_system, rồi kích hoạt chuỗi zval_ptr_dtor để chạy lệnh.
Điểm đáng giá nhất về mặt kỹ thuật là bypass ASLR. Tác giả dùng hai lần rò rỉ qua header Set-Cookie (một lần ra chunk base, một lần ra libphp base), ở giữa thì xoay worker bằng SIGABRT. Prefork fork ra worker mới nên địa chỉ không đổi. Toàn bộ chain không cần đụng tới libc, chỉ dùng symbol nằm trong libphp.so.
Vì sao nó đang hot
605 sao trong một ngày với một repo security exploit là con số không nhỏ. Có vài lý do. Thứ nhất, PHP 8.4.26 là bản còn khá mới, chưa ai public chain hoàn chỉnh kiểu này. Thứ hai, điều kiện khai thác được mô tả rất “đời thường”: image Docker chính thức, cấu hình mặc định của nhiều người, không cần extension lạ. Thứ ba, tác giả viết README cực kỳ chi tiết, kèm file riêng cho từng giai đoạn (HTTP_HEAP_LEAK.md, ASLR_PURE_HTTP.md, DISPLAY_ERRORS_OFF.md, NOTES.md), có cả gdb evidence. Với dân làm offensive security, đây là loại tài liệu họ thích đọc hơn là một PoC trần trụi.
Cũng phải nói thêm là repo có yếu tố “lab report” rõ ràng. Tác giả ghi cả phiên bản Apache trong lab (2.4.68) và trong image thật (2.4.69), kèm profile --profile lab để ai muốn khớp số liệu. Chi tiết này khiến mình tin hơn.
Chạy thử trong vài phút
Repo không build lại PHP. Nó dùng thẳng image chính thức, chỉ mount config. Yêu cầu là Docker Engine hoặc Desktop, Docker Compose v2 và Python 3.9+. Toàn bộ command dưới đây lấy nguyên từ README, không đổi tên lệnh:
# Dependencies: 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) Pull the official image (PHP 8.4.26 / Debian 12 / Apache prefork)
docker compose -f docker/docker-compose.yml pull
# 2) Start the target: single worker, display_errors=On, memory_limit=8M
docker compose -f docker/docker-compose.yml up -d
# 3) Wait for Apache and check a normal page
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) One command for the whole exploit
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)
Lần chạy đầu tiên, exploit.py sẽ tự gọi docker/offsets.py để đọc offset zif_system và zval_ptr_dtor từ libphp.so trong container, ghi ra out/offsets.env. Đây là điểm mình thấy thực dụng, vì khi image đổi version thì không phải mò offset bằng tay. Proof được ghi vào file /out/p1 trong container, mount ra thư mục ./out ở host.
Một giới hạn cần biết trước khi nghịch: command trong payload bị giới hạn 14 byte. Muốn chạy lệnh dài hơn thì phải viết script ra thư mục mounted, rồi gọi sh /out/p1, đúng như README hướng dẫn.
Ai nên đọc repo này
Nếu bạn làm pentest, đây là tài liệu tham khảo tốt cho heap grooming với Zend memory manager, cách lợi dụng OOM bailout để tạo dangling pointer, và cách leak bằng header. Sinh viên học binary exploitation cũng có thể dùng làm case study vì tác giả ghi rõ từng bước và có cả gdb log.
Nếu bạn là dev đang chạy PHP trong Docker, nên đọc phần “边界” (giới hạn) cuối README để biết mình có nằm trong vùng nguy hiểm hay không. Chain yêu cầu display_errors=1, memory_limit=8M, max_input_vars=1000, session.use_cookies=1, session.use_strict_mode=0, và MPM prefork một worker. Đây là cấu hình khá phổ biến khi dev dựng lab hoặc môi trường staging nội bộ. Trên production thường display_errors=0, và đúng là bản RCE đầy đủ khi display_errors=0 hiện chưa có, tác giả nói “sẽ submit sau”. Tuy nhiên phần heap leak thuần HTTP cho case display_errors=0 đã được note là “đã thông”.
So với các lựa chọn khác
Mình chưa thấy PoC public nào cho đúng chuỗi PHP 8.4.26 + Apache prefork + mod_php theo kiểu “pure HTTP, không cần file target”. Các bài về session UAF trong PHP thường yêu cầu phải chèn code PHP vào page (destructor userland, session_unset()) hoặc phải có extension cụ thể. Tác giả cũng tự thừa nhận trong NOASLR_REPORT.md rằng Defect D (arData UAF) đã đạt RCE nhưng vi phạm điều kiện “không viết PHP vào target”, nên chỉ để tham khảo cơ chế. Vì vậy đừng kỳ vọng tìm một repo khác cùng loại để so sánh trực tiếp, gần như không có.
Ưu điểm và những chỗ cần dè chừng
- Minh bạch, kỹ thuật chi tiết. Có file markdown riêng cho từng giai đoạn, có gdb evidence, có bảng offset rõ ràng cho cả lab và image chính thức.
- Tự động lấy offset. Khi image update, không cần chỉnh tay. Đây là chi tiết nhỏ nhưng cứu nhiều thời gian.
- Repro cực nhanh. Ba lệnh Docker + một lệnh Python là xong, không build gì cả.
- License không rõ. README không ghi license, GitHub cũng không detect được. Nếu định dùng nội dung hay code trong repo cho mục đích khác, phải hỏi tác giả trước. Đây là điểm trừ thật sự.
- Chưa hoàn chỉnh. Case
display_errors=0chỉ mới có leak, chưa có RCE. Ai cần chain “sạch” cho production thì chưa có. - Repo rất mới. Tạo ngày 21/10, cập nhật 22/10, chỉ một người maintain. Đừng kỳ vọng có issue tracker sôi động hay version ổn định.
- Rủi ro pháp lý. Đây là exploit RCE thật. README có ghi rõ “chỉ dùng cho môi trường được uỷ quyền”. Cá nhân mình thấy câu này không phải để cho có, vì chain đã được chứng minh chạy được trên image chính thức.
Vài nhận xét cá nhân
Điều mình thích ở repo này là tác giả không thổi phồng. Đoạn “边 界” ghi thẳng những điều kiện cần có, kể cả việc display_errors=1 là bắt buộc. Nhiều PoC khác sẽ lờ đi chi tiết này để trông nguy hiểm hơn. 605 sao là con số xứng đáng cho mức độ chi tiết của tài liệu, chứ không hẳn vì độ “hot” của lỗi.
Một chỗ mình hơi nghi: tác giả nói RCE đầy đủ khi display_errors=0 “sẽ submit sau”. Có thể sẽ có, có thể không. Đừng xây workflow dựa trên giả định đó. Nếu bạn cần kiểm tra hộp PHP trong Docker của mình có dính không, chạy thử chain hiện tại là đủ biết.
Nếu bạn đang vận hành PHP 8.4.26 trên Apache prefork và chưa từng nghĩ tới việc một request multipart có thể khởi tạo session mà không cần session_start(), thì repo này là lý do để đi kiểm tra lại cấu hình. Còn nếu bạn làm security research, đây là một trong số ít repo trong tuần này đáng bỏ thời gian đọc từ đầu đến cuối: 9bie/php2shell-bypass-aslr-exp.
