Vá lỗ hổng PHP2Shell (CVE-2026-109618, CVE-2026-109619) trên Ubuntu cho PHP 5.4 – 8.1: hướng dẫn thực tế

Lỗ hổng PHP2Shell CVE-2026-109618 và CVE-2026-109619 - khẩn cấp cài bản vá PHP

Ngày 21/10/2026, PHP công bố hai lỗ hổng nghiêm trọng CVE-2026-109618 và CVE-2026-109619, được giới bảo mật gọi chung là PHP2Shell. Lỗ hổng có điểm CVSS 9.2, cho phép thực thi mã từ xa mà không cần đăng nhập, và đã bị khai thác hàng loạt ngay sau khi công bố (theo Amazon, lượt thử đầu tiên ghi nhận từ 17/10).

Bài viết này tổng hợp cách Nam IT đã xử lý thực tế trên nhiều máy chủ Ubuntu (14.04, 16.04, 18.04, 22.04) chạy PHP từ 5.4 đến 8.1, gồm cả các server i-MSCP/ISPCP chạy nhiều website, các hệ thống ERP và Nextcloud. Mục tiêu: vá được ngay khi có thể, và giảm thiểu tối đa rủi ro khi PHP đã hết hỗ trợ, không còn bản vá chính thức.

1. Lỗ hổng PHP2Shell là gì?

  • Vị trí lỗi: nằm trong lõi PHP (bộ phân tích request multipart/form-data, phần upload progress của session và cơ chế dọn trạng thái sau lỗi “bailout”), không phụ thuộc framework hay mã nguồn ứng dụng.
  • Phạm vi: mọi cách chạy PHP đều bị ảnh hưởng: Apache mod_php (rủi ro cao nhất), PHP-FPM, php-cgi, LiteSpeed, FrankenPHP. Các bản từ PHP 5.4 trở lên đều dính.
  • Cách tấn công: chỉ cần gửi một request POST multipart/form-data tới bất kỳ file .php nào. PHP xử lý phần body này trước khi code ứng dụng chạy, nên đăng nhập hay phân quyền của ứng dụng không chặn được.
  • Bản vá chính thức: PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12. Từ 8.1 trở xuống đã hết hỗ trợ: không có bản phát hành chính thức, chỉ có backport từ các nhà phân phối.

2. Xác định máy chủ của bạn thuộc trường hợp nào

Chạy các lệnh sau để biết phiên bản PHP, nguồn gói (Ubuntu gốc hay PPA ondrej/sury) và cách PHP được chạy:

lsb_release -ds
php -v
dpkg -l | grep -E '^ii +(php|libapache2-mod-php)' | awk '{print $2, $3}'
grep -rh 'ondrej|sury' /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null
apache2ctl -M 2>/dev/null | grep -iE 'php|fcgi|proxy_fcgi'
ps -eo cmd | grep -E 'php-fpm: master|php-cgi' | grep -v grep
Trường hợp Cách xử lý
Ubuntu 22.04 / 24.04, PHP từ PPA ppa:ondrej/php (kể cả 5.6, 7.x, 8.0, 8.1) Cập nhật gói (mục 3). Ondřej Surý đã backport bản vá cho mọi nhánh 5.6 → 8.1.
Ubuntu 22.04 / 24.04, PHP gốc của Ubuntu Cập nhật gói theo USN-8948: php8.1 8.1.2-1ubuntu2.28 (22.04), php8.3 8.3.6-0ubuntu0.24.04.13 (24.04).
Ubuntu 14.04 / 16.04 / 18.04 với PHP 5.4 – 7.x Không có gói vá. PPA không còn build cho các bản Ubuntu này. Áp dụng giảm thiểu: mục 4 (bắt buộc) + mục 5 (khuyến nghị) + mục 6 (tùy chọn).

3. Ubuntu 22.04/24.04: cập nhật gói đã vá

Lưu ý quan trọng: bản backport giữ nguyên số phiên bản upstream (ví dụ vẫn là 8.1.34), nên phải kiểm tra changelog của gói, đừng chỉ nhìn số phiên bản. Nếu apt update báo lỗi PPA “changed its Label”, thêm --allow-releaseinfo-change.

apt-get update --allow-releaseinfo-change
apt-cache policy php8.1-common

# Kiểm tra gói ứng viên đã có bản vá chưa (phải ra số > 0)
cd /tmp && apt-get download php8.1-common
dpkg-deb --fsys-tarfile php8.1-common_*.deb | tar -xO --wildcards './usr/share/doc/*/changelog.Debian.gz' 
  | zcat | grep -c 'CVE-2026-109618'

# Cập nhật đúng nhánh đang dùng, giữ nguyên file cấu hình cũ
apt-get install --only-upgrade -o Dpkg::Options::=--force-confold 
  $(dpkg -l | awk '/^ii +(php8.1-|libapache2-mod-php8.1)/{print $2}')

Sau khi cập nhật bắt buộc khởi động lại mọi tiến trình đang chạy PHP:

  • Apache mod_php: systemctl restart apache2. graceful/reload là không đủ, Apache vẫn chạy thư viện libphp cũ.
  • PHP-FPM: systemctl restart php8.1-fpm
  • Queue worker, cron chạy lâu: khởi động lại.

Kiểm tra không còn tiến trình nào giữ thư viện PHP cũ:

grep -l 'libphp.*(deleted)|php.*(deleted)' /proc/*/maps 2>/dev/null | wc -l   # phải = 0

4. Server PHP cũ (5.4 – 7.x): tắt session.upload_progress (bắt buộc, làm ngay)

Đây là khuyến nghị của nhóm bảo mật PHP, giúp chặn một trong các đường khai thác. Hầu hết ứng dụng không dùng tính năng thanh tiến trình upload nên tắt đi không ảnh hưởng.

B=/root/php2shell-$(date +%Y%m%d); mkdir -p $B
INIS=$(ls /etc/php5/*/php.ini /etc/php/*/*/php.ini /usr/local/etc/imscp_panel/php.ini 2>/dev/null)
POOLS=$(ls /etc/php5/fpm/pool.d/*.conf /etc/php/*/fpm/pool.d/*.conf 2>/dev/null)
tar -czf $B/backup.tgz $INIS $POOLS

for f in $INIS; do
  if grep -qE '^s*;?s*session.upload_progress.enabled' $f; then
    sed -i -E 's/^s*;?s*session.upload_progress.enableds*=.*/session.upload_progress.enabled = Off/' $f
  else
    sed -i '/^[Session]/a session.upload_progress.enabled = Off' $f
  fi
done
# Khóa ở từng pool FPM để website không tự bật lại
for f in $POOLS; do
  grep -q upload_progress $f || echo 'php_admin_flag[session.upload_progress.enabled] = off' >> $f
done

service php5.6-fpm reload      # hoặc php5-fpm / php7.1-fpm ... tùy server
php-fpm5.6 -i | grep session.upload_progress.enabled   # phải là Off
  • PHP 5.3 trở về trước không có tính năng này và nằm ngoài vùng ảnh hưởng của PHP2Shell (lỗi xuất hiện từ 5.4).
  • Với i-MSCP: lệnh imscp-reconfigure sẽ tạo lại file pool, nhưng thiết lập trong php.ini gốc vẫn còn.
  • Bước này chỉ chặn được một phần. Lỗi hỏng trạng thái khi hết bộ nhớ (bailout) vẫn còn, nên cần thêm mục 5.

5. Server PHP cũ: cài extension hp_uploadprogress_guard (khuyến nghị)

hp_uploadprogress_guard là extension mã nguồn mở (giấy phép MIT) của Hosted Power, hỗ trợ PHP 5.4 – 8.5. Ở chế độ mặc định mode = parse, nó:

  • Thay bộ phân tích multipart của PHP bằng bản sao đã vá, bỏ hẳn upload progress.
  • Thoát an toàn (_exit 137) khi hết bộ nhớ lúc PHP đang đọc POST, chặn hướng khai thác CVE-2026-109618.
  • Dọn trạng thái cũ cuối mỗi request, chặn hướng khai thác CVE-2026-109619.
  • Cách ly unserialize(), có thêm bản sửa lỗi GC cho SimpleXML.
  • Tự đứng ngoài nếu phát hiện PHP đã có bản vá chính thức.

Khuyến nghị đọc mã nguồn và tự build thay vì dùng file .so dựng sẵn.

5.1. Chuẩn bị header PHP (phpize)

Nếu có gói dev thì cài (php5.6-dev, php7.1-dev, php5-dev…). Trên Ubuntu 14.04/16.04/18.04 PPA thường đã gỡ các gói này. Khi đó tự dựng bộ header từ mã nguồn php.net. Các bản cùng nhánh (5.6.x, 7.1.x…) dùng chung ABI extension, và cách này không đụng tới PHP đang chạy.

apt-get install -y autoconf m4 gcc make
V=5.6.40     # 7.1.33 cho nhánh 7.1, 7.0.33, 7.2.34, 7.3.33, 7.4.33 ...
cd /root && wget https://www.php.net/distributions/php-$V.tar.gz
sha256sum php-$V.tar.gz    # so khớp với https://www.php.net/releases/?json&version=$V
tar xzf php-$V.tar.gz && cd php-$V
./configure --prefix=/opt/php-hdr --disable-all --enable-session --disable-cgi --without-pear
make -j2 && make install-headers install-build install-programs

5.2. Build extension

git clone --depth 1 https://github.com/HOSTED-POWER/php_uploadprogress_guard /root/hpg
cd /root/hpg/src
export PATH=/opt/php-hdr/bin:$PATH        # bỏ dòng này nếu dùng phpize từ gói php-dev
phpize && ./configure --with-php-config=$(which php-config) && make
# PHP 5.4/5.5 tự dùng bản parser của 5.6

# Kiểm tra nạp được vào PHP đang chạy trên máy
php5.6 -n -d extension=$PWD/modules/hp_uploadprogress_guard.so --ri hp_uploadprogress_guard

Kết quả cần thấy: multipart/form-data handler replaced => yes, session.upload_progress handler detached => yes, out-of-memory exit ... => active, stale request state cleared ... => active.

Build trên server có glibc cũ nhất thì file .so dùng được cho các server cùng nhánh PHP (file .so chỉ yêu cầu GLIBC_2.4).

5.3. Thử upload trước khi bật cho web

Nếu có php-cgi, chạy cùng một request multipart có đính kèm file, lần lượt không có và có extension, rồi so sánh $_POST, $_FILES và md5 của file. Hai kết quả phải giống hệt nhau:

REDIRECT_STATUS=1 SCRIPT_FILENAME=/tmp/t.php REQUEST_METHOD=POST 
CONTENT_TYPE='multipart/form-data; boundary=XyZ' CONTENT_LENGTH=$(stat -c%s body.bin) 
php-cgi5.6 -c /etc/php/5.6/fpm/php.ini -d extension=/root/hpg/src/modules/hp_uploadprogress_guard.so < body.bin

5.4. Cài đặt và kích hoạt

V=5.6
ED=$(php$V -n -r 'echo PHP_EXTENSION_DIR;')
install -m 0644 /root/hpg/src/modules/hp_uploadprogress_guard.so $ED/

cat > /etc/php/$V/mods-available/hp_uploadprogress_guard.ini <<'EOF'
; priority=30
extension=hp_uploadprogress_guard.so
hp_uploadprogress_guard.mode = parse
EOF
phpenmod -v $V -s fpm hp_uploadprogress_guard
phpenmod -v $V -s cli hp_uploadprogress_guard
# PHP 5.4/5.5 gốc Ubuntu 14.04: file ở /etc/php5/mods-available, lệnh php5enmod

php-fpm$V -t && service php$V-fpm restart     # FPM cần restart 1 lần
M=$(pgrep -f "php-fpm: master process (/etc/php/$V" | head -1)
grep -c hp_uploadprogress_guard /proc/$M/maps     # > 0 là đã nạp

Kiểm tra sau khi cài: gửi GET và POST multipart tới từng website. Mã trả về phải giống như trước khi cài:

for h in site1.vn site2.vn; do
  echo "$h GET=$(curl -s -o /dev/null -w '%{http_code}' -H "Host: $h" http://IP-SERVER/) 
  POST=$(curl -s -o /dev/null -w '%{http_code}' -H "Host: $h" -F a=1 -F f=@/etc/hostname http://IP-SERVER/)"
done

Lưu ý với i-MSCP: PHP-FPM của panel (imscp_panel) đọc chung thư mục conf.d của FPM, nên extension đã được nạp sẵn. Không thêm dòng extension= vào /usr/local/etc/imscp_panel/php.ini nữa, nếu không sẽ bị nạp 2 lần (cảnh báo “Module already loaded”). Chỉ cần service imscp_panel restart.

Extension chạy tốt cùng ionCube Loader và OPcache.

6. (Tùy chọn) Chặn multipart từ ngoài ở tầng web server

Mọi cách khai thác đã biết đều đi qua POST multipart/form-data. Nếu website không cần cho người ngoài upload, có thể chặn ngay ở Apache trước khi PHP chạy. Ví dụ dưới đây chỉ cho phép từ mạng LAN (đặt trong /etc/apache2/conf-available/, rồi a2enconf):

<If "%{REQUEST_METHOD} !~ /^(GET|HEAD|OPTIONS)$/ && %{HTTP:Content-Type} =~ /multipart/i && ! -R '192.168.0.0/16' && ! -R '10.0.0.0/8' && ! -R '127.0.0.0/8'">
    Require all denied
    ErrorDocument 403 "Forbidden"
</If>
  • Dòng ErrorDocument là bắt buộc. Với Nextcloud, WordPress… file .htaccess chuyển trang lỗi 403 về index.php, khiến PHP vẫn chạy với request bị chặn.
  • Với Nextcloud, upload file qua web/desktop/mobile dùng WebDAV PUT nên không bị ảnh hưởng. Chỉ đổi ảnh đại diện hoặc logo từ ngoài LAN là không được.

7. Kiểm tra dấu hiệu đã bị khai thác

Rà soát từ ngày 17/10/2026, không chỉ từ ngày công bố:

# Tiến trình lạ chạy dưới user web
ps -eo user,ppid,pid,etime,cmd --forest | awk '$1=="www-data"' | grep -vE 'php-fpm|apache2'

# Worker PHP bị crash / hết bộ nhớ khi khởi tạo request
journalctl --since "2026-10-17" -u 'php*-fpm' | grep -iE 'segfault|SIGSEGV|exited on signal|Allowed memory size'
grep -hE 'segfault|exited on signal|Allowed memory size' /var/log/php*-fpm.log /var/log/apache2/error.log*

# File PHP mới hoặc vừa sửa trong thư mục upload
find /var/www -newermt '2026-10-17' -type f ( -name '*.php*' -o -name '*.phtml' -o -name '.htaccess' ) | head

Nếu phát hiện webshell: cách ly file (mv sang thư mục quarantine, không xóa ngay), đổi toàn bộ mật khẩu DB/admin, và kiểm tra cron, tài khoản UID 0 lạ.

8. Gỡ bỏ khi đã nâng cấp PHP chính thức

# Extension
phpdismod -v 5.6 hp_uploadprogress_guard && service php5.6-fpm restart   # PHP 5.4: php5dismod
# Rule Apache (nếu có)
a2disconf <ten-file-rule> && systemctl restart apache2

9. Kinh nghiệm rút ra

  • Đọc changelog, không chỉ nhìn số phiên bản: bản backport giữ nguyên số phiên bản cũ. Chúng tôi đã suýt kết luận sai rằng php8.1 8.1.34-9 chưa vá, vì chỉ đọc 20 dòng đầu changelog.
  • mod_php phải restart hẳn Apache; PHP-FPM restart hoặc reload.
  • Script quản trị chạy hàng loạt nên thử đúng mật khẩu trước. Đăng nhập sai lặp lại có thể khiến fail2ban ban luôn IP NAT của chính máy chủ.
  • Giảm thiểu không thay được bản vá. Các server Ubuntu 14.04/16.04/18.04 với PHP 5.x/7.x nên được lên kế hoạch chuyển sang Ubuntu 22.04/24.04 (PPA ondrej vẫn cung cấp PHP 5.6/7.x đã vá cho các bản này) hoặc nâng cấp ứng dụng lên PHP 8.2+.

Nguồn tham khảo

Nam IT, 23/10/2026. Bài viết dựa trên quá trình xử lý thực tế. Hãy sao lưu cấu hình và thử trên một server trước khi áp dụng hàng loạt.

Cùng chuyên mục

Bình luận