Khai thác lỗ hổng PHP 8.4 qua OOM để chiếm mã thực thi: Phân tích PoC 'Steal My Bitcoin' CTF
Một PoC mã nguồn mở mô tả chi tiết cách lợi dụng lỗi tràn bộ nhớ trong quá trình phân tích upload của PHP để rò rỉ địa chỉ heap và địa chỉ mã, từ đó gọi hàm system và đạt được thực thi mã từ xa trong thử thách CTF. Bài viết phân tích chuỗi khai thác, các điểm yếu thiết kế và hàm ý bảo mật cho lập trình viên.

Khai thác PHP 8.4 qua tràn bộ nhớ: PoC "Steal My Bitcoin" và bài học bảo mật
Python 3.11+
Một kho lưu trữ GitHub có tên StealMyBitcoin-PoC đang thu hút sự chú ý của cộng đồng bảo mật. Đây là bản tái hiện một chuỗi khai thác trong thử thách CTF "Steal My Bitcoin", sử dụng một file Python duy nhất, chỉ dựa vào thư viện chuẩn. Điểm đáng chú ý không nằm ở giải thưởng, mà ở kỹ thuật: biến một lỗi tràn bộ nhớ (OOM) trong PHP thành rò rỉ địa chỉ bộ nhớ, rồi từ đó đạt được thực thi lệnh trên máy chủ.
Chuỗi khai thác gồm bốn bước
PoC mô tả một quy trình gọn gàng với tối đa 20 yêu cầu HTTP, không lặp, không thử lại:
- Bước 0 – Kiểm tra sức khỏe: gửi
GET /php-version.phpđể xác nhận máy chủ chạy PHP 8.4.26. - Bước 1 – Rò rỉ heap: một "producer" multipart khoảng 249 KB kết hợp 8 "consumer" nhỏ, đẩy tiến trình PHP vào giới hạn bộ nhớ 128 MiB. Trạng thái cũ còn sót lại trong worker bị tái sử dụng khiến cookie
PHPSESSIDtrả về 256 byte thay vì 32 byte, chứa địa chỉ heap Zend. - Bước 2 – Rò rỉ mã: lặp lại kỹ thuật trên với bố cục mảng khác, thu được cookie 16 byte chứa con trỏ tới hàm nội bộ
zval_ptr_dtor. - Bước 3 – Callback: gửi một POST khoảng 368 KB chứa HashTable giả, làm đường dọn dẹp của PHP gọi nhầm
zif_systemkèm tham số lệnh, dẫn tới thực thi lệnh trên máy chủ.
Điều thú vị là kích thước yêu cầu chỉ vài trăm KB, nhưng khi PHP phân tích cấu trúc
multipart/form-datavới hàng trăm trường lồng nhau, nó sinh ra vô số mảng PHP lồng nhau và vượt xa giới hạn bộ nhớ.
Không phụ thuộc bên ngoài
Vì sao cần hai địa chỉ?
Hệ điều hành ngẫu nhiên hóa vị trí của vùng mã và vùng heap của PHP một cách độc lập — đây chính là cơ chế ASLR. Do đó:
- Địa chỉ heap cho biết nơi đặt HashTable giả.
- Địa chỉ mã cho biết hàm cần gọi (
zif_system) nằm ở đâu.
PoC nhấn mạnh rằng địa chỉ từ một worker hay một lần chạy trước không thể dùng thay thế — mọi nỗ lực như vậy đều thất bại. Đây là điểm khác biệt so với nhiều PoC "chạy được một lần" nhưng không tái lập được.
Công thức tính địa chỉ hàm mục tiêu được hiệu chỉnh riêng cho bản Alpine PHP 8.4.26:
zif_system = zval_ptr_dtor - 0x204BC0
Và vị trí bảng giả:
fake_table = zend_heap_base - 0x8400000 + 0x16D1D8
Tác giả cảnh báo rằng khoảng cách này phụ thuộc vào image, cấu hình bộ nhớ và số worker. Chỉ cần đổi từ Alpine sang Debian với cùng số phiên bản 8.4.26 là bố cục nhị phân đã khác, khiến quá trình dò con trỏ mã thất bại hoàn toàn. Đây là lời nhắc nhở quen thuộc: chuỗi phiên bản không nói lên bố cục nhị phân.
Mục tiêu PHP 8.4.26 Alpine
Tỷ lệ thành công không tuyệt đối
Điều đáng tôn trọng ở PoC này là tác giả công bố trung thực tỷ lệ thành công thực tế. Trong 10 lần chạy liên tiếp trên môi trường lab:
- 7 lần xác minh được mã thực thi (
execution_proof_verified: true). - 2 lần chạm tới callback nhưng thiếu dấu hiệu thực thi.
- 1 lần không rò rỉ được địa chỉ heap.
Lý do là cả hai giai đoạn rò rỉ đều là cuộc đua giữa các worker. Pipelining không đảm bảo consumer nhận đúng tiến trình còn giữ trạng thái cũ. Tuy nhiên, tác giả cũng lưu ý rằng một khi container đã khởi động, địa chỉ trả về ổn định qua các lần chạy — vì ASLR là theo tiến trình, không phải theo yêu cầu.
Hàm ý bảo mật cho lập trình viên Việt Nam
Với các đội phát triển đang vận hành PHP trên môi trường container (Docker, Kubernetes) — mô hình rất phổ biến ở các startup và công ty outsourcing Việt Nam — đây là lời cảnh báo đáng lưu tâm:
- Cập nhật PHP thường xuyên. Một lỗi OOM tưởng như vô hại có thể trở thành mắt xích trong chuỗi khai thác.
- Giới hạn tài nguyên ở tầng runtime. Đặt
memory_limithợp lý không đủ nếu trạng thái lỗi để lại dữ liệu nhạy cảm trong worker. - Không tin tưởng dữ liệu multipart từ người dùng. Các trường lồng sâu với hàng trăm khóa là dấu hiệu bất thường cần bị chặn ở WAF hoặc proxy.
- Cô lập tiến trình. Chạy worker trong sandbox, tách quyền, và không để worker tái sử dụng trạng thái sau sự cố.
Tác giả PoC thẳng thắn thừa nhận mã nguồn "chưa được dọn dẹp" và kêu gọi cộng đồng cải tiến. Tinh thần chia sẻ này — kèm cảnh báo rõ ràng rằng đầu ra của một lần chạy có thể chứa khóa riêng Bitcoin thật dưới dạng WIF — là chuẩn mực tốt cho nghiên cứu bảo mật có trách nhiệm.
Lab dùng Docker
Kết luận
"Steal My Bitcoin" không chỉ là một bài CTF về tiền mã hóa. Nó là minh chứng sống động cho thấy các lỗ hổng bộ nhớ trong PHP vẫn có thể bị khai thác theo cách tinh vi, ngay cả khi không có lỗi tràn bộ đệm cổ điển. Đối với cộng đồng kỹ thuật Việt Nam, giá trị lớn nhất nằm ở phương pháp luận: quan sát, hiệu chỉnh theo môi trường cụ thể, và trung thực về giới hạn của khai thác. Một PoC khiêm tốn trên GitHub đôi khi dạy nhiều hơn cả một khóa học bảo mật dài hơi.

