Vào ngày 21 tháng 10 năm 2026, dự án PHP đã phát hành các bản cập nhật khẩn cấp cho hai lỗi nghiêm trọng cho phép kẻ tấn công chưa xác thực chạy mã trên máy chủ web. Cặp lỗi này được theo dõi với mã CVE-2026-109618 và CVE-2026-109619, mang tên PHP2Shell. Cả hai đều đạt điểm 9.2 theo CVSS v4.0, cả hai đều ảnh hưởng đến mọi cấu hình PHP phục vụ HTTP, và các honeypot của Amazon đã ghi nhận các nỗ lực khai thác vào ngày 17 tháng 10, bốn ngày trước khi bản vá tồn tại.
PHP chạy trên 69,3% các trang web có ngôn ngữ phía máy chủ được xác định (W3Techs). Nếu bạn vận hành một API PHP, một backend Laravel hay Symfony, một trang WordPress, hoặc một container image được đóng gói sẵn PHP, thì đây chính là vấn đề của bạn.
Hướng dẫn này trình bày các lỗi này gây ra điều gì, những phiên bản nào bị ảnh hưởng, cách kiểm tra máy chủ của bạn, các biện pháp giảm thiểu thực sự mang lại điều gì, và cách chứng minh API của bạn vẫn hoạt động sau khi vá.
TL;DR
| Câu hỏi | Trả lời |
|---|---|
| Đây là gì? | Hai lỗi an toàn bộ nhớ trong cơ chế xử lý yêu cầu của PHP, được kết hợp thành thực thi mã từ xa |
| CVE | CVE-2026-109618 (hỏng dữ liệu do hết bộ nhớ), CVE-2026-109619 (trạng thái cũ, use-after-free) |
| Mức độ nghiêm trọng | CVSS v4.0 9.2 cho mỗi lỗi (các ID bị rò rỉ ban đầu ghi 10.0; PHP nói rằng con số đó sai) |
| Cần xác thực không? | Không |
| Bị ảnh hưởng | Mọi SAPI phục vụ HTTP: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed |
| Phiên bản đã vá | 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
| PHP 8.1 và cũ hơn | Không có bản phát hành chính thức; chỉ có bản backport tối thiểu từ mã nguồn cho 7.4, 8.0, 8.1; từ 5.4 đến 7.3 không có gì |
| Đã bị khai thác chưa? | Đã thấy các nỗ lực từ ngày 17 tháng 10 (Amazon), Cloudflare báo cáo các cuộc tấn công sớm |
| Cách khắc phục | Nâng cấp, khởi động lại mọi worker. Các biện pháp tạm thời giảm rủi ro nhưng không bịt kín hoàn toàn |
PHP2Shell thực chất là gì
Cả hai lỗi đều có chung một nguyên nhân gốc: PHP có thể để lại bộ nhớ ở trạng thái hỏng khi một yêu cầu kết thúc bất thường, và đoạn mã dọn dẹp sau đó lại tin tưởng vào trạng thái hỏng đó.
CVE-2026-109618: hỏng dữ liệu do hết bộ nhớ trong quá trình khởi động yêu cầu. Khi PHP phân tích dữ liệu form vào $_POST, đoạn mã bảng băm của Zend engine tăng bộ đếm entry của nó trước khi cấp phát khóa mới. Phân tích của Datadog truy vết vấn đề đến _zend_hash_str_add_or_update_i() trong zend_hash.c. Nếu việc cấp phát đó vượt quá memory_limit, PHP ném ra lỗi fatal và bỏ dở thao tác chèn. Bộ đếm lúc này đã tính cả một bucket chưa từng được thiết lập. Khi tắt, PHP hủy $_POST và coi mọi bucket đã được đếm là thật.
Điều mà các đội API nên lo lắng: việc này xảy ra khi PHP vẫn đang phân tích phần thân yêu cầu, trước khi một dòng mã nào của bạn chạy. Middleware xác thực, bộ giới hạn tốc độ và khâu kiểm tra dữ liệu đầu vào của bạn không bao giờ thấy yêu cầu đó. Datadog đã tái hiện nó bằng một phần thân application/x-www-form-urlencoded thông thường, chứ không phải tải tệp lên.
CVE-2026-109619: trạng thái toàn cục cũ tồn tại qua các yêu cầu. PHP giữ một số trạng thái ở phạm vi worker trỏ đến bộ nhớ của từng yêu cầu. Nếu một lỗi fatal cắt ngang quá trình thiết lập lại trạng thái đó, hoặc một chẩn đoán lúc tắt muộn màng tạo lại nó sau khi dọn dẹp, thì yêu cầu tiếp theo trên cùng worker đó sẽ đọc vùng bộ nhớ đã được giải phóng. PHP xác nhận có hai con đường:
- Backtrace lỗi fatal (chỉ PHP 8.5): một chẩn đoán cấu hình trong yêu cầu sau đó đọc trạng thái backtrace còn sót lại. Đã xác nhận thực thi lệnh trên mod_php.
- Xử lý tải lên: sau một lỗi fatal, tiến trình tải lên của session có thể để lại trạng thái cho một lần tải lên sau đó. Tiến trình tải lên được bật mặc định và vẫn chạy ngay cả khi ứng dụng của bạn không bao giờ gọi
session_start(). Đã xác nhận thực thi lệnh trên mod_php và PHP-FPM.
Nhóm bảo mật PHP đánh giá mod_php là rủi ro cao nhất, vì khai thác được chứng minh chỉ cần một yêu cầu chưa xác thực là kích hoạt trước khi bất kỳ mã ứng dụng nào được tải.
Ai đã tìm ra (và vì sao AI lại lên mặt báo)
Các lỗi này bắt nguồn từ đoạn mã được đưa vào từ PHP 5.4.0, nghĩa là chúng đã nằm trong mã nguồn hơn một thập kỷ. PHP Foundation ghi nhận công lao cho Calif, OpenAI và Wiz.
Các bài báo từ Decrypt và The Next Web cho biết nhà nghiên cứu Ryan Austin của Calif đã tìm ra các lỗ hổng vào tháng 8 trong một cuộc kiểm tra mã nguồn có sự hỗ trợ của GPT-5.6 Sol từ OpenAI. OpenAI nói rằng nhóm của họ đã tìm ra một phiên bản đầy đủ hơn vào tháng 9 trong khuôn khổ công việc “Patch the Planet”. Wiz nói rằng họ tự tìm ra cả hai lỗi bằng Gemini 4 Argon, Wiz Atlas và GPT-5.6 Sol. Công lao còn tranh cãi; nhưng bài học thì không. Các cuộc kiểm tra có AI hỗ trợ giờ đây đang tìm ra những lỗi bộ nhớ đã tồn tại hàng thập kỷ trong hạ tầng cốt lõi, và kẻ tấn công cũng có những mô hình tương tự.
Dòng thời gian: vì sao bản vá đến sớm một ngày
| Ngày (2026) | Diễn biến |
|---|---|
| Tháng 8 đến tháng 9 | Calif, OpenAI và Wiz báo cáo các lỗi một cách riêng tư |
| 17 tháng 10 | Honeypot MadPot của Amazon ghi nhận một nỗ lực khai thác |
| 20 tháng 10 | Các ID CVE bị rò rỉ trên Hacker News và mạng xã hội; PHP đăng thông báo trước; CERT-EU ra khuyến cáo 2026-018 |
| 21 tháng 10, 14:00 UTC | PHP phát hành bản vá, được đẩy sớm từ ngày 22 tháng 10 sau vụ rò rỉ; CISA công bố cảnh báo; Cloudflare báo cáo các cuộc tấn công sớm |
| 22 tháng 10 | Cả hai CVE vẫn chưa có trong danh mục KEV của CISA |
Amazon khuyến nghị điều tra từ ngày 17 tháng 10 (FAQ của Tenable theo dõi toàn bộ dòng thời gian), chứ không phải từ ngày công bố. Khi phát hành, nhóm PHP cho biết họ đã kiểm tra mã proof-of-concept xuất hiện sau vụ rò rỉ và không tìm thấy khai thác nào hoạt động được. watchTowr sau đó nói rằng họ đã tái hiện được cả hai lỗi. Hãy coi mọi repo “PoC” bạn tìm thấy là đáng ngờ: mã khai thác chưa được kiểm chứng thường kèm theo malware.
Bạn có bị ảnh hưởng không? Kiểm tra trong năm phút
1. Kiểm tra runtime đang phục vụ lưu lượng, không phải laptop của bạn. Tệp nhị phân CLI có thể khác về phiên bản và cấu hình so với module FPM hoặc Apache.
# On the server or inside the running container
php -v
php-fpm -v
# Apache mod_php: check the loaded module version
apachectl -M 2>/dev/null | grep -i php
Hoặc công khai nó một lần từ một route được bảo vệ rồi xóa đi:
<?php echo PHP_VERSION, ' ', PHP_SAPI;
2. So sánh với các phiên bản đã vá.
| Nhánh | Bị ảnh hưởng | Được vá trong |
|---|---|---|
| 8.5 | < 8.5.12 | 8.5.12 |
| 8.4 | < 8.4.27 | 8.4.27 |
| 8.3 | < 8.3.36 | 8.3.36 |
| 8.2 | < 8.2.35 | 8.2.35 |
| 7.4 đến 8.1 | Tất cả | Chỉ có bản backport từ mã nguồn, hoặc LTS của nhà cung cấp |
| 5.4 đến 7.3 | Tất cả | Không có bản vá |
Debian, Ubuntu, Red Hat và các bản phân phối khác thường backport bản vá mà không thay đổi chuỗi phiên bản gốc. Nếu php -v hiển thị số phiên bản cũ hơn, hãy đọc khuyến cáo của bản phân phối bạn dùng trước khi hoảng.
3. Kiểm kê PHP ẩn. Các container được build FROM php:8.3-fpm từ nhiều tháng trước, các thiết bị của nhà cung cấp, các bản build tự biên dịch, các bảng quản trị trên những subdomain bị lãng quên. Cisco đã mở một khuyến cáo để kiểm tra dòng sản phẩm của chính mình. Shodan đếm được hơn 1,9 triệu máy chủ PHP hướng ra internet; một nhà nghiên cứu ước tính 1,3 triệu hoặc hơn có thể đang bị ảnh hưởng.
Cách vá
# Debian / Ubuntu (Ondřej Surý PPA or distro packages)
sudo apt update && sudo apt install --only-upgrade 'php8.*'
# RHEL / Rocky / Alma with Remi
sudo dnf upgrade 'php*'
# Official Docker image: rebuild on a fixed tag
docker pull php:8.4.27-fpm
docker build --pull -t my-api:patched .
Sau đó làm bước mà hầu hết các đội bỏ qua: khởi động lại hoặc thay thế mọi worker PHP. Một pool FPM đang chạy vẫn giữ tệp nhị phân cũ trong bộ nhớ. Khởi động lại php-fpm, tải lại Apache, và triển khai lại (roll) mọi container. CVE-2026-109619 liên quan đến trạng thái tồn tại qua các yêu cầu bên trong một worker, nên một worker chưa được khởi động lại vẫn còn dễ bị tấn công.
Trên 8.1 hoặc cũ hơn, bạn có ba lựa chọn: gói LTS của nhà cung cấp, build từ bản backport mã nguồn của PHP, hoặc nâng cấp lên 8.2+. CISA khuyến nghị đưa các hệ thống PHP không thể vá được ra khỏi mạng, và NCSC của Anh cùng FBI cũng nói như vậy.
Các biện pháp giảm thiểu nếu hôm nay bạn không thể vá
Những biện pháp này chỉ câu giờ. Gina Peter Banyard thuộc nhóm core của PHP nói với BleepingComputer rằng không biện pháp giảm thiểu nào chặn được hoàn toàn các cuộc tấn công.
Đối với CVE-2026-109618:
- Đặt
memory_limit=-1ở nơi ứng dụng không thể ghi đè (php_admin_valuetrong pool FPM hoặc cấu hình Apache). Điều này chặn con đường đã biết, nhưng không chặn mọi cách mà việc cấp phát có thể thất bại. - Giảm
post_max_size(8M nếu không tải tệp lên, 32M nếu có tải tệp lên). - Giới hạn kích thước phần thân yêu cầu tại reverse proxy (
client_max_body_sizetrong nginx), và đặt giới hạn bộ nhớ cho container hoặc cgroup như một ranh giới về tính khả dụng. - Không dùng giới hạn tiến trình
RLIMIT_AShoặcRLIMIT_DATA. Chúng có thể gây ra chính những thất bại cấp phát mà khai thác cần.
Đối với CVE-2026-109619:
- Đặt
session.upload_progress.enabled=0trong cấu hình được tải trước khi quá trình phân tích yêu cầu diễn ra. - Trên PHP 8.5, cũng đặt
fatal_error_backtraces=0. - Ẩn lỗi bằng
display_errors=0hay@không có tác dụng gì. Trạng thái cũ vẫn tồn tại.
Ở lớp biên: Cloudflare đã triển khai các quy tắc WAF cho những vector đã biết trên mọi gói, kể cả gói miễn phí. CTO của họ gọi đó là các biện pháp tạm thời.
Sau bất kỳ thay đổi nào trong php.ini, hãy khởi động lại mọi worker, nếu không thay đổi đó không có hiệu lực.
Dấu hiệu bạn đã bị tấn công
Hướng dẫn phát hiện của Datadog quy về bốn tín hiệu:
- Dòng log:
PHP Fatal error: Allowed memory size of ... bytes exhaustedkèmUnknown on line 0. Điều đó nghĩa là PHP đã chết trong lúc khởi động yêu cầu, trước khi bất kỳ script nào chạy. - Apache bị crash:
AH00052: child pid ... exit signal Segmentation fault (11), khả năng cao là một phỏng đoán khai thác thất bại. - Các bài post form lạ: một tên trường dài vài megabyte, hoặc các byte nhị phân và null trong một trường có tên rỗng.
- Tín hiệu mạnh nhất: một worker web (
php-fpm,apache2,httpd) sinh ra một shell.
Đừng tin vào mã trạng thái. Bản khai thác tinh chỉnh của Datadog trả về HTTP 200 và worker vẫn tiếp tục chạy. Hãy tìm trong log ngược về ngày 17 tháng 10.
Chứng minh API của bạn vẫn hoạt động sau khi vá: để agent của bạn chạy kiểm thử
Vá PHP trên môi trường production dưới áp lực là cách bạn phát hiện ra rằng một bản nâng phiên bản nhỏ đã làm thay đổi điều gì đó. Trước và sau khi triển khai bản sửa, hãy chạy bộ kiểm thử API của bạn trên môi trường staging, và để coding agent của bạn thực hiện việc đó với Apidog CLI.
Thiết lập (một lần):
npm install -g apidog-cli
apidog auth login --with-token <your-access-token>
apidog test-suite list --project <projectId>
Một câu lệnh cho Claude Code, Codex hoặc Cursor:
Chúng tôi vừa nâng cấp PHP lên 8.4.27 trên staging để xử lý PHP2Shell. Hãy dùng Apidog CLI để chạy bộ kiểm thử hồi quy của chúng tôi trên môi trường staging, tạo báo cáo HTML và JUnit, và tóm tắt các endpoint bị lỗi. Sau đó xác nhận rằng một POST với phần thân vượt quá client_max_body_size của nginx sẽ bị từ chối với mã 413 tại proxy thay vì đi tới PHP.Những gì agent sẽ chạy:
apidog test-suite run <testSuiteId> --project <projectId> -e <stagingEnvId>
apidog run --test-suite <testSuiteId> --project <projectId> -e <stagingEnvId> \
-r cli,html,junit --out-dir ./php2shell-reports --upload-report
-r chọn các trình báo cáo, --out-dir đặt nơi báo cáo được lưu, và --upload-report đẩy lên một bản tóm tắt mà bạn có thể mở trong ứng dụng Apidog. Thêm --notification-failed-event <targetId> để chỉ ping nhóm của bạn khi có sự cố. Hãy đưa nguyên dòng apidog run đó vào CI để mọi image PHP được build lại đều được kiểm thử trước khi phát hành. Hướng dẫn thiết lập Claude Code và bài hướng dẫn quy trình CI/CD trình bày chi tiết cả hai.
Hãy chạy bộ kiểm thử một lần trên phiên bản cũ để lấy đường cơ sở, một lần sau khi nâng cấp, rồi so sánh. Nếu một bài kiểm thử mà bạn không hề nghĩ sẽ thất bại lại thất bại, bạn sẽ biết trước cả người dùng của mình. Tải Apidog để xây dựng và quản lý các bộ kiểm thử mà CLI chạy.
Câu hỏi thường gặp
PHP2Shell có đang bị khai thác không? Amazon đã thấy các nỗ lực từ ngày 17 tháng 10 và nói rằng nó hiện đang bị khai thác rộng rãi; Cloudflare và watchTowr cũng báo cáo các cuộc tấn công. Tính đến ngày 22 tháng 10, CISA vẫn chưa xác nhận việc bị khai thác.
Framework của tôi có bảo vệ tôi không? Không. CVE-2026-109618 kích hoạt trong khi PHP phân tích phần thân yêu cầu, trước khi Laravel, Symfony hay WordPress được tải.
Tôi đang dùng shared hosting. Tôi phải làm gì? Hãy kiểm tra trang trạng thái của nhà cung cấp hosting. SiteGround và những đơn vị khác nói rằng họ đã vá. Nếu nhà cung cấp của bạn chưa nói gì, hãy hỏi họ.
Đặt session.upload_progress.enabled=0 có đủ không? Không. Nó chỉ bao phủ một con đường của một CVE. Hãy vá.
Tôi có cần thay đổi/xoay vòng các secret không? CVE-2026-109619 cho phép rò rỉ bộ nhớ. Nếu log cho thấy dấu hiệu bị xâm phạm từ ngày 17 tháng 10, hãy xoay vòng mật khẩu cơ sở dữ liệu, khóa API và các secret của session mà tiến trình PHP có thể đọc được.
Kết luận
Hãy nâng cấp lên 8.5.12, 8.4.27, 8.3.36 hoặc 8.2.35. Khởi động lại mọi worker. Tìm những instance PHP mà bạn đã quên. Kiểm tra log ngược về ngày 17 tháng 10. Sau đó chạy các bài kiểm thử API của bạn để bản sửa lỗi không trở thành sự cố gián đoạn tiếp theo. Nếu PHP của bạn đang ở 8.1 hoặc cũ hơn, đây chính là tuần để cuối cùng rời bỏ nó.
