Debian 13 的 PHP 從 sury 換回官方源:套上 php2shell 修補的步驟,附七台實戰紀錄

PHP 官方在台北時間 10 月 21 日晚上發布 8.5.12、8.4.27、8.3.36、8.2.35,修補 php2shell 兩個可以未登入遠端執行程式碼的漏洞;Debian 13 的 trixie-security 同一晚就有 8.4.27-1~deb13u1。我們七台 PHP 主機同一晚全部升到這一版,四台本來就用 Debian 官方源,三台從 sury 換回官方源。這篇照實際做過的順序寫:怎麼確認受影響、怎麼看官方源有沒有修補、怎麼換源、8.2/8.3 升 8.4 要注意什麼、升完怎麼驗證、怎麼回滾,還有我們踩到的坑:mod_php 升級後只 reload 不會換上新版。

10 月 21 日下午,我們寫了 php2shell 漏洞的處置說明,那時官方修補還沒出來。當天晚上修補出來,我們維運的七台 PHP 主機同一晚全部升級完成。這篇接著寫:修補怎麼裝。

七台都是 Debian 13。其中四台本來就用 Debian 官方源,直接升級;另外三台用的是第三方的 sury 套件庫,這次趁修補一起換回 Debian 官方源,順便把 PHP 8.2、8.3 升到 8.4。下面照實際做過的順序寫,指令都可以照抄。

先講結論:

  1. 官方修補版本是 PHP 8.5.12、8.4.27、8.3.36、8.2.35(PHP 官方安全公告),低於這些版本的都要升
  2. Debian 13 官方源的修補版本是 8.4.27-1~deb13u1,從 trixie-security 來(Debian 套件追蹤 php8.4)
  3. 用 Apache mod_php 的,升級後一定要 systemctl restart apache2,不能只 reload。我們實測只 reload,網站跑的還是舊版
  4. 驗證要看網站實際在跑的版本,不是 php -v

我的主機受不受影響?

PHP 官方公告寫得很直接:所有處理網頁請求的 PHP 都在風險範圍,包括 PHP-FPM、Apache 的 mod_php、php-cgi、FrankenPHP 與 LiteSpeed;風險最高的是 mod_php,已經有人示範,未登入的請求在網站程式碼執行之前就能觸發攻擊。

先看三件事:

php -v
dpkg -l | grep -E '^ii +(php[0-9.]+-(common|cli|fpm|cgi)|libapache2-mod-php)'
grep -rl 'packages.sury.org' /etc/apt/sources.list /etc/apt/sources.list.d/ 2>/dev/null
  • 第一行看版本。低於 8.5.12、8.4.27、8.3.36、8.2.35 的,都要升。8.1 以前的版本 PHP 官方已經不再更新
  • 第二行看裝了哪些 PHP 套件、網頁用的是哪一種(fpm 是 PHP-FPM,libapache2-mod-php 是 mod_php)。版本號結尾是 ~deb13u1 的是 Debian 官方源,帶 +0~ 加日期的是 sury
  • 第三行有印出檔名,就是這台有用 sury 套件庫

官方源有沒有修補?

apt-get update
apt-cache policy php8.4-common

「Candidate(候選)」那行出現 8.4.27-1~deb13u1、來源是 trixie-security,就是官方修補已經可以裝。如果候選版本還是舊的,先確認 apt 來源有沒有 trixie-security。Debian 13 新裝的主機通常在 /etc/apt/sources.list.d/debian.sources,要有這一段:

Types: deb
URIs: http://security.debian.org/debian-security
Suites: trixie-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

我們每 15 分鐘在一台 Debian 官方源主機和三台 sury 主機各跑一次 apt-get update,比對已裝與候選版本。21:56 兩邊同時出現修補:Debian 是 8.4.27-1~deb13u1,sury 是 8.4.27、8.3.36、8.2.35。

要確認這一版真的修了 php2shell,裝好後看套件附的 changelog:

zcat /usr/share/doc/php8.4-common/changelog.Debian.gz | head -20

Debian 8.4.27-1~deb13u1 的 changelog 寫明修了 CVE-2026-109618 與 CVE-2026-109619。

本來就用 Debian 官方源:直接升級

先模擬,確認只會動到 PHP 套件、沒有要移除東西(模擬結果裡沒有 Remv):

apt-get update
PKGS=$(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php*' 'libapache2-mod-php*' 2>/dev/null | awk '$1=="ii"{print $2}')
apt-get -s install --only-upgrade $PKGS

模擬結果是 0 個的話,多半是沒先 apt-get update。確認沒問題再真的跑:

DEBIAN_FRONTEND=noninteractive apt-get install -y --only-upgrade \
  -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef $PKGS

--force-confold 是遇到設定檔有改過時保留你原本的版本,不會卡在互動問答。我們四台都是 8.4.26-1~deb13u1 升到 8.4.27-1~deb13u1,不用重開機,也沒有設定檔衝突。

我們踩到的坑:mod_php 只 reload 不會換上新版

套件裝完,要讓網頁伺服器換上新的 PHP:

  • Apache mod_php:apachectl configtest && systemctl restart apache2
  • PHP-FPM:php-fpm8.4 -t && systemctl reload php8.4-fpm

重點在 Apache 那行是 restart,不是 reload。我們第一次升完只做了 systemctl reload apache2,以為就好了,實測網站跑的還是 8.4.26。原因是 reload 是「平順重載」,Apache 主行程沒有重新啟動,舊的 PHP 模組還留在記憶體裡。查得到:

grep 'libphp.*(deleted)' /proc/$(cat /run/apache2/apache2.pid)/maps

有印出東西,就是已經被新版覆蓋掉的舊模組還在用。改成 systemctl restart apache2 完整重啟,每台停約 1 到 2 秒,之後網站實際跑的才是 8.4.27。

以我們自己這個官網為例:21:59 套件裝完、reload,22:04 才完整重啟,中間大約 5 分鐘,網站跑的其實還是有漏洞的舊版。PHP-FPM 不受這個影響,FPM 的 reload 會重新執行程式。

用 sury 的主機:要不要換回官方源?

先說清楚:sury 這次也出了修補,留在 sury、照常升級也能補上。要不要換回官方源,看你需要什麼:

留在 sury換回 Debian 官方源
能用的 PHP 版本8.2、8.3、8.4、8.5 都有Debian 13 只有 8.4
誰發安全更新sury 維護者Debian 安全團隊,跟著 Debian 13 的支援期
擴充套件版本通常比較新照 Debian 13 釋出時的版本,只補安全修正
要做的事照常升級停用 sury、換成官方版本,8.2/8.3 要升 8.4

我們為什麼換回官方源?sury 這次也修得很快,問題不在快慢,而在一致:我們七台原本有 8.2、8.3、8.4 三種版本、兩種來源,等於三條修補進度要各自追,漏掉一台就是破口。所以這次把七台統一成 Debian 13 內建的 PHP 8.4,來源統一走 Debian 官方 trixie-security。以後任何一台有修補,七台同一輪一起升級、一起核對版本。

sury 只留作緊急備援:Debian 修補還沒出、又非修不可時,才臨時用,而且只鎖定 PHP 套件,Debian 修補出來後就換回。這是我們的做法,不是說 sury 不好。

sury 換回官方源:指令

第 0 步:先做主機快照。能做快照就先做,這是最快的回滾方式。

第 1 步:停用 sury,但不刪。sury 的來源檔改名讓 apt 不讀它,再加一個 pin,讓 apt 就算之後又讀到 sury,也不會從那裡裝東西。檔名常見是 php.list 或 php-sury.list,以前面 grep 找到的為準:

mv /etc/apt/sources.list.d/php-sury.list /etc/apt/sources.list.d/php-sury.list.disabled
cat > /etc/apt/preferences.d/sury-emergency-only <<'EOF'
Package: *
Pin: origin packages.sury.org
Pin-Priority: -1
EOF
apt-get update

第 2 步:列出要換的套件和 Debian 版本號。這一步最容易卡住:sury 的版本號排序比 Debian 大,同樣是 8.4.27,sury 是 8.4.27-1+0~20261021…,Debian 是 8.4.27-1~deb13u1,apt 會認為已經裝的 sury 版比較新,不會自己換。所以要逐一指定「套件=Debian 版本號」,而且每個套件的 Debian 版本號不一定一樣,不能全部套同一個:

for p in $(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4-*' 'libapache2-mod-php8.4' 2>/dev/null | awk '$1=="ii"{print $2}'); do
  v=$(apt-cache madison "$p" | grep -v sury | head -1 | awk -F'|' '{gsub(/ /,"",$2); print $2}')
  [ -n "$v" ] && echo "$p=$v"
done > /root/php-debian-list.txt

第 3 步:先模擬,看有沒有要移除的東西:

apt-get -s install --allow-downgrades $(cat /root/php-debian-list.txt) | grep -c '^Remv'

這個數字要是 0。我們有一台一開始把 php-common 也放進清單,模擬出現 21 個 Remv:sury 的 php-common 是 2:101,Debian 的是 2:96,把它降回去,會連帶拆掉那台上面沒在用、但還裝著的 7.4、8.2、8.5 舊套件。我們照規則停手,改成只換 php8.4-* 和 libapache2-mod-php8.4,Remv 變成 0。php-common 和 libgd3 先留 sury 版,等之後清舊版本時再一起換。

第 4 步:真的換,然後完整重啟:

DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-downgrades \
  -o Dpkg::Options::=--force-confold -o Dpkg::Options::=--force-confdef \
  $(cat /root/php-debian-list.txt)
apachectl configtest && systemctl restart apache2

那台是 WordPress 官網,換之前先記下圖片處理函式庫 GD 支援的格式(WebP、AVIF、JPEG、PNG),換完再看一次,四項都在。

8.2/8.3 升 8.4 要注意什麼?

Debian 13 官方只有 PHP 8.4,所以原本跑 8.2 或 8.3 的,換回官方源就等於大版本升級。我們有兩台:Nextcloud(8.3)和一台自己寫的 PHP 商店(8.2)。換源的指令跟上一節一樣,另外多做這幾件事:

1. 先查你的程式支不支援 8.4

  • WordPress:官方相容表寫 6.7 起支援 PHP 8.4;外掛與主題要另外看,舊外掛最常出問題
  • Nextcloud:官方系統需求頁,31 版起列出 PHP 8.4,30 版以前沒有。我們那台是 34 版
  • 自己寫的程式:先用 8.4 把所有 PHP 檔跑一次語法檢查,跟舊版比。我們的商店掃了 6,346 個檔案,只比 8.2 多 1 個錯誤,而且在一個早就壞掉、沒在用的舊函式庫裡。要注意語法檢查只抓得到語法錯誤,抓不到執行時的問題;PHP 8.4 也把「預設值是 null 就自動變成可為 null」的參數寫法列為棄用,舊程式的記錄檔會多出很多 Deprecated 訊息
find /var/www/你的程式 -name '*.php' -print0 | xargs -0 -n1 php8.4 -l 2>&1 | grep -v '^No syntax errors'

2. 擴充套件要補裝 8.4 版。舊版有、8.4 還沒裝的擴充,要一起放進換源的清單。列出差異:

comm -23 \
  <(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.3-*' | awk '$1=="ii"{sub("php8.3-","",$2); print $2}' | sort) \
  <(dpkg-query -W -f='${db:Status-Abbrev} ${Package}\n' 'php8.4-*' | awk '$1=="ii"{sub("php8.4-","",$2); print $2}' | sort)

我們 Nextcloud 那台補了 apcu、igbinary、imagick、redis、sqlite3。Nextcloud 的本機快取用 APCu,少裝 apcu 會起不來。商店那台補了 bz2、curl、intl、ldap、pgsql、soap。

3. 設定不會自動搬過去,只搬差異。不要整份 php.ini 複製過去。比對兩版實際生效的設定值,只把差異寫成 8.4 設定夾裡的一個覆蓋檔,原本的 php.ini 不動:

PHP_INI_SCAN_DIR=/etc/php/8.3/apache2/conf.d php8.3 -c /etc/php/8.3/apache2/php.ini -i > /root/ini-8.3.txt
PHP_INI_SCAN_DIR=/etc/php/8.4/apache2/conf.d php8.4 -c /etc/php/8.4/apache2/php.ini -i > /root/ini-8.4.txt
diff /root/ini-8.3.txt /root/ini-8.4.txt

PHP_INI_SCAN_DIR 一定要加,不然讀到的是命令列那份設定夾,看不到網頁伺服器自己的設定。我們 Nextcloud 搬了 memory_limit 512M 和 opcache 的設定;商店只搬了 upload_max_filesize 10M、post_max_size 20M。

搬設定時順便檢查舊設定對不對。商店那台 8.2 原本開著 display_errors=On,會把錯誤訊息直接顯示給客人看,這個我們沒有搬,8.4 用預設的關閉。另外,修補前加的臨時緩解設定(下面「現在還不能升級怎麼辦」那節)也要記得在 8.4 的設定夾放一份。

4. 網頁伺服器和命令列都要切到 8.4:

a2dismod php8.3
a2enmod php8.4
update-alternatives --set php /usr/bin/php8.4
update-alternatives --set phar /usr/bin/phar8.4
update-alternatives --set phar.phar /usr/bin/phar.phar8.4
apachectl configtest && systemctl restart apache2

update-alternatives 這三行很容易漏。排程(cron)呼叫的是 /usr/bin/php,不切的話,網站跑 8.4,排程還在跑 8.3。用 PHP-FPM 的,socket 路徑會從 /run/php/php8.3-fpm.sock 變成 /run/php/php8.4-fpm.sock,nginx 或 Apache 指到舊路徑的設定要一起改。

5. Nextcloud 要先開維護模式再拍快照。順序是:開維護模式 → 拍快照 → 換源升級 → occ status 確認正常 → 關維護模式 → 有用 notify_push 的重啟它 → 手動跑一次 cron.php → occ setupchecks 看 PHP 版本、記憶體、APCu 快取都正常。先開維護模式再拍快照,萬一要回滾,不會把使用者剛上傳的檔案一起退掉。我們那台維護模式開了大約 1 分 30 秒。

升完怎麼驗證?

驗證要分「裝了什麼」和「網站實際在跑什麼」兩層,只看第一層不夠:

1. 套件版本與來源:

dpkg -l | grep -E '^ii +(php8.4-common|libapache2-mod-php8.4|php8.4-fpm)'
apt-cache policy php8.4-common

版本要是 8.4.27-1~deb13u1,來源是 trixie-security。

2. 網站實際跑的 PHP:命令列的 php -v 不等於網站在跑的版本。在網站根目錄放一個隨機檔名的暫存檔,用瀏覽器打開看一次,看完立刻刪掉:

<?php echo php_sapi_name(), '|', PHP_VERSION, '|', ini_get('session.upload_progress.enabled');

mod_php 會印 apache2handler|8.4.27|…,PHP-FPM 會印 fpm-fcgi|8.4.27|…。最後一欄是臨時緩解有沒有生效,關掉時會印空白或 0。

3. 舊模組沒有留在記憶體:用 mod_php 的跑一次前面那行 grep 'libphp.*(deleted)' …,沒有印出東西才對。

4. 網站功能:首頁、後台登入、上傳一張圖、送一次表單(表單主旨寫「測試請忽略」,收到就刪)。有排程的,手動跑一次。

出事怎麼回滾?

  • 最快的回滾是快照還原。換源前有做主機快照,出事直接還原,再回頭找原因
  • 換源後要回 sury:把 sury-emergency-only 那個 pin 檔移走、來源檔改回原本的名字,apt-get update 後再裝回 sury 版本
  • 8.2/8.3 升 8.4 後要退回:我們這次沒有移除舊版的 8.2、8.3 套件,設定也還在。真要退回,a2dismod php8.4 && a2enmod php8.3、update-alternatives 切回 8.3、restart apache2 就接得回去。舊版套件我們預計留兩週,確定沒問題再清

七台這次都沒有用到回滾。

現在還不能升級怎麼辦?

PHP 官方公告寫得很清楚:只有升級才是完整解法,下面的做法只能減少特定攻擊路徑,不會讓主機變安全。官方列的臨時措施:

  • CVE-2026-109619:設 session.upload_progress.enabled=0,每一個 worker 都要重啟。要寫在 php.ini、FPM pool 或伺服器設定這種在請求解析前就生效的地方,不能靠 ini_set() 或 FPM 的 .user.ini
  • CVE-2026-109618:設 memory_limit=-1,最好用應用程式改不掉的方式(FPM pool 或 Apache 設定裡的 php_admin_value),並把 post_max_size 調低:不需要上傳檔案的網站設 8M,需要的設 32M

我們修補出來前的做法,是在每個 PHP 版本的設定夾放一個獨立檔案,不改原本的 php.ini,要回復時刪掉這個檔就好:

# /etc/php/8.4/apache2/conf.d/99-php2shell-mitigation.ini
session.upload_progress.enabled = Off

升級完我們也沒有拿掉它,多一層防護,對網站沒有影響。

我們七台的實戰紀錄

主機用途原本換完怎麼做狀況
WordPress 官網(三台)Debian 官方 8.4.26Debian 官方 8.4.27-1~deb13u1直接升級只 reload 沒換上新版,改 restart apache2,每台停約 1 到 2 秒
客服信件系統(PHP-FPM)Debian 官方 8.4.26Debian 官方 8.4.27-1~deb13u1直接升級FPM reload 就換上,沒有狀況
WordPress 官網(一台)sury 8.4.26Debian 官方 8.4.27-1~deb13u1同版本換源降 php-common 會連帶移除 21 個舊套件,改成只換 php8.4 套件
Nextcloud 34sury 8.3.35Debian 官方 8.4.27-1~deb13u1換源+升 8.4補裝 5 個擴充;維護模式約 1 分 30 秒;記錄檔沒有新錯誤
自寫 PHP 商店sury 8.2.34Debian 官方 8.4.27-1~deb13u1換源+升 8.46,346 個檔案語法檢查;補裝 6 個擴充;不沿用 display_errors=On

做完之後:

  • 七台都是 Debian 官方源的 PHP 8.4.27-1~deb13u1,sury 停用,只留作緊急備援
  • 對外的停機只有兩種:Apache 完整重啟,每台約 1 到 2 秒;Nextcloud 維護模式,約 1 分 30 秒
  • 修補前加的臨時緩解設定、只記錄上傳類請求的偵測紀錄都留著,之後查異常上傳用得到
  • 舊版 PHP 套件先留兩週當回滾,之後再清

需要人幫忙看自己的主機、或排升級,可以跟我們聯絡。

常見問題

Debian 13 的 php2shell 修補版本是哪一版?

官方源是 8.4.27-1~deb13u1,從 trixie-security 安全更新來,changelog 寫明修了 CVE-2026-109618 與 CVE-2026-109619。

升級 PHP 之後,Apache 要 reload 還是 restart?

用 mod_php 的要 restart。我們實測只 reload,舊的 PHP 模組還留在記憶體,網站跑的還是舊版;完整 restart 每台只停約 1 到 2 秒。用 PHP-FPM 的,reload PHP-FPM 就會換上新版。

用 sury 的主機一定要換回官方源嗎?

不一定。sury 這次也出了 8.4.27、8.3.36、8.2.35,留在 sury、照常升級也能補上。我們換回官方源是為了七台版本和來源一致、同一輪升級;代價是 Debian 13 官方只有 PHP 8.4。

停用 sury 之後,為什麼 apt 沒有換回官方版本?

因為 sury 的版本號排序比 Debian 官方大,apt 會認為已經裝的是比較新的版本。要用 apt-cache madison 查出每個套件的 Debian 版本號,逐一指定「套件=版本號」,加 –allow-downgrades 安裝。先模擬,確認沒有要移除的套件。

升級完 php -v 顯示 8.4.27,就代表網站安全了嗎?

不一定。php -v 是命令列的 PHP,網站用的是 Apache 模組或 PHP-FPM。要重啟服務,再用暫存檔確認網站實際跑的版本,才算數。

WordPress 可以直接升 PHP 8.4 嗎?

WordPress 官方相容表寫 6.7 起支援 PHP 8.4。核心沒問題,要注意的是外掛與主題,升級前先做快照或備份。

參考來源

最後更新: