Welcome to MikeDaDev's Blog – notes on Java, backend & system design.

Explore
MikeDaDev's Blog

PHP2Shell: hai lỗ hổng RCE CVE-2026-109618 và CVE-2026-109619

Tung Bui Thanh (TungDaDev)22 mins read

Lines of html codes

Sáng thứ Tư 21/10, bạn mở Slack thấy kênh sự cố nhảy đỏ. Có người dán link thông báo của php.net: hai lỗ hổng RCE, không cần đăng nhập, điểm CVSS 9.2, "update now". Câu hỏi đầu tiên của cả team: "Mình đang chạy PHP bản nào nhỉ?"

Nghe đơn giản, nhưng thử trả lời xem. Con WordPress của phòng marketing nằm trên shared hosting. Cái portal nội bộ viết bằng Laravel chạy trong Docker, image build từ ba tháng trước. Con server cũ cài Ubuntu 22.04 bằng apt, php -v báo 8.1.2, mà bản 8.1 thì upstream đã ngừng hỗ trợ từ lâu. Còn phía trước tất cả là CDN và WAF, nên ai cũng yên tâm rằng "có tường lửa rồi".

Nếu đang ở vị trí đó, thứ bạn cần là biết lỗi nằm ở đâu, mình có dính không và vá thế nào cho đúng. Mọi dữ kiện lấy từ advisory của PHP, CISA và các distro. Chỗ nào chỉ có một nguồn thứ cấp hoặc các nguồn lệch nhau thì mình ghi rõ.

Lưu ý: bài chỉ mô tả cơ chế ở mức đủ để hiểu và phòng thủ. Không có exploit hay payload nào ở đây. Đoạn code PHP/C dưới đây là minh hoạ đơn giản hoá của mình, không phải code gốc của php-src.

# dòng thời gian

Các mốc dưới đây lấy từ trang tin của php.net, hai advisory trên GitHub và bản tin của CISA.

ngày (2026)sự kiện
20/10PHP báo trước sẽ có bản phát hành bảo mật khẩn vào 21/10 lúc 14:00 UTC, hai lỗ hổng cùng CVSS v4.0 9.2. Thông báo nói rõ team có quyền ra sớm hơn nếu chi tiết bị lộ hoặc có khai thác ngoài thực tế
21/10Phát hành PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35. Hai advisory (GHSA) được công bố cùng ngày. PHP cũng đưa ra bản backport tối thiểu cho 7.4, 8.0, 8.1 dưới dạng branch trên GitHub
23/10PHP đăng "Informational advisory: Session upload progress", nhắc tới CVE-2026-115560, khuyên tắt session.upload_progress.enabled
26/10CISA thêm cả CVE-2026-109618 và CVE-2026-109619 vào danh mục Known Exploited Vulnerabilities (KEV). Cùng ngày PHP đăng bản rebuild Windows (xem phần cuối)

Một chi tiết mình thấy đáng nói: theo bài của Bitsight, ngày 20/10 có một bài đăng mạng xã hội chưa kiểm chứng nói trước PHP sẽ công bố hai lỗ hổng, kèm luôn các CVE ID đã đặt trước, và nó lan rất nhanh. Cũng theo bài đó, một công cụ khai thác tên PHP2Shell bị rò rỉ trước khi có bản vá. Đây là thông tin từ một nguồn thứ cấp, mình không tìm được nguồn gốc nào xác nhận ngày cụ thể của vụ rò rỉ, nên đừng coi nó là mốc chắc chắn.

Còn chuyện "bị khai thác thật" thì có nguồn gốc: việc CISA đưa vào KEV nghĩa là họ có bằng chứng khai thác. Tuy vậy bản tin CISA mình đọc được không nêu chi tiết thủ phạm hay quy mô. Phía vendor thì có người khẳng định khai thác rộng (một bài tổng hợp trích Amazon Threat Intelligence), nhưng Fortinet lại viết rằng kịch bản mod_php trong advisory là demo của nhà nghiên cứu. Cả hai điều này cùng đúng được: có khai thác thật, nhưng độ tin cậy của exploit trên từng kiểu deploy khác nhau rất nhiều, mình sẽ quay lại ở phần cơ chế.

# hai lỗi, một họ nguyên nhân

Cả hai CVE đều nằm trong lõi PHP chứ không phải trong WordPress hay plugin nào. Cập nhật CMS không làm mất lỗi này. Điểm chung là: PHP xử lý lỗi theo một cách mà để lại bộ nhớ ở trạng thái không hợp lệ, và đoạn code dọn dẹp phía sau tin rằng bộ nhớ đó vẫn ổn. Advisory gắn cả hai vào nhóm CWE-416 (use-after-free) cùng mấy mã liên quan tới xử lý lỗi và trạng thái.

CVE-2026-109618: out-of-memory và longjmp

Theo GHSA-w8hw-w9fx-j79w, khi hết bộ nhớ, PHP dùng longjmp() để thoát khỏi request hiện tại. longjmp là kiểu "nhảy thẳng ra ngoài": bỏ qua mọi hàm đang lồng nhau, không chạy đoạn code nào đang dở.

Hình dung việc đi chợ mua một lượt chục món: bạn đang bỏ đồ vào giỏ, đang ghi dở danh sách thì chợ cúp điện, bạn bị đẩy ra cổng. Lát sau có người đi dọn, cứ theo tờ danh sách mà thu từng món. Nhưng tờ danh sách ghi dở nửa chừng, có dòng trỏ vào ô hàng đã bị dọn mất, có dòng ghi một con số rác. Người dọn tin tờ giấy và làm theo.

Trong PHP thì "tờ danh sách" là các cấu trúc dữ liệu của request, như mảng $_FILES, thông tin upload. Có những ngữ cảnh mà vùng nhớ này đã nằm trong tầm với của đoạn dọn dẹp trước khi được khởi tạo xong. Nếu một lần cấp phát bộ nhớ thất bại đúng lúc đó, longjmp thoát ra, vùng nhớ nằm dở, và đoạn shutdown của request đọc hoặc rẽ nhánh dựa trên dữ liệu rác. Advisory nhấn mạnh một điểm hay: lần cấp phát thất bại không cần liên quan tới vùng nhớ hỏng. Chỉ cần làm cho nó hỏng đúng thời điểm.

Minh hoạ đơn giản hoá (không phải code của php-src):

// minh hoa don gian hoa, KHONG phai code goc
struct upload_info *info = emalloc(sizeof *info);   // da "nhin thay" boi cleanup
info->files = NULL;                                  // chua khoi tao xong
 
parse_next_part(info);   // emalloc() that bai o day -> longjmp ra ngoai
                         // khong dong nao ben duoi duoc chay
 
// ... o ham shutdown cua request:
for (f = info->files; f; f = f->next)   // tin rang info->files hop le
    efree(f->tmp_name);                 // neu files tro vao rac: tham nhung bo nho

Làm sao kẻ tấn công ép được hết bộ nhớ? Advisory liệt kê vài cách, và đều là kiểu "request trông bình thường nhưng ngốn nhiều RAM hơn vẻ ngoài của nó":

  • Request multipart không có Content-Length dùng được có thể lọt qua bước kiểm tra post_max_size ban đầu.
  • Một part multipart có thể chứa số header không giới hạn, mà header trong bộ nhớ PHP tốn nhiều hơn nhiều so với trên đường truyền.
  • Các ô file rỗng không tính vào max_file_uploads, nhưng vẫn sinh ra mảng metadata, và tên field lồng nhau nhân việc đó lên.

Về chuyện ai bị thật: advisory nói rõ Apache mod_php là nơi đã xác nhận, RCE đầy đủ được kiểm chứng qua HTTP với cấu hình giới hạn mặc định. PHP-FPM được đánh giá là có khả năng bị dựa trên phân tích mã nguồn, nhưng chưa có exploit hoàn chỉnh được trình diễn. FrankenPHP và LSAPI "likely" nhưng chưa kiểm chứng.

CVE-2026-109619: state cũ sống sót sang request sau

GHSA-6wmf-4rv5-p3rp mô tả lỗi thứ hai. Một worker PHP sống qua nhiều request, và giữ vài thứ "toàn cục" (trạng thái lỗi, session, object, state của extension). Các thứ đó đôi khi trỏ vào bộ nhớ thuộc về request đã kết thúc. Bình thường chúng được reset trước khi heap của request bị giải phóng. Nhưng fatal error, kể cả hết bộ nhớ hay Maximum execution time exceeded, có thể bỏ qua bước reset, và các cảnh báo không-fatal lúc shutdown còn có thể điền lại state lỗi.

Kết quả: request sau, chạy trên cùng worker, có một số thao tác chạy trước khi khởi tạo request bình thường (ví dụ cảnh báo INI sớm, hoặc xử lý session upload progress) và chúng dùng lại con trỏ đã bị free. Đó là use-after-free, dẫn tới lộ bộ nhớ và RCE với quyền của worker web. Điều kiện: request "sinh lỗi" và request "dùng lỗi" phải rơi vào cùng một worker.

Hai trường hợp đã xác nhận trong advisory: PHP 8.5 có bật fatal_error_backtraces (demo trên mod_php), và vòng đời session upload progress được kích bởi OOM lúc xử lý input (kiểm chứng trên mod_php và FPM).

Gộp lại, một đường tấn công khả dĩ (mô tả ở mức sơ đồ, không phải quy trình khai thác):

# vì sao "không cần xác thực" lại nghiêm trọng đến vậy

Nghe CVSS 9.2 thì dễ lướt qua. Nhưng hãy để ý vector của advisory: AV:N/AC:H/AT:P/PR:N/UI:N. Tức là tấn công qua mạng, không cần đặc quyền, không cần người dùng làm gì. Hai chữ AC:H và AT:P cho thấy PHP tự đánh giá độ phức tạp cao, cần điều kiện đặc biệt. Vì thế các vendor chấm điểm lệch nhau: NVD và Ubuntu ghi CVSS v3 9.8, còn Red Hat chấm CVE-2026-109618 là 8.1, mức "Important". Lý do Red Hat đưa ra là khai thác ổn định cần thao túng heap chính xác và canh thời điểm, và RHEL tiêu chuẩn chạy PHP-FPM chứ không phải mod_php.

Mình thấy lập luận của Red Hat hợp lý về mặt xác suất, nhưng không nên dùng nó làm lý do để trì hoãn. Lý do thứ nhất là CISA đã đưa vào KEV, tức đã có người khai thác thật. Lý do thứ hai là điều kiện "cùng worker" ở CVE-2026-109619 nghe khó, nhưng với kẻ tấn công gửi hàng nghìn request thì chỉ là chuyện xác suất và thời gian. Thứ ba, advisory của PHP nói thẳng: đây là lỗi xảy ra trước khi code ứng dụng của bạn chạy, nên mọi lớp bảo vệ ở tầng ứng dụng (framework, middleware, validate input) đều đứng ngoài cuộc.

Chuyện WAF cũng cần nói cho rõ. Theo NCSC-NL, các advisory nêu rằng không có biện pháp cấu hình chung nào thay được việc vá, và WAF hay cập nhật plugin không loại bỏ lỗi. WAF có thể chặn một số mẫu request, nhưng nó không biết request nào làm PHP hết bộ nhớ ở đâu đó phía sau. Chỉ nên coi WAF là lớp giảm nhẹ, mà một vài hãng có signature riêng (Fortinet có hai signature IPS trong database 37.317), không phải là bản vá.

# ai bị ảnh hưởng, bản nào đã vá

Theo advisory, mọi cài đặt PHP phục vụ web request đều nằm trong diện rủi ro: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed. Bản PHP bị ảnh hưởng là các bản trước những bản vá dưới đây.

nhánhbản đã vághi chú
8.58.5.12
8.48.4.27Windows có bản rebuild 8.4.27-1
8.38.3.36
8.28.2.35PHP ghi rõ 8.2 hết hạn hỗ trợ ngày 31/12/2026
8.1, 8.0, 7.4không có bản chính thứcPHP đưa backport tối thiểu dạng branch trên GitHub, hoặc lấy bản backport từ distro/LTS vendor

Còn nhánh nào cũ hơn nữa thì sao? Một số nguồn thứ cấp nói lỗi có từ rất lâu, nhưng mình chưa thấy advisory chính thức nào xác nhận phạm vi phiên bản cũ ngoài những nhánh PHP có đưa backport, nên mình không khẳng định. Bitsight có nhận xét đáng suy nghĩ: trong dữ liệu quét của họ, PHP 7.4 là nhánh phổ biến nhất (hơn 2 triệu quan sát), và chỉ khoảng một phần ba số instance nhận diện được nằm trong nhánh 8.2 tới 8.5. Nghĩa là phần lớn máy PHP ngoài kia cần backport của distro, hoặc cần nâng cấp lớn.

Điểm hay bị hiểu nhầm: bản distro giữ số version cũ. Ví dụ theo trang tracker của Ubuntu, Ubuntu 24.04 đã vá ở gói 8.3.6-0ubuntu0.24.04.13, và php -v vẫn báo 8.3.6, thấp hơn 8.3.36 nhưng đã chứa bản sửa. Ubuntu 22.04 vá ở 8.1.2-1ubuntu2.28. Debian vá bookworm ở 8.2.35-1~deb12u1 và trixie ở 8.4.27-1~deb13u1 (DSA-6574-1 cho trixie). Nên đừng so số version một cách máy móc, hãy đọc changelog của gói.

# kiểm tra mình có dính không

Tất cả lệnh dưới đây chỉ đọc, không đụng gì tới dịch vụ đang chạy.

Bước 1: xem bản PHP mà tiến trình đang phục vụ web dùng. Lưu ý php -v là CLI, có thể khác bản của PHP-FPM hay mod_php trên cùng máy (nhiều máy cài song song vài bản).

php -v
php-fpm -v                 # hoặc php-fpm8.3 -v, tuỳ cách cài
apache2ctl -M | grep php   # có module php nghĩa là đang chạy mod_php

Bước 2: với bản distro, đọc changelog thay vì so số.

# Debian / Ubuntu
apt changelog php8.3-fpm | grep -E 'CVE-2026-10961[89]'
 
# RHEL / Fedora
dnf updateinfo list --cve CVE-2026-109618
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]'

Dòng dnf updateinfo nằm trong gợi ý của một nguồn thứ cấp, mình chưa kiểm chứng trên máy RHEL thật. Nếu không có kết quả mà bạn vẫn nghi ngờ, so với tracker của distro: Ubuntu, Debian, Red Hat.

Bước 3: image Docker. Image build từ ba tháng trước thì PHP bên trong cũng dừng ở ba tháng trước, bất kể host đã vá hay chưa.

# kiểm tra một image cụ thể (ghi đè entrypoint để chạy thẳng php)
docker run --rm --entrypoint php my-app:latest -v
 
# liệt kê nhanh các image PHP trên máy
docker images --format '{{.Repository}}:{{.Tag}}' | grep -i php

Với image dùng bản distro (apt) thì quay lại bước 2 bên trong container. Với image PHP chính thức theo tag cố định thì so với bảng bên trên. Nhớ rằng docker pull rồi build lại mới có bản mới, container đang chạy thì phải được thay bằng container mới.

Bước 4: PHP-FPM sau CDN/WAF. Chuyện CDN che mất origin khiến nhiều người nghĩ mình an toàn, nhưng nếu request vẫn đi được tới origin thì origin vẫn là nơi PHP chạy. Cách kiểm tra ở đây không phải là scan từ ngoài vào, mà là lên máy origin xem:

# worker FPM có đang chạy bản cũ không? So thời điểm tiến trình bắt đầu với thời điểm cài gói
ps -eo pid,lstart,cmd | grep '[p]hp-fpm'
 
# cấu hình thực tế FPM đang dùng cho các giá trị liên quan
php-fpm -i | grep -E 'memory_limit|post_max_size|upload_progress|fatal_error_backtraces'

Fortinet và NCSC-NL cùng nhấn một điểm: cập nhật gói chưa đủ, phải xác nhận tiến trình đang chạy là bản mới và các worker cũ đã dừng. Nên sau khi cập nhật, restart (hoặc reload) PHP-FPM / Apache rồi kiểm tra lại thời điểm bắt đầu của tiến trình.

Một điểm nữa của kiến trúc: xem origin có bị lộ không. Nếu IP origin truy cập thẳng được từ internet, CDN/WAF phía trước gần như vô nghĩa. Cho origin chỉ nhận traffic từ dải IP của CDN, hoặc đặt sau tunnel/VPN, là việc nên làm kể cả khi không có vụ này.

Bước 5: mapping toàn bộ nơi có PHP. NCSC-NL khuyên liệt kê cả những thứ ít người nhớ: container, base image, managed hosting, appliance, control panel, sản phẩm của vendor mà bên trong có PHP. NCSC-NL còn duy trì một danh sách theo dõi trạng thái vá của nhiều sản phẩm, đáng xem nếu bạn dùng phần mềm đóng gói sẵn.

# vá và biện pháp tạm thời

Vá là cách duy nhất đầy đủ, cả hai advisory đều nói vậy. Bản vá của CVE-2026-109618 bỏ hẳn longjmp() cho trường hợp hết bộ nhớ: thay bằng một cờ OOM "mềm" xử lý ở điểm an toàn kế tiếp của VM, cộng với OOM "cứng" thoát ngay bằng _exit() ở ngưỡng cao hơn. Ý tưởng là thà giết worker sạch sẽ còn hơn nhảy ra giữa chừng rồi để dữ liệu nằm dở.

Cụ thể:

  1. Cập nhật lên 8.5.12, 8.4.27, 8.3.36 hoặc 8.2.35 (hoặc gói đã backport của distro). Với 8.1 trở xuống, hỏi distro/vendor LTS, hoặc nâng lên nhánh còn hỗ trợ.
  2. Rebuild image Docker và deploy lại, không chỉ cập nhật host.
  3. Restart toàn bộ worker, rồi kiểm tra lại bằng các lệnh ở phần trên.
  4. Sau khi vá, nếu app không dùng tính năng upload progress, giữ session.upload_progress.enabled=0 (xem phần CVE-2026-115560 bên dưới).

Nếu chưa vá được ngay, advisory liệt kê một số biện pháp. PHP nhấn mạnh chúng chỉ giảm bề mặt cho từng đường tấn công cụ thể, không làm hệ thống an toàn:

  • Với CVE-2026-109618: đặt memory_limit=-1 theo cách app không ghi đè được (ví dụ php_admin_value trong pool FPM hoặc cấu hình Apache); đặt post_max_size thấp (8M nếu app không cần upload, 32M nếu cần) và giữ upload_max_filesize nhỏ hơn; giới hạn kích thước request, số field, số part multipart, số header, độ sâu lồng ở web server hoặc proxy. Đừng dùng RLIMIT_AS hay RLIMIT_DATA để giới hạn bộ nhớ, vì chúng có thể làm chính lần cấp phát thất bại mà lỗi cần.
  • Với CVE-2026-109619: tắt session.upload_progress.enabled trong php.ini, pool FPM hoặc cấu hình server (không dựa vào ini_set() hay .user.ini) rồi restart worker. Trên PHP 8.5 thì đặt thêm fatal_error_backtraces=0. Sửa các fatal error và timeout mà người ngoài kích được. Đáng chú ý là advisory nói giấu cảnh báo bằng error_reporting, display_errors hay @ không giúp được gì.
  • Advisory cũng nhắc tới pm.max_requests=1 nếu hiệu năng chịu được. Vì lỗi 109619 cần hai request chạm cùng worker, tái sinh worker sau mỗi request cắt đúng điều kiện đó. Nhưng chi phí là mỗi request phải khởi động lại worker, nên đây là việc đánh đổi tạm thời, không phải cấu hình lâu dài.

Một chi tiết làm mình để ý: lời khuyên memory_limit=-1 nghe ngược đời, vì bình thường ai cũng muốn giới hạn bộ nhớ. Lý do là lỗi nằm ở đường xử lý OOM, nên loại bỏ giới hạn do PHP tự đặt sẽ loại bỏ một cách kích hoạt dễ dàng. Nhưng advisory cũng nói rõ: một lần cấp phát thất bại thật sự (hết RAM của máy, cgroup) vẫn đi vào đường lỗi đó. Nên PHP khuyên dùng giới hạn của container hoặc cgroup như ranh giới về tính sẵn sàng, ưu tiên chính sách chỉ giết worker bị ảnh hưởng. Đổi lại bạn đánh đổi: một request xấu có thể ngốn nhiều RAM hơn trước khi bị chặn, nên giới hạn request ở tầng proxy trở thành bắt buộc. Với team nhỏ, mình thấy bước vá lên bản mới rẻ hơn hẳn việc tinh chỉnh bộ biện pháp này cho đúng.

# CVE-2026-115560 và session upload progress

Một CVE nữa hay bị gộp chung vào câu chuyện này là CVE-2026-115560. Theo thông báo ngày 23/10 của PHP, nó là một "unlikely RCE vector" (hướng RCE ít khả năng xảy ra) và không phải một phần của hai CVE kia. Điều kiện xảy ra khá đặc biệt: app phải cấu hình session.serialize_handler là php_binary hoặc php_serialize, rồi lại đổi sang php lúc chạy bằng ini_set() trước session_start(), đồng thời session upload progress vẫn bật. PHP xử lý trong chu kỳ bản vá thường kỳ và hứa sẽ có advisory đầy đủ ở bản kế tiếp. Trong lúc đó khuyên đặt session.upload_progress.enabled=0. Nếu bạn đã làm việc này từ advisory 109619 thì không cần làm gì thêm.

Tính tới ngày mình viết (7/11), mình chưa tìm thấy advisory đầy đủ cho CVE-2026-115560, nên chi tiết kỹ thuật cụ thể chỉ có đến mức thông báo nói trên. Trang wiki của Syhunt (một nguồn thứ cấp) còn nhận định nó không phải một đường bypass bản vá của hai CVE chính. Mình không có nguồn gốc nào để đối chiếu thêm.

# dấu hiệu bị xâm nhập

Phải nói thật: nguồn gốc không công bố IoC cụ thể (hash, IP, domain). Cả NCSC-NL lẫn Fortinet đều nêu rõ không có danh sách đó. Cái duy nhất có tên gọi là hai signature IPS của Fortinet (62657 và 62658), chỉ hữu ích nếu bạn đang dùng sản phẩm của họ.

Những gì còn lại là chỉ dấu hành vi mà Fortinet liệt kê, mà thực ra là đúng với mọi vụ RCE trên web server:

  • Worker PHP bị crash hoặc restart bất thường, đặc biệt ngay sau các request multipart lạ.
  • Tiến trình con bất thường sinh ra từ worker web (shell, curl, wget...).
  • File ứng dụng bị sửa, hoặc file mới xuất hiện trong thư mục web.
  • Kết nối ra ngoài không giải thích được.

Với một lỗi mà chính cơ chế là gây crash và hỏng bộ nhớ, mình cho rằng log lỗi của PHP-FPM và log hệ thống (segfault, worker bị kill) là nơi đáng xem đầu tiên, nhưng đây là suy luận của mình từ cơ chế, không phải IoC được nguồn công bố. Nếu nghi ngờ đã bị xâm nhập, Fortinet khuyên giữ lại chứng cứ, rà persistence và các credential có thể đã lộ. Vá lỗi sau khi bị vào thì không đuổi được kẻ đã vào.

# về bản 8.4.27 "rebuild"

Brief của mình có nhắc 8.4.27 bị rebuild ngày 22/10. Mình kiểm tra ở nguồn gốc và thấy tình hình hơi khác:

  • Trang tin php.net ghi ngày 26/10: PHP đăng "Updated PHP Windows builds for Brotli security fix", gồm các bản 8.5.12-1, 8.4.27-1, 8.3.36-1, 8.2.35-1, kèm Brotli 1.2.1 sửa CVE-2026-111497 (ghi out-of-bounds trong giải nén Brotli). Hậu tố -1 đánh dấu bản rebuild Windows với dependency cập nhật, phiên bản runtime PHP không đổi. Đây là lỗi khác, không liên quan hai CVE ở trên, và chỉ chạm được qua extension cURL khi bật tự động giải nén response HTTP (mặc định tắt).
  • Có một điểm lệch nhỏ trong chính php.net: trang ChangeLog ghi ngày 22/10 cho 8.5.12 và 8.4.27, trong khi các mục tin tức ghi 21/10. Mình không giải thích được lệch này và không có nguồn gốc nào nói đó là rebuild.

Nên nếu bạn chạy PHP trên Windows, hãy lấy bản -1. Còn trên Linux, đây không phải việc của bạn.

# bài học cho team vận hành

Vài điều mình rút ra, đúng theo nghĩa quan điểm cá nhân.

Tồn kho PHP là việc phải có từ trước. Câu hỏi "mình đang chạy bản nào" mà mất nửa ngày mới trả lời được thì vụ việc này mới chỉ là lời nhắc. Một file liệt kê dịch vụ, image, base image và bản PHP của từng cái, cập nhật tự động trong CI, đáng giá hơn mọi cuộc họp khẩn.

Image Docker là nơi vá chậm nhất. Host vá xong trong 10 phút, nhưng image nằm trong registry, trong cache CI, trong đám container chạy lâu không ai deploy lại thì còn lại bản cũ. Pin theo digest thì an toàn về tái lập, nhưng cũng nghĩa là không ai tự cập nhật cho bạn. Hãy có job build lại định kỳ và cảnh báo khi base image quá cũ.

Đừng coi WAF là bản vá. Mình không có ý chê WAF, nó tốt cho chuyện chặn quét rác. Nhưng lỗi nằm ở tầng parse request của PHP, trước code của bạn, và nguồn gốc nói thẳng không có cấu hình chung nào thay được việc vá. Origin phải vá thật.

PHP cũ là món nợ có lãi. 8.1 trở xuống đã hết hỗ trợ, 8.2 hết hạn 31/12/2026. Lần này PHP còn tốt bụng đưa backport tối thiểu, nhưng lần sau chưa chắc. Nếu bạn đang dựa vào backport của distro (Ubuntu Pro, Freexian ELTS...), đó là một lựa chọn kinh doanh hợp lệ, miễn là bạn biết mình đang trả tiền để trì hoãn việc nâng cấp.

Thiết kế chịu lỗi cho worker. Bản vá thay longjmp bằng _exit: thà mất một worker còn hơn tiếp tục chạy với bộ nhớ không rõ. Triết lý này áp dụng được cho cả hệ thống của bạn: có giới hạn cgroup, có health check, có tự khởi động lại, để một worker chết không kéo theo cả dịch vụ.

Nếu chỉ có thời gian làm ba việc hôm nay: liệt kê nơi nào đang chạy PHP, xác nhận bản đang chạy (đọc changelog nếu là gói distro), rồi vá và restart.

Tài liệu tham khảo:


Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!

Happy coding 😎 👍🏻 🚀 🔥.