資料核驗截至:2026 年 10 月 22 日 適用對象:PHP 應用維、安全回應人員,以及維護 PHP 自編譯版本的工程師。
本文整理 PHP2Shell 的漏洞成因、影響範圍、正式修復流程、臨時緩解措施與官方修補程式實現。正文使用 S1 等編號引用;全部來源連結集中列於文末附錄。配置片段是部署模板,需結合實際版本、業務容量和配置繼承關係調整。
TL;DR:修復步驟與建議
- 升級 PHP 運行時:對應分支至少升級至 8.2.35、8.3.36、8.4.27 或8.5.12;使用發行版軟體套件時,以廠商確認同時修復兩個 CVE 的完整套件版本為準。 S1S10
- 取代全部舊進程:升級後重新啟動或捲動取代所有 PHP worker、承載 PHP 的服務及容器,確認舊實例已退出。
- 從實際 Web 連結驗收:核對 Web SAPI、完整軟體包版本和運行實例;僅檢查
php -v不足以證明線上已修復。 - 做業務回歸並恢復正常預算:檢查登入、session、上傳、匯入及錯誤處理;若緊急時取消 PHP 記憶體上限,修復後恢復適當的有限預算。
- 暫時無法升級時:優先在 PHP 之前限制不可信流量;按第 5 節評估關閉 upload progress、PHP 8.5 fatal backtrace 等措施。所有配置緩解和 WAF 均有覆蓋邊界,不能取代升級。 S1S2S3
1. 漏洞概覽與風險判斷
PHP2Shell 在本文中指 2026 年 10 月 21 日 PHP 官方揭露的兩個 Critical 等級漏洞。兩者都與 PHP 引擎的記憶體和請求生命週期管理有關,存在未認證遠端程式碼執行風險。 S1S2S3
| 專案 | CVE-2026-109618 | CVE-2026-109619 |
|---|---|---|
| 官方名稱 | RCE via OOM corruption | RCE via stale global state |
| 核心問題 | 記憶體不足導致異常跳轉,留下尚未完成初始化的狀態 | worker 中殘留指向已釋放請求記憶體的全域狀態 |
| 典型發生時機 | 目前請求的初始化、執行或清理過程 | 前一請求留下狀態,後續請求提前消費 |
| OOM 是否為必要條件 | 屬於漏洞核心觸發機制 | 不是;其他致命錯誤及清理後期診斷也可能參與 |
| 根本修復方向 | 安全處理分配失敗與請求終止 | 修正狀態所有權、清理時機與提前初始化 |
| 處置要求 | 必須修復 | 必須同時修復 |
1.1 受影響部署應如何理解
PHP 官方將 所有處理 Web 請求的 PHP 部署 列入風險範圍,包括 PHP-FPM 、 Apache mod_php 、 php-cgi 、 FrankenPHP 和 LiteSpeed 。已示範的 mod_php 攻擊可以發生在應用程式碼執行之前,因此僅依賴 PHP 應用程式中的登入檢查、框架中間件或上傳校驗,並不能覆寫這種路徑。 S1
需要區分「部署需要修復」與「某一攻擊鏈已完整證明」。 CVE-2026-109618 的公告說明:FPM 在請求啟動失敗後會退出子進程,阻斷了 mod_php 對應的啟動失敗後清理路徑;但啟動成功後的 OOM 路徑仍存在風險。該公告同時註明,針對此 CVE 的完整 FPM 利用尚未展示。這個限制不能據此推導出 FPM 安全,尤其不能忽略 CVE-2026-109619。 S2S3
成功利用的直接權限邊界取決於承載 PHP 的進程身分及其可存取資源。通常應專注於網站程式碼、上傳目錄、資料庫憑證、服務令牌和內部網路存取能力;「遠端程式碼執行」不等於天然取得 root 權限。這裡的後果判斷是基於進程權限模型,實際影響需要結合部署權限評估。
一般一次性 CLI 腳本不具備相同的 HTTP 入口;但以 CLI 或嵌入方式長期處理外部任務的進程,仍應納入運行時與生命週期盤點。只更新 CMS、框架或 Composer 依賴,也不會自動取代底層 PHP 引擎。
2. 成因分析
2.1 先理解兩個生命週期
PHP worker 往往連續處理多個請求。請求期間分配的記憶體可以在請求結束時統一釋放,而 worker 本身及其部分全域欄位繼續存在。
因此,引擎必須維持兩項限制:
- 初始化約束:對其他程式碼可見的數據,必須處於可安全讀取、遍歷和銷毀的狀態。
- 生命週期約束:請求記憶體釋放後,存活更久的欄位不能繼續把其中的位址當成有效狀態。
這兩項限制分別對應兩組漏洞的主要失敗方式。以下流程是結合公告與修補程式整理的機製說明,不代表每個部署都存在完全相同的可達路徑。 S2S3S5
2.2 CVE-2026-109618:OOM 打斷了尚未完成的狀態更新
舊實作使用 longjmp() 一類非局部跳轉來處理 OOM,相關路徑透過 zend_bailout() 退出目前執行上下文。危險在於,一段 C 程式碼可能已經將新物件、陣列或請求狀態發佈給其他程式碼,但尚未完成全部欄位初始化;此時任意一次相關時機上的分配失敗,都可能使正常的收尾步驟被跳過。 S2
可以把失敗過程拆成四個步驟:
- 建立或更新請求內部結構,並使其可存取。
- 後續記憶體分配失敗,觸發 OOM。
- 執行流跳離目前函數,原本用於完成初始化或恢復狀態的程式碼沒有執行。
- 請求清理繼續處理這些結構,將無效內容當作有效欄位或位址。
這裡的關鍵不是“內存用完就一定產生 RCE”,而是內存不足改變了控制流,破壞了引擎對結構有效性的假設。失敗的分配也不必與最終被錯誤讀取的結構直接相關。 S2
為什麼較小的請求也可能耗盡內存
請求體位元組數與解析後的堆佔用不是一回事。一個欄位在 PHP 內部可能對應多個字串、陣列元素和元資料物件;輸入的結構複雜度會影響記憶體放大程度。
官方公告列出的 multipart 問題包括:缺少可用聲明長度時的初始大小檢查缺口、單個 part 的頭部數量未受到充分限制,以及空文件控制項仍產生元資料、嵌套名稱進一步增加數組工作量。應用自身對輸入的展開、解碼或重複分配,也可能成為 OOM 來源。 S2
由此可得兩條防護結論:
- 請求體限制必須盡量在進入 PHP 前執行,並考慮實際讀取長度與結構複雜度。
- 單獨限製檔案大小、檔案數量,或只關閉一個上傳入口,無法覆蓋所有 OOM 來源。
為什麼應用鑑權可能來不及
在 mod_php 中,請求初始化階段就可能自動解析請求體;即使啟動失敗,相關請求清理仍會執行。已確認攻擊路徑因此可以先於應用程式碼發生。若要在這個階段減少暴露,限制需要放在反向代理、Web 伺服器或其他位於 PHP 請求處理之前的邊界。 S1S2
2.3 CVE-2026-109619:請求記憶體已釋放,全域引用仍存活
這問題涉及錯誤、session、物件或擴充等狀態:欄位保存在 worker 可長期存取的位置,而欄位引用的資料卻屬於某一次請求。只要引用在請求堆釋放前未被正確清理,後續請求就可能遇到懸空狀態,形成 use-after-free。 S3
官方分析將可利用條件分為「生產者」與「消費者」:
| 角色或條件 | 意義 | 對防護的啟示 |
|---|---|---|
| 狀態生產者 | 將本請求擁有的狀態放入稍後仍可存取的位置 | 若要檢視異常退出後是否完成對應清理 |
| 狀態殘留 | 沒有清空,或清空後又被晚期操作重新填入 | 僅在較早的 shutdown 階段清空一次可能不夠 |
| worker 復用 | 同一 worker 繼續處理後續請求 | 多 worker 只是調度差異,不是記憶體安全保證 |
| 提前消費者 | 在對應狀態正常初始化前讀取、釋放或判斷殘留值 | 必須檢查初始化之前的 INI、session 等路徑 |
這些條件需要組合成立;任一次錯誤日誌本身並不證明漏洞已經被成功利用。 S3
它不以 OOM 為必要前提
致命錯誤可能使成對的狀態發布與恢復過程被打斷。例如編譯或解析失敗、未捕獲錯誤、require 失敗及執行逾時,都可能參與特定狀態的殘留。清理後期的非致命診斷也可能重新填入已經清空過的錯誤訊息。 S3
因此,將 memory_limit 改為無限制,無法覆蓋這組漏洞。
「初始化之前」需要分別看待
兩類已確認消費者的時序不同:
- 早期 INI 診斷:某些按請求應用的配置在普通 executor 初始化前發出 warning、notice 或 deprecation,診斷處理可能存取舊錯誤狀態。
- session upload progress:請求體解析過程中可能提前處理 session,而常規模組
RINIT尚未完成;不能以應用沒有明確呼叫session_start()推斷路徑不存在。 S3
並非所有 INI 配置都具有相同風險時機。 mod_php 的目錄級配置與 FPM 前端傳入的 PHP_VALUE / PHP_ADMIN_VALUE ,需要與普通 php.ini 、 FPM pool 啟動配置及 FPM .user.ini 區分。後幾者的應用時機不同,不能一概稱為同一種早期消費者。 S3
關閉 display_errors、log_errors,降低 error_reporting,或使用 @,只影響診斷的可見性;相關內部狀態可能在決定是否輸出日誌之前已經被存取。 S3
3. 修復版本與補丁選擇
3.1 上游正式修復基線
| 目前 PHP 分支 | 首個包含本次正式修復的版本 | 建議 |
|---|---|---|
| 8.2 | 8.2.35 | 升級至該版本或包含相同修復的後續版本 |
| 8.3 | 8.3.36 | 同上 |
| 8.4 | 8.4.27 | 同上 |
| 8.5 | 8.5.12 | 同上 |
| 8.1 及更早版本 | 上游常規支援已結束 | 優先遷移至支援分支;短期核對發行版或 LTS 廠商回補 |
版本與支援狀態依據 PHP 官方公告,時間基準為 2026 年 10 月 22 日。 S1
這些是分支內的修復門檻,不能用一句「所有低於 8.5.12 的版本均未修復」替代。 8.2.35 與 8.4.27 等其他分支版本已經包含修復。
3.2 發行版版本號碼不能只看上游部分
發行版常在較舊的上游版本號基礎上回補安全修復。驗收應同時檢查發行版、倉庫來源、完整包版本和安全公告。
例如,Ubuntu USN-8948-1 列出的 Ubuntu 24.04 LTS 修復套件版本為:
8.3.6-0ubuntu0.24.04.13
它的上游部分仍是 8.3.6,但該發行版完整包版本已列入本次修復公告。這個結論只適用於對應發行版和套件來源,不能推廣到任意同樣顯示 8.3.6 的建置。 S10
3.3 實際上應選擇哪一種 patch
| 場景 | 優先方案 | 驗收重點 |
|---|---|---|
| 使用發行版 PHP 套件 | 安裝廠商發布的安全性更新 | 公告同時涵蓋兩個 CVE;完整包版本正確 |
| 使用官方 PHP 容器或自建鏡像 | 更新基礎運行時,重新建置並部署 | 運行實例使用新鏡像;舊副本全部退出 |
| 自編譯支援分支 | 使用該分支正式安全版本或完整官方補丁集 | 分支匹配、依賴補齊、擴展兼容與回歸通過 |
| 受制於 7.4、8.0、8.1 | 優先廠商回補;必要時評估官方應急源碼分支 | 明確行為變化,並安排遷移至支援版本 |
官方已發布補丁,實際修復應從這些補丁出發。第 6 節中的程式碼片段用於說明設計與審查重點,不構成可單獨套用的完整補丁。 S1S4S6
4. 正式修復流程
4.1 盤點真正處理請求的運行時
建議形成一張簡短台賬,至少記錄:
- 入口網域名稱或服務、反向代理後端、PHP SAPI。
- PHP 分支、完整軟體包版本或鏡像摘要、建置來源。
- FPM pool、實際服務單元、容器或 Pod、副本數量。
- 已載入擴充功能、主配置與額外設定檔。
- 是否存在其他 PHP 版本、舊部署副本或可直接存取的來源站入口。
這些是實施建議,目的是使「已升級」能對應到每個實際流量入口。特別檢查命令列 PHP 與 Web PHP 使用不同版本、鏡像建置完成但舊容器仍運作、部分 pool 或副本未替換等情況。
4.2 選擇並部署正確更新
依第 3 節確認修復版本,優先使用既有可信任包源或受控建構鏈。更新範圍應包含實際承載請求的元件,例如 FPM 套件、Apache PHP 模組、CGI 二進位,或將 PHP 嵌入其中的服務建置。
升級前保留配置與套件清單,在與生產相近的環境先驗證啟動和關鍵業務。對於自行維護的擴展,檢查對應 PHP 版本的建置與運作相容性。只從另一個分支挑取一個看似相關的提交,不能保證整個修復集已完整落地。
4.3 重啟或捲動取代運行實例
安裝檔案與進程載入的新程式碼是兩件事。應重啟對應服務,或完成滾動替換,並確認舊 worker 與舊容器已退出。
對於 mod_php,重啟物件通常是承載模組的 Apache 進程;對於嵌入 PHP 的服務,需要取代承載進程本身。對 FPM 而言,清理 OPcache 不能取代運行時更新與舊 worker 退出。
採用優雅切換時,需要檢查舊代進程是否仍處理長連線或未完成請求。驗收記錄應包含實際服務、部署版本、變更完成時間及舊實例退出結果。
4.4 從真實 Web 請求驗證結果
推薦把下面幾項證據組合起來,而不是只依賴單一版本指令:
| 證據 | 能證明什麼 | 不能單獨證明什麼 |
|---|---|---|
| 套件管理器或鏡像元數據 | 安裝來源與完整建置版本 | 流量已經切換到該實例 |
| 行程及部署狀態 | worker 或容器完成替換 | 對外入口一定路由到它 |
| 經真實入口訪問的受限診斷結果 | 實際 Web SAPI 和運行配置 | 發行版回補是否涵蓋兩支 CVE |
| 廠商公告或完整補丁記錄 | 本次漏洞修復包含在該建置中 | 所有副本均已部署 |
可透過已有內部診斷能力,或臨時且受存取控制的診斷入口,核對 PHP_VERSION、PHP_SAPI、載入的設定檔及緊急設定。確保請求沒有被 CDN 或頁面快取直接返回;完成後移除臨時診斷入口。
php -v 通常只說明目前 shell 選擇的 CLI 二進位;php-fpm -i 等指令也不等於對現存、正在接收流量的 worker 做了驗證。不要將完整 phpinfo() 頁面長期暴露到公網。
4.5 業務回歸與緊急配置退出
圍繞本次變更影響的功能驗證正常登入、session 讀寫、表單與上傳、匯入、錯誤處理和 worker 穩定性。檢查錯誤率、回應延遲、記憶體峰值、進程退出及重建頻率是否異常。
若暫時設定了 memory_limit=-1,在正式修補程式生效後恢復與業務容量相符的有限預算;若已設定 pm.max_requests=1,在確認修復後恢復經過容量評估的生命週期配置。輸入大小限制和來源存取控制可依業務需要保留。
若升級出現相容性問題,應優先暫停受影響入口、限制其外部存取或調整應用,再處理回退。直接讓未修復運行時重新對公網服務,會重新開啟原有風險。
5. 無法立即升級時的緩解措施
本節用於短期降低暴露或改善可用性。每項措施只涵蓋特定路徑,多個措施組合也不等價於完整修復。 S1S2S3
5.1 優先控制進入 PHP 的流量
如果業務允許,將敏感服務暫時限制為可信任來源,或在 PHP 之前執行存取控制。需要上傳的業務收緊請求體、欄位、part、part 內頭部數量及嵌套深度;不需要 multipart 的業務可臨時在前端拒絕這類要求。 S2
這些限制要涵蓋所有通往 PHP 的入口。僅攔截名為「上傳」的 URL,會漏掉其他仍由 PHP 自動解析請求體的入口。對於分塊傳輸、缺少可用聲明長度等情況,應以實際讀取量執行限制;應用再次解壓縮或展開內容時,還要有自己的輸出大小預算。
5.2 FPM pool 設定模板
以下內容放在實際生效的 FPM pool 配置段中,不能原樣放入 php.ini。這裡的 8M 和 4M 是演示值,應根據業務調整;限制整個 POST 請求時也要為表單欄位與 multipart 邊界保留空間。 S11S12
; Temporary mitigation: evaluate capacity before applying.
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 4M
php_admin_flag[session.upload_progress.enabled] = off
僅 PHP 8.5,另加:
php_admin_flag[fatal_error_backtraces] = off
各項作用與邊界如下:
| 配置 | 預期作用 | 必須保留的限制說明 |
|---|---|---|
memory_limit=-1 | 去掉可預測的 PHP 請求記憶體上限觸發條件 | 不防底層分配失敗;取消了原有預算,可能增加記憶體耗盡和服務中斷風險 |
較小的 post_max_size | 縮小進入解析流程的資料規模 | 未修復解析器有已知缺口;無法單獨阻止記憶體放大 |
upload_max_filesize < post_max_size | 限制單一檔案大小並保留請求開銷空間 | 不約束全部欄位、元資料或應用程式內分配 |
| 關閉 upload progress | 阻斷已確認的 session 相關路徑 | 不覆蓋其他全域狀態變體 |
| PHP 8.5 關閉 fatal backtrace | 阻斷已確認的相關狀態生產者 | 不覆蓋 session 等其他路徑 |
措施邊界來自官方公告;pool 語法與管理層級配置語意請參閱 PHP 手冊。 S1S2S3S11
管理級 php_admin_* 設定不能由應用程式透過 ini_set() 改寫。仍應審查所有 pool、目錄級與前端傳入配置,確認實際請求上的值符合預期。修改後重建全部 worker,避免已有行程繼續持有舊狀態。
如果選擇直接修改 php.ini,語法應改為:
memory_limit = -1
post_max_size = 8M
upload_max_filesize = 4M
session.upload_progress.enabled = Off
與 pool 中的 php_admin_value 不同,僅在 php.ini 設定 memory_limit=-1 無法阻止應用程式再次修改它。應檢查框架、應用程式碼及其他配置是否重新設定有限值;需要強制執行時,使用適合當前 SAPI 的管理層級設置,並核對實際生效值。 S11S12
PHP 8.5 才追加 fatal_error_backtraces = Off。不要在應用程式碼中使用 ini_set() 作為關閉 upload progress 的補救,也不要依賴 FPM .user.ini 來保證它在請求解析前生效。 S1S3
5.3 取消 PHP 記憶體上限時如何控制代價
memory_limit=-1 是未修復環境的臨時路徑緩解,不應作為長期預設配置照搬。當應用或請求失控時,它可能耗盡更多宿主機記憶體。因此,使用前應評估並發數、單 worker 峰值及服務總容量,同時部署前端輸入限制。 S2
容器或 cgroup 記憶體限制可以提供可用性邊界,但不能保證分配永不失敗;限制觸發時,也可能終止整個容器、master 和全部 worker。隔離粒度及終止策略需要結合部署評估,不能稱為這兩個 CVE 的完整防護。 S2
不要以 RLIMIT_AS、RLIMIT_DATA 或對應的進程位址空間限制取代上述策略;它們可能直接造成底層分配失敗,進入仍然有問題的 OOM 路徑。 S2
5.4 Nginx 設定範例
適用於 Nginx 向 PHP-FPM 轉送請求的部署。可在實際承載服務的配置層級設定:
client_max_body_size 8m;
fastcgi_request_buffering on;
client_max_body_size 限制請求體大小;fastcgi_request_buffering on 讓 Nginx 先讀取完整請求體,再傳送至 FastCGI 後端。是否在某個 server 或 location 中生效,需要結合已有繼承和覆蓋關係檢查。 S13S14
僅在業務完全不需要 multipart 時,可在對應服務入口追加拒絕規則:
if ($http_content_type ~* "multipart/form-data") {
return 403;
}
這是使用 if 和 return 的簡單入口限制模板,而不是解析器漏洞的通用過濾規則。部署前先驗證設定語法與正常要求,並檢查自訂 error_page 是否把 403 請求重新交給 PHP。字串匹配對所有解析差異不提供完整保證。 S15
此外,這些配置不會自動限制應用自行解壓縮後的資料量、欄位數量或 PHP 物件數;需要上傳的業務應使用能夠理解其請求結構的額外限制。
5.5 可選措施:FPM 每個 worker 只處理一次請求
對能承受開銷的 FPM 部署,可評估短期設定:
pm.max_requests = 1
並重建已有 worker。它在 worker 復用層面阻斷已描述的跨請求鏈,但會增加進程創建和初始化開銷;同時不能修復 CVE-2026-109618 的同請求 OOM 與清理問題。值較小但大於 1 時,仍存在請求復用視窗。 S3S11
5.6 WAF 與 Cloudflare 的適用邊界
Cloudflare 已公佈針對 PHP2Shell 的防護,包括 Free 計畫在內的部署安排。使用其服務時,應核對實際流量是否經過代理、防護規則是否生效,以及源站能否被直接繞過;付費計劃還需檢查相應 Managed Rules 的配置與例外。 S16
該廠商說明的部分偵測利用回應訊號,因此不能將所有防護理解成「每次攻擊都在到達 PHP 之前被攔截」。 WAF 可輔助減少暴露並提供觀察信息,但繼續升級 PHP 仍是必要工作。 S16
5.7 常見措施為何不足
| 措施或判斷 | 不足之處 |
|---|---|
只關閉 file_uploads | 不能覆蓋全部 multipart 處理和應用自身的 OOM 路徑 |
設定 enable_post_data_reading=Off | 改變自動填充 POST/FILES 的行為;請求體仍可由應用程式讀取,也可能破壞正常業務 |
關閉錯誤顯示、日誌或使用 @ | 內部診斷處理仍可能存取懸空狀態 |
| 增加 worker 數量 | 不建立安全的狀態清理機制,也不消除單 worker 復用 |
僅配置 disable_functions 或 open_basedir | 這些限制不能修復 C 引擎層記憶體破壞 |
| 只檢查一次版本或單一副本 | 無法證明所有入口與運行實例都已替換 |
| 沒有看到異常日誌 | 不能證明漏洞不存在,也不能證明先前未被利用 |
其中 PHP 配置與漏洞機制依據見 S2S3S12;關於覆蓋、驗收和權限邊界的判斷是基於這些機制提出的實施建議。
6. 官方 patch 的實現思路與審閱重點
6.1 應使用完整、匹配分支的修復
PHP 8.5 的代表性官方投稿為:
31c0ea7d7ff1d1fccb6492e52dab544011628065
Avoid state corruption via bailout
提交明確關聯兩個 CVE,既調整 OOM 退出機制,也修復可能在初始化階段存取的全域狀態。提交頁面與完整 diff 請參閱 S4S5。其他分支應使用自己的正式安全發布或匹配補丁,不能假設 8.5 的改動可直接應用到任意舊版本。
6.2 OOM 修復:先恢復可處理狀態,再終止請求
正式實作引入了軟、硬兩個層次的 OOM 處理:達到軟限制時記錄待處理狀態,並在 VM 等安全位置進行受控退出;可失敗分配介面則允許呼叫方明確處理失敗。對於無法直接返回失敗的分配,若預期超過更高的硬性閾值,便直接終止進程,避免在不安全的中間狀態繼續處理請求。 S2S5
以下是 8.5 diff 中用於標記待處理 OOM 的關鍵程式碼節選:
EG(delayed_oom).state = ZEND_OOM_STATE_PENDING;
zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
VM 中斷處理及其他安全位置隨後呼叫 zend_handle_delayed_oom()。其使用者程式碼執行上下文使用受控的 unwind 路徑;硬 OOM 路徑呼叫 _exit(137)。 S5
從設計來看,重要變化是:避免在結構正處於中間狀態時隨意跨越 C 呼叫棧,把退出放在能夠維護狀態約束的位置。軟限制觸發後可能需要額外記憶體完成此過程,因此不能把新的軟閾值理解為進程總記憶體的絕對上限。
也不能概括成「PHP 已刪除所有 zend_bailout()」。補丁仍在特定上下文使用相關機制;修復的是危險的退出時機及與之配套的狀態管理。 S5
6.3 分配呼叫者與 multipart 解析必須一起修改
如果只把一個分配函數改成“失敗返回 NULL”,而原來的呼叫者仍假定永遠返回有效指針,就會引入新的空指針訪問或未完成清理。補丁中的可失敗分配接口,需要與呼叫方檢查及異常傳播共同審閱。 S5
multipart 相關修復還包括以實際讀取量執行累積檢查、減少無用或重複頭部的處理,並在 OOM 待處理狀態下採用一致的退出與清理邏輯。這些改動既縮小資源放大面,也使解析器遵守新的 OOM 協定。 S5
對維護自有分支的團隊,建議以下問題作為程式碼審查重點:
- 狀態在完全初始化前是否已對外可見?
- 分配失敗或回呼異常後,所有權是否仍然唯一且明確?
- 中途失敗能否到達統一清理出口?
- 清理函數能否辨識未初始化或已釋放欄位?
- 解析預算是否以實際處理量為依據,而不僅信任請求宣告?
- 新的失敗回傳值是否已被所有呼叫者處理?
這些都是依據修復機制歸納的審閱問題,並非對某個未提供程式碼庫已經完成的稽核結論。
6.4 跨請求修復:初始化更早,最終清理更晚
對應 CVE-2026-109619,補丁同時調整多個生命週期邊界。以下列出的文件和操作以 PHP 8.5 官方 diff 為例。 S5
| 位置 | 改動方向 | 解決的問題 |
|---|---|---|
Zend/zend_execute_API.c 等 | 重設錯誤、異常、handler、物件等執行器狀態 | 請求結束後仍存在可被下一請求存取的舊引用 |
main/main.c 的啟動路徑 | 在 SAPI 啟動前呼叫 php_basic_early_rinit() | 提前消費者發生時,相關基礎狀態尚未初始化 |
main/main.c 的關閉路徑 | 銷毀 HTTP globals 後標記為未定義 | 釋放後的值仍被當成有效對象 |
| 請求堆最終釋放之前 | 再次清理最後錯誤訊息 | 較晚的清理診斷重新填入錯誤狀態 |
ext/session/session.c | upload progress 早期路徑先初始化 session globals | 常規模組 RINIT 之前訪問舊狀態 |
| 序列化相關程式碼 | 早期處理中限制類別解析與使用者回調 | 初始化未完成時執行額外使用者程式碼 |
例如,啟動順序中加入了:
zend_activate();
php_basic_early_rinit();
sapi_activate();
HTTP globals 銷毀後補上狀態破壞標記:
zval_ptr_dtor(&PG(http_globals)[i]);
ZVAL_UNDEF(&PG(http_globals)[i]);
這些節選表達了兩個通用原則:第一次可能使用某類狀態之前就要初始化;最後一次可能重新產生該狀態之後才進行最終清理。 只在常規 RINIT 或較早的 shutdown 中修一次指針,可能遺漏更早或更晚的訪問。 S5
6.5 為什麼不建議自製“一行修復”
把所有 zend_bailout() 替換成 _exit(),或僅在某個入口加一個空指標檢查,都會跨越大量不同的執行上下文。它們可能遺漏其他狀態字段,也可能改變析構、擴展回調、進程宿主和請求結束語義。
對於受支援版本,優先採用已經發布的完整修復。自有分支的移植應記錄修補程式來源、適用基準、衝突處理和行為變化,並使用對應上游迴歸案例及本業務流程驗證。這裡的建議來自對補丁跨 allocator、VM、session 與請求生命週期改動範圍的分析。 S4S5
7. PHP 7.4、8.0、8.1 的緊急回補
PHP 官方針對嚴重性提供了三個舊分支的最小原始碼回補,分支名分別為:
PHP-7.4-CVE-2026-109618-109619
PHP-8.0-CVE-2026-109618-109619
PHP-8.1-CVE-2026-109618-109619
分支連結見 S7S8S9。這些是針對本次漏洞的緊急交付,不表示上游恢復對舊分支的常規支持,也不取代其他安全更新。 S1
7.1 以 PHP 8.1 回補為例
PHP 8.1 的相關提交是:
1d7dec178a094e6e75b229ce22754045f8c6070a
其策略更偏向快速失敗:在對應 OOM 路徑中輸出診斷後呼叫 _exit(1),並包含全域狀態及其他相關修正;同時在內部停用 session upload progress 的相應處理。相關原始碼提交請見 S6。
這與受支援分支中較完整的延遲 OOM 設計存在行為差異。不能只因為配置裡仍顯示 upload progress 開啟,就認為經過該回補的內部處理行為也保持不變。
7.2 部署時需要接受的代價
直接退出程序可能會使目前請求失敗,跳過正常 PHP shutdown 回調及部分應用收尾;若宿主採用執行緒模型,還需評估整個宿主程序退出對其他並發工作造成的影響。進程管理器是否能及時拉起替代進程,也應納入驗證。
因此,舊版本用戶應按順序考慮:
- 遷移至受支援 PHP 分支及其修復版本。
- 使用明確覆蓋兩款 CVE 的發行版或 LTS 廠商更新。
- 確實受制於舊版原始碼時,採用符合版本的官方應急回補,驗證完整改動與相容性,並安排後續遷移。
這裡的「最小回補」描述的是官方選擇的兼容性與實現範圍,不應自動解釋為「已知不完整的漏洞修復」。應依對應分支源碼及廠商公告判斷,不自行刪減其中的配套改動。 S1S6
8. 驗收標準與後續檢查
建議以下結果作為本次修復完成的依據:
| 驗收項 | 完成標準 |
|---|---|
| 漏洞覆蓋 | 建置或廠商公告明確修復 CVE-2026-109618 和 CVE-2026-109619 |
| 部署覆蓋 | 全部入口、pool、服務及副本使用修復後的運行時 |
| 行程生效 | 舊 worker、舊宿主程序和舊容器全部退出 |
| 真實請求驗證 | 經實際 Web 連結確認 SAPI、建置與配置,排除快取誤導 |
| 業務可用性 | session、上傳、匯入、錯誤處理等關鍵功能透過正常回歸 |
| 臨時措施收尾 | 無限記憶體及極端 worker 回收配置已按容量計畫恢復或有明確保留原因 |
| 變更證據 | 記錄包版本或鏡像摘要、更新範圍、完成時間與驗證結果 |
上述為實施建議。若需驗證 OOM 行為、異常退出或自訂補丁,應在隔離環境中使用上游迴歸測試或受控故障注入,避免以生產環境反覆製造記憶體耗盡作為驗收方式。
同時檢查更新前後的 PHP fatal、worker 崩潰、異常重啟、請求錯誤率及 WAF 事件。日誌只能提供調查線索:出現 OOM 不等於已經被利用,缺少警告也不能證明未發生過利用。升級修復的是未來可達到的漏洞路徑;如已發現文件竄改或憑證外洩等異常,應另行進行事件調查。
附錄:引用來源與鏈接
所有連結均集中在本附錄。漏洞公告、修補程式和廠商通告以 2026 年 10 月 22 日 的核驗結果為基準;後續部署應繼續查看供應商更新。正文 [Sx] 對應以下編號。
S1 — PHP 官方安全公告與修復版本
- 資料來源:PHP 項目,2026 年 10 月 21 日。
- 用途:風險範圍、四個正式修復版本、官方臨時措施及舊分支應急回補說明。
- 參考連結: PHP 2026 年公告歸檔中的本安全公告
S2 — CVE-2026-109618 官方技術公告
- 資料來源:php/php-src GitHub Security Advisory,2026 年 10 月 21 日。
- 標識:GHSA-w8hw-w9fx-j79w。
- 用途:OOM、multipart 放大、SAPI 差異與緩解措施的覆蓋邊界。
- 參考連結: RCE via OOM corruption
S3 — CVE-2026-109619 官方技術公告
- 資料來源:php/php-src GitHub Security Advisory,2026 年 10 月 21 日。
- 標識:GHSA-6wmf-4rv5-p3rp。
- 用途:跨請求懸空狀態、生產者與消費者、提前初始化、配置緩解。
- 參考連結: RCE via stale global state
S4 — PHP 8.5 官方修復提交
- 來源:php/php-src。
- 提交:
31c0ea7d7ff1d1fccb6492e52dab544011628065。 - 用途:確認正式提交同時修復兩個漏洞,並說明整體改動方向。
- 參考連結: Avoid state corruption via bailout
S5 — PHP 8.5 官方修復完整 diff
- 來源:同一官方提交的 diff。
- 用途:核對 allocator、VM、HTTP globals、session、序列化和請求生命週期的具體實現。
- 參考連結: 官方完整 diff
S6 — PHP 8.1 官方緊急回補提交
- 來源:php/php-src。
- 提交:
1d7dec178a094e6e75b229ce22754045f8c6070a。 - 用途:核對舊分支快速退出及 upload progress 行為變化。
- 參考連結: PHP 8.1 緊急回補提交
- 參考連結: 對應完整 diff
S7 — PHP 7.4 官方緊急回補分支
- 資料來源:php/php-src,PHP 官方安全公告所列原始碼分支。
- 參考連結: PHP-7.4-CVE-2026-109618-109619
S8 — PHP 8.0 官方緊急回補分支
- 資料來源:php/php-src,PHP 官方安全公告所列原始碼分支。
- 參考連結: PHP-8.0-CVE-2026-109618-109619
S9 — PHP 8.1 官方緊急回補分支
- 資料來源:php/php-src,PHP 官方安全公告所列原始碼分支。
- 參考連結: PHP-8.1-CVE-2026-109618-109619
S10 — Ubuntu 安全公告 USN-8948-1
- 資料來源:Canonical / Ubuntu,2026 年 10 月 21 日。
- 用途:發行版回補及完整軟體包版本範例。
- 參考連結: USN-8948-1: PHP vulnerabilities
S11 — PHP-FPM 設定手冊
- 資料來源:PHP 官方文件。
- 用途:
php_admin_value、php_admin_flag、pm.max_requests等設定語意。 - 參考連結: PHP-FPM Configuration
S12 — PHP 核心 INI 設定手冊
- 資料來源:PHP 官方文件。
- 用途:請求體、上傳、記憶體與自動 POST 讀取組態的定義及適用範圍。
- 參考連結: Description of core php.ini directives
S13 — Nginx 請求體大小限制
- 資料來源:Nginx 官方文件。
- 用途:
client_max_body_size的意義及可用配置層級。 - 參考連結: ngx_http_core_module / client_max_body_size
S14 — Nginx FastCGI 請求緩衝
- 資料來源:Nginx 官方文件。
- 用途:
fastcgi_request_buffering的處理順序與作用。 - 參考連結: ngx_http_fastcgi_module / fastcgi_request_buffering
S15 — Nginx 條件與返回指令
- 資料來源:Nginx 官方文件。
- 用途:
if與return的設定語法。 - 參考連結: ngx_http_rewrite_module
S16 — Cloudflare PHP2Shell 防護說明
- 資料來源:Cloudflare 官方部落格。
- 用途:其 WAF 部署範圍、Free 計畫安排及偵測機制說明。
- 參考連結: Cloudflare deploys protections against php2shell vulnerability