0

PHP dính lỗ hổng CVE nghiêm trọng 9.2 — Bạn đã nâng cấp chưa?

Hôm qua, PHP đã công bố hai lỗ hổng thực thi mã từ xa (RCE) nghiêm trọng, được gắn mã CVE-2026-109618 và CVE-2026-109619, với điểm CVSS là 9.2. Theo thông báo trước chính thức và bản tin từ Nhóm Ứng phó Sự cố Máy tính Châu Âu (CERT-EU), khung thời gian vá lỗi được ấn định vào 22:00 giờ Bắc Kinh ngày 21 tháng 10.

Thông tin này đã làm dấy lên cuộc thảo luận rộng rãi trong cộng đồng nhà phát triển — thậm chí còn lan tới cả Thủ tướng Nhật Bản Sanae Takaichi (thử kể tên một runtime ngôn ngữ khác được người đứng đầu chính phủ nhắc tới xem. PHP thực sự đã thắng lớn ở đây).

Trong hàng trăm bình luận trên thread nóng trên Hacker News, các đội kỹ thuật liên tục xoay quanh hai chủ đề. Thứ nhất, các bản build của distro, việc xác minh và phân phối gói luôn để lại một khoảng trễ thời gian trước khi bản vá đến được mọi nơi. Thứ hai, rất nhiều dự án PHP cũ vẫn đang chạy nhưng không còn được upstream hỗ trợ bản vá chính thức — và việc nhảy qua các phiên bản major đi kèm rủi ro lớn về di chuyển mã nguồn và tương thích extension.

ServBay đã phát hành bản cập nhật bảo mật cho sự cố PHP này, đồng thời cũng đã phát hành hỗ trợ bản vá cho các phiên bản PHP cũ hơn. Với những nhà phát triển đang duy trì dự án đa phiên bản hoặc hệ thống legacy, các bản build đã được vá đều có sẵn — nên phần khó không còn là tìm bản vá nữa. Bước tiếp theo mang tính quyết định là xác nhận rằng các binary đã cập nhật thực sự là thứ mà mọi site mục tiêu đang chạy.

Khi một bản vá bảo mật PHP được phát hành, việc thấy một lệnh nâng cấp chạy thành công trong terminal không có nghĩa là các site đang chạy thật và các job nền của bạn đã thoát khỏi nguy hiểm. Trên một server hoặc máy nhà phát triển có nhiều phiên bản PHP, dòng lệnh (CLI), đường dẫn request Web (PHP-FPM) và các queue worker sống lâu đều nạp binary và cấu hình riêng của chúng. Chỉ khi đi qua bốn cửa — gói đã cài → site đã chọn → tiến trình đã chuyển → kiểm thử hồi quy đã qua — và giữ lại bằng chứng runtime ở từng bước, bạn mới có thể chứng minh bản vá mới thực sự đang phục vụ lưu lượng production.

Vì sao đã cài đặt ≠ đang chạy

Nhiều lập trình viên dựa vào các trợ lý lập trình AI để sinh ra lệnh nâng cấp. Sau khi quá trình nâng cấp gói hoàn tất và terminal in ra một chuỗi phiên bản mới, rất dễ cho rằng bản vá đã xong. Trong môi trường đa phiên bản, giả định đó chính là cách kinh điển để lỗ hổng vẫn còn bỏ ngỏ.

Trên bất kỳ một máy nào, PHP thường có ít nhất ba đường thực thi độc lập.

Điểm vào Terminal / CLI

Khi lập trình viên gõ php -v trong một phiên SSH hoặc terminal cục bộ, hệ điều hành sẽ phân giải file thực thi từ PATH của người dùng hiện tại. Sau khi cập nhật gói, symlink mặc định có thể đã trỏ tới bản build mới — nhưng điều đó chỉ thay đổi hành vi của phiên CLI hiện tại. Nó không có liên kết trực tiếp nào tới các tiến trình phục vụ lưu lượng HTTP.

Điểm vào FastCGI của web server (PHP-FPM)

Khi Nginx hoặc Apache xử lý các request động, chúng chuyển tiếp qua fastcgi_pass tới một địa chỉ listen của PHP-FPM (ví dụ 127.0.0.1:9000) hoặc một Unix domain socket (ví dụ /run/php/php8.2-fpm.sock).

Ở đây xuất hiện hai kiểu lệch pha kinh điển.

Thứ nhất là sự cùng tồn tại giữa các nhánh: hệ thống có thể đã cài PHP 8.3, trong khi cấu hình Nginx của site vẫn kết nối tới php8.1-fpm.sock, nên lưu lượng từ bên ngoài vẫn tiếp tục đập vào nhánh cũ.

Thứ hai là cập nhật minor trong cùng một nhánh: file binary trên đĩa đã được thay thế, nhưng nếu tiến trình master php-fpm đang chạy không hề nhận được tín hiệu reload hay restart, các worker còn thường trú trong bộ nhớ vẫn tiếp tục thực thi chương trình cũ.

Các tiến trình nền chạy dài hạn (Queue Worker)

Những dự án dùng Laravel Queue, Symfony Messenger, Swoole hay RoadRunner thường giữ các worker dưới Supervisor hoặc systemd, thường trú trong bộ nhớ trong thời gian dài. Ngay cả khi CLI và PHP-FPM cho Web đã được cập nhật, các consumer của queue và các job theo lịch vẫn chạy bên trong image cũ trong bộ nhớ cho đến khi bạn restart rõ ràng các worker đó.

Quy trình xác minh khép kín bốn bước

Để chứng minh một bản vá thực sự đang có hiệu lực trên production hoặc staging, bạn không thể dừng lại ở thông báo "cài đặt thành công". Bạn cần bằng chứng tại thời điểm chạy (runtime) được thu thập qua bốn bước.

Đang tải...

Bước 1 — Kiểm kê mọi bản build PHP trên máy

Trước và sau khi nâng cấp, hãy liệt kê toàn bộ đường dẫn tệp nhị phân PHP CLI và FPM trên host để biết được mốc chuẩn (baseline).

# Liệt kê mọi tệp thực thi `php` trên PATH
which -a php
# Hiển thị phiên bản CLI mặc định và thời điểm build
php -v
# Tìm các tiến trình php-fpm đang chạy (và cấu hình của chúng)
ps aux | grep php-fpm

Nếu bạn dùng trình quản lý gói của Linux, hãy xác nhận tên gói và phiên bản build đã cài có bao gồm các bản vá cho CVE-2026-109618 và CVE-2026-109619.

# Debian / Ubuntu: liệt kê phiên bản gói PHP CLI/FPM đã cài
dpkg -l | grep -E "php[0-9]+\.[0-9]+-(cli|fpm)"
# RHEL / Rocky / AlmaLinux: liệt kê các gói PHP đã cài
rpm -qa | grep -E "php-(cli|fpm)"

Bước 2 — Xác minh liên kết FastCGI của site đích

Đừng mặc định rằng lệnh nâng cấp đã ghi lại cấu hình Web server của bạn. Hãy mở tệp virtual-host Nginx hoặc Apache của site và tìm địa chỉ thật đứng sau fastcgi_pass hoặc SetHandler.

# Tìm mục tiêu fastcgi_pass trong toàn bộ cấu hình site
grep -rn "fastcgi_pass" /etc/nginx/conf.d/ /etc/nginx/sites-enabled/

Khi đã có đường dẫn socket hoặc cổng, hãy ánh xạ ngược lại PID của tiến trình đang lắng nghe và tệp nhị phân thật mà tiến trình đó đang thực thi.

# Xác định PID master của php-fpm đứng sau socket (điều chỉnh đường dẫn)
sudo lsof /run/php/php8.3-fpm.sock
# Kiểm tra tệp nhị phân trên đĩa mà PID đó thực sự đang chạy (thay 12345)
sudo ls -l /proc/12345/exe

Trên Linux, nếu tệp nhị phân trên đĩa đã được cập nhật nhưng tiến trình chưa từng được khởi động lại, /proc/PID/exe sẽ kết thúc bằng dấu (deleted). Đó là bằng chứng trực tiếp cho thấy trên đĩa đã là bản mới trong khi trong bộ nhớ vẫn còn là bản cũ.

Bước 3 — Khởi động lại dịch vụ và các worker chạy nền

Khi cấu hình site đã trỏ đúng nhánh PHP, hãy khởi động lại dịch vụ PHP-FPM tương ứng và mọi tiến trình queue chạy nền.

# Khởi động lại dịch vụ PHP-FPM tương ứng (điều chỉnh phiên bản)
sudo systemctl restart php8.3-fpm
# Xác nhận PID master mới và thời điểm khởi động
sudo systemctl status php8.3-fpm

Với các dự án dùng queue bất đồng bộ hoặc runtime trong bộ nhớ, hãy chạy tiếp các lệnh làm mới ở cấp framework và trình quản lý tiến trình.

# Laravel: yêu cầu các queue worker thoát sau job hiện tại rồi nạp lại
php /var/www/project/artisan queue:restart
# Supervisor: khởi động lại (các) nhóm worker
sudo supervisorctl restart all

Bước 4 — Kiểm tra runtime có kiểm soát và regression nghiệp vụ

Để xác minh phiên bản PHP thực sự đang phục vụ lưu lượng Web, đừng để một tệp phpinfo.php truy cập được công khai trong thư mục gốc của site — nó làm lộ biến môi trường và cấu trúc đường dẫn ra internet. Nên dùng một script tối giản, chỉ chạy một lần và chỉ truy cập được từ địa chỉ loopback (127.0.0.1 / ::1), in ra phiên bản runtime và thông tin SAPI, rồi xóa ngay lập tức.

# 1. Tạo script kiểm tra một lần, chỉ truy cập được từ loopback
cat << 'EOF' > /var/www/project/public/.patch_check.php
<?php
if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1' && $_SERVER['REMOTE_ADDR'] !== '::1') {
    http_response_code(403);
    exit('Forbidden');
}
header('Content-Type: application/json');
echo json_encode([
    'sapi' => PHP_SAPI,
    'version' => PHP_VERSION,
    'version_id' => PHP_VERSION_ID,
    'ini_loaded' => php_ini_loaded_file(),
    'opcache_enabled' => function_exists('opcache_get_status') && opcache_get_status(false) !== false,
], JSON_PRETTY_PRINT);
EOF
# 2. Gọi nó từ chính server với Host header thật
curl -s -H "Host: example.com" http://127.0.0.1/.patch_check.php
# 3. Xóa ngay sau khi xác nhận phiên bản
rm -f /var/www/project/public/.patch_check.php

Khi JSON cho thấy sapi là fpm-fcgi và version khớp với bản phát hành đã được vá, đường đi Web đã thực sự chuyển sang bản mới. Tiếp theo, xác nhận các extension cốt lõi mà dự án phụ thuộc (ví dụ redis, pdo_mysql, bcmath, opcache) vẫn nạp được, và chạy kiểm thử regression trên các API quan trọng.

Bản vá cho phiên bản cũ và chuyển đổi phiên bản theo từng dự án

Trong công việc bảo trì thực tế, nhiều đội ngũ rơi vào thế khó. Một bên, họ chạy các dự án mới trên PHP 8.3 song song với những hệ thống cũ vẫn còn dùng PHP 7.x — hoặc cũ hơn. Bên còn lại, các mirror package truyền thống thường chỉ phát hành bản cập nhật cho những nhánh đang được hỗ trợ; buộc một codebase cũ phải nhảy lên major version mới sẽ phá vỡ cú pháp lịch sử và các extension lỗi thời chỉ sau một đêm.

Để giải quyết nỗi đau đó, ServBay không chỉ vá các nhánh PHP phổ biến hiện nay sớm nhất có thể, mà còn phát hành bản vá lỗ hổng cho các phiên bản PHP cũ hơn. Kết hợp với cách ly phiên bản PHP theo từng dự án của ServBay, người bảo trì có thể tăng cường bảo mật mà không phá vỡ khả năng tương thích trên code cũ. Xem thêm PHP versio

n features.

Trong môi trường đa phiên bản hỗn hợp, mô hình này giải quyết ba bài toán kỹ thuật:

  • •Vá các dự án cũ mà không cần viết lại: Giữ nguyên nhánh major ban đầu, cài gói bảo mật và khởi động lại dịch vụ — bịt lỗ hổng nghiêm trọng mà không cần refactor xuyên major.
  • •Gắn site rõ ràng với runtime mà nó dùng: Mỗi site hoặc dự án chọn tường minh phiên bản PHP của mình, nên lưu lượng không thể âm thầm tiếp tục đổ vào socket cũ sau khi nâng cấp toàn host.
  • •Hợp nhất việc chuyển phiên bản với kiểm chứng runtime: Thay đổi PHP của dự án và xác nhận CLI, FPM cùng các worker thực sự nạp gì trở thành một quy trình duy nhất, thay vì hai việc rời rạc.

Danh mục kiểm tra xác minh nâng cấp và FAQ

Danh mục kiểm tra nâng cấp môi trường PHP đa phiên bản

Hạng mục kiểm tra Cách xác minh Tiêu chí đạt
Gói đã được cài which -a php, php -v, dpkg -l / rpm -qa Bản build đã được vá cho CVE-2026-109618 / CVE-2026-109619 có mặt trên đĩa
Site đã được chọn Kiểm tra fastcgi_pass / SetHandler (hoặc thiết lập PHP theo từng site của ServBay) Vhost trỏ đúng tới socket hoặc port của nhánh đã được vá
Tiến trình đã được chuyển systemctl restart cho FPM; queue:restart / supervisorctl cho worker; ls -l /proc/PID/exe Không còn dấu (deleted); binary của PID khớp với đường dẫn đã được vá
Kiểm thử hồi quy đã đạt .patch_check.php chỉ chạy qua loopback + kiểm thử extension lõi / API sapi là fpm-fcgi, phiên bản khớp với bản vá, các luồng quan trọng vẫn chạy tốt

FAQ

Q1: Tại sao terminal hiển thị phiên bản mới nhất qua php -v nhưng website vẫn chạy bản build cũ?

php -v chỉ phản ánh binary CLI được phân giải từ PATH hiện tại của bạn. Lưu lượng HTTP đi qua Nginx/Apache tới socket hoặc port của PHP-FPM. Nếu vhost vẫn bind vào socket cũ, hoặc các worker FPM chưa từng được reload, phía Web sẽ tiếp tục chạy phiên bản cũ dù CLI trông như mới nhất.

Q2: Chúng tôi duy trì các dự án legacy không thể chuyển lên PHP 8.3. Làm sao để khắc phục lỗ hổng nghiêm trọng này?

Nhảy lên phiên bản major trên các ứng dụng cũ thường gây lỗi cú pháp và làm hỏng các thư viện bên thứ ba. Với những dự án cũ chưa thể refactor ngay, hãy dùng một môi trường như ServBay — nơi đã có sẵn các bản vá bảo mật cho các phiên bản PHP cũ: cập nhật gói vá trên chính nhánh ban đầu và khởi động lại các service. Bạn bịt được lỗ hổng nghiêm trọng trong khi vẫn giữ nguyên logic nghiệp vụ và các extension hiện có.

Q3: Sau khi áp dụng bản vá bảo mật trên cùng một nhánh, tại sao các job queue chạy nền vẫn không bị ảnh hưởng?

Các worker chạy dài như Laravel Queue, Swoole hay Workerman nạp trình thông dịch PHP vào bộ nhớ ngay khi khởi động. Việc cập nhật gói trên đĩa hoặc khởi động lại PHP-FPM không làm gián đoạn các tiến trình đó. Bạn cũng phải chạy php artisan queue:restart (hoặc khởi động lại nhóm worker Supervisor tương ứng) để các tác vụ nền nạp binary đã được vá.

Tóm tắt

Khi phải xử lý những vấn đề nghiêm trọng như CVE-2026-109618 và CVE-2026-109619, việc tải về và cài đặt một bản build mới chỉ là vạch xuất phát — xác nhận rằng các tiến trình đang chạy thực sự đã nạp bản vá mới mới là vạch đích. Trong môi trường kỹ thuật đa phiên bản, chỉ dựa vào một dòng kết quả php -v là cách rất dễ bị đánh lừa bởi những kết quả bề mặt.

Một chiến lược nâng cấp vững chắc có hai mặt. Về phía môi trường, hãy dùng một công cụ như ServBay — công cụ đã phát hành sẵn bản vá bảo mật cho cả các nhánh PHP chính thống lẫn các nhánh cũ, đồng thời hỗ trợ gán phiên bản PHP theo từng dự án — giải quyết cùng lúc hai vấn đề: "ứng dụng cũ không thể vượt qua các bản major" và "cấu hình nhiều site trở nên hỗn loạn". Về phía xác minh khi đưa lên môi trường thật, hãy tuân thủ nghiêm ngặt danh sách kiểm tra bốn bước — đã cài gói, đã chọn site, đã chuyển tiến trình, đã vượt qua kiểm thử hồi quy — và ghi lại trạng thái tiến trình thực tế cho CLI, PHP-FPM và các hàng đợi thường trú. Biến việc xác minh đó thành SOP của nhóm, và lần cập nhật bảo mật runtime tiếp theo sẽ có thể kiểm toán được và triển khai một cách bình tĩnh. Nắm chắc quy trình trước, bạn có thể triển khai bản vá tiếp theo mà không phải đau đầu.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí