2026 年 10 月 20 日,PHP 宣布將於 10 月 21 日 14:00 UTC(台灣時間22:00)釋出修補程式,修正兩項重大漏洞。
這兩項漏洞合稱 PHP2Shell,因為攻擊者可利用它們達成遠端程式碼執行(Remote Code Execution,RCE)。
漏洞細節原定於 10 月 22 日公開,但包括 CVE 編號(CVE-2026-109619 與 CVE-2026-109618)在內的資訊提前外洩。
CVE-2026-109619 是 PHP核心上傳進度功能中的釋放後使用(Use-After-Free,UAF)漏洞。
CVE-2026-109618
則是由記憶體不足(Out-of-Memory,OOM)狀態觸發的記憶體毀損漏洞。
本文將詳細解析這兩項漏洞的成因與運作機制。
目標使用 PHP 5 或更新版本,且 session.upload_progress.enabled = On(預設設定)。
目標存在可接收檔案上傳的端點,即使應用程式本身忽略上傳檔案也一樣:PHP的工作階段層會在使用者層程式碼執行前,先處理上傳進度。
攻擊者必須取得該編譯版本中 zval_ptr_dtor 與 zend_call_function之間的位址偏移量;此資訊可由執行階段洩漏取得,或根據目標實際使用的 PHP執行檔與發行版推導。
利用這項漏洞可分為五個步驟:
啟用上傳進度功能
建立記憶體釋放的觸發條件
觸發記憶體釋放
進行堆積噴灑
觸發漏洞利用

圖 1 CVE-2026-109619 漏洞利用流程
圖中流程由左至右:① 開始上傳,觸發 upload_progress,指標指向 $_SESSION 資料。②暫停連線超過 1 秒並觸發釋放,產生 UAF 與懸空指標。③ 堆積噴灑,以 t[]酬載填入含有 system() 的偽造結構。④ 觸發 RCE,執行system('cmd')。圖中將正文的「建立條件」與「觸發釋放」合併為同一階段。
PHP 的預設編譯版本提供upload_progress(上傳進度)功能,可即時回報檔案已成功上傳至伺服器的進度。
使用者透過 multipart/form-data(RFC 1867 所描述的格式)上傳資料,且包含名為PHP_SESSION_UPLOAD_PROGRESS 的 POST 參數時,PHP會在目標指令碼開始執行前攔截並處理該參數。
PHP 會立即在 $_SESSION 陣列中建立特定的索引鍵。$_SESSION是內建的超全域關聯陣列,用於在同一次網站造訪期間,跨頁面儲存與存取使用者資料。檔案逐區塊上傳時,PHP會持續更新該工作階段陣列中的資訊,例如content_length(總大小)、bytes_processed(已處理位元組數)與檔案名稱。
在底層,PHP 的 HTTP 剖析器遇到檔案欄位時,會觸發內部 FILE_START事件。為了有效率地追蹤進度,PHP 會建立直接指向記憶體中該工作階段陣列的 zval指標,也就是 PHP 核心內部使用的 C 語言記憶體指標,讓 C層級引擎快速寫入收到的位元組計數。
以下是 PHP 核心原始碼 php_session.h 中的 php_session_rfc1867_progress 結構:
typedef struct _php_session_rfc1867_progress {
zval sid;
smart_str key;
zend_long update_step;
zend_long next_update;
double next_update_time;
bool cancel_upload;
bool apply_trans_sid;
size_t content_length;
zval data; /* 匯出至工作階段資料的陣列 */
zval files; /* data["files"] 陣列 */
zval *post_bytes_processed; /* data["bytes_processed"] */
zval *current_file_bytes_processed;
zval current_file; /* 目前上傳檔案的陣列 */
} php_session_rfc1867_progress;
PHP 在處理 multipart 上傳時,會快取兩個原始 zval * 指標:
zval *post_bytes_processed;
zval *current_file_bytes_processed;
因此,當攻擊者送出包含下列內容的 POST 請求時:
PHPSESSID Cookie/欄位:設定工作階段識別碼。
PHP_SESSION_UPLOAD_PROGRESS 欄位:啟用進度追蹤。
檔案欄位:觸發 FILE_START,並快取上述兩個指標。
當使用者送出觸發進度追蹤的 POST 請求後,這兩個 zval 指標會指向已解碼進度資料樹中,仍有效的雜湊表儲存格(bucket)。
攻擊者將 TCP 資料串流暫停超過 1 秒,也就是預設的session.upload_progress.min_freq 間隔。這是為了通過 PHP對進度更新頻率的限制檢查。
此時,在第二次 update() 執行前,記憶體狀態如下:
progress->post_bytes_processed → [ Tree-A 中的 bucket ]
progress->current_file_bytes_processed → [ Tree-A 中的 bucket ]
Tree-A 是目前有效且已解碼的工作階段資料,儲存在 $_SESSION 的"upload_progress_x" 索引鍵下。
資料傳輸恢復時,會觸發第二個 FILE_DATA事件。由於已經過足夠時間,php_session_rfc1867_update()
會再次執行,並依序產生下列流程:
flush 完成後,將 session_status 設為 none。
由於狀態為 none,PHP 將此視為開啟新的工作階段。它從磁碟讀取檔案,並將資料反序列化為全新的資料樹,以下稱為Tree-B。Tree-B 配置在不同的記憶體位址,內容雖然與 Tree-A相同,但實際上是一份副本。
此時,PHP 中有兩份使用相同工作階段索引鍵的資料樹:
Tree-A:舊的資料樹,仍位於 $_SESSION 中。
Tree-B:剛完成反序列化的新資料樹。
zend_hash_update() 將 Tree-B 放入 $_SESSION。雜湊表的pDestructor(zval_ptr_dtor)會處理舊值 Tree-A,釋放 Tree-A 及其所有 bucket。
此時,progress->post_bytes_processed 與 progress->current_file_bytes_processed所指向的記憶體已被釋放,因此成為懸空指標。
Tree-B 位於新的位址,但快取的指標並未更新。
也就是說,PHP 先快取指向雜湊表內部的原始 zval *指標,之後卻銷毀並替換雜湊表,沒有同步更新這些指標。這便造成 UAF,也是CVE-2026-109619 的漏洞根源。
記憶體釋放後,攻擊者立即在同一次上傳的 HTTP POST 主體中加入大量名為 t[]的表單欄位。為了提高填滿目標配置區域的機率,使用的並非單一欄位:
--boundary
Content-Disposition: form-data; name="t[]"
<288 位元組的偽造結構>
--boundary
Content-Disposition: form-data; name="t[]"
<288 位元組的偽造結構>
...(共 149 個)
PHP 的 multipart 剖析器(rfc1867.c)會將這些欄位當作 FORMDATA 事件處理。每個值會轉為 zend_string,存入 PHP 陣列 $_POST['t']。t[]這個名稱表示將值附加至陣列,因此 149 個值會依序存入 $_POST['t'][0] 到$_POST['t'][148]。
每個欄位都包含偽造的 PHP 內部函式結構,其中嵌入system("<command>")。這些配置的大小經過設計,目的是佔用剛被釋放的相同記憶體區塊。若配置器重新分配該區塊,攻擊酬載便可佔據它,使懸空指標轉而指向攻擊者的酬載。
攻擊者在同一個 POST 請求中送出第二個檔案。PHP透過懸空指標,向它認為仍是進度結構的記憶體寫入資料:
case MULTIPART_EVENT_FILE_DATA: {
const multipart_event_file_data *data = event_data;
if (!Z_TYPE(progress->sid) || !progress->key.s) {
break;
}
Z_LVAL_P(progress->current_file_bytes_processed) = data->offset +
data->length;
Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;
php_session_rfc1867_update(progress, false);
}
但該位置此時已是攻擊者偽造的函式結構。當 PHP引擎存取這些遭毀損的資料時,便會沿著偽造指標執行,呼叫 system("攻擊者的命令")。
這項漏洞的問題在於:OOM 迫使 PHP以非預期方式中止執行,可能略過正常的清理程序,導致未初始化的記憶體遭惡意覆寫,進而被用於執行程式碼。
攻擊者送出特製請求,快速消耗記憶體,直到達到 PHP 設定的memory_limit。大量檔案上傳便可能造成這種情況。
PHP 核心偵測到 OOM狀態後,必須停止執行。它不會循正常函式返回流程逐步清理變數,而是使用底層 C 語言機制 longjmp(),直接跳離目前的呼叫流程,進入請求結束處理機制。
由於 longjmp()突然跳過區域清理與初始化程式碼,部分內部結構會處於不穩定、未初始化或懸空的狀態。
攻擊者可以利用此情況,在 OOM 發生前先操控堆積配置,例如使用前述大量送出 t[]表單欄位的技巧。
這會將含有惡意執行指標的偽造 C 語言結構填入記憶體區域。
最後,在 php_request_shutdown 階段,PHP會依序處理並釋放仍有效的資源。當引擎遇到因 longjmp()中斷而留下的未初始化記憶體時,便可能直接讀取攻擊者偽造的結構。若引擎透過該毀損記憶體中的函式指標(例如解構函式)進行跳轉,執行流程就會轉向攻擊者的酬載,造成
RCE。
AkamaiExAR客戶已收到事件通知,內容列出受影響資產,包括有漏洞的 PHP版本、使用中的網頁伺服器整合方式,以及建議的緩解與網路分段措施。
以下 Akamai Guardicore Segmentation Insight 查詢可用於偵測有漏洞的應用程式,並協助進行分段:
編註:原稿下列 Linux 查詢有多處截斷,包含REGEX_MATCH、版本字串與型別轉換運算式,無法直接執行。此處保留原始內容供對照;執行前須取得完整查詢。
WITH LINUX AS (
SELECT DISTINCT name, version, source
FROM deb_packages
WHERE REGEX_MATCH(name, '^php[0-9][0-9.]*-(common|cli)$', 0) IS NOT NULL
UNION ALL
SELECT DISTINCT name, version, source
FROM rpm_packages
WHERE REGEX_MATCH(name, '^(rh-)?php[0-9.]*[uw]?(-php)?-(common|cli)$', 0) IS
NOT NULL
),
PARSED AS (
SELECT name, source, version,
CAST(REGEX_MATCH(version, '([0-9]+)\.([0-9]+)\.([0-9]+)', 1) AS INTEGER) AS
major,
CAST(REGEX_MATCH(version, '([0-9]+)\.([0-9]+)\.([0-9]+)', 2) AS INTEGER) AS
minor,
CAST(REGEX_MATCH(version, '([0-9]+)\.([0-9) AS patch,
CASE
WHEN version LIKE '%8.5.9-0ubuntu2.%'
THEN CAST(REGEX_MATCH(version, '0ubuntu2\.([0-9]+)', 1) AS INTEGER) >= 1
WHEN version LIKE '%8.5.4-0ubuntu1.%'
THEN CAST(REGEX_MATCH(version, '0ubuntu1\.([0-9]+)', 1) AS INTEGER) >= 5
WHEN version LIKE '%8.3.6-0ubuntu0.24.04
THEN CAST(REGEX_MATCH(version, '24\.04\.([0-9]+)', 1) AS INTEGER) >= 13
WHEN version LIKE '%8.1.2-1ubuntu2.%'
THEN CAST(REGEX_MATCH(version, '1ubunt >= 28
WHEN version LIKE '%7.4.3-4ubuntu2.%'
THEN CAST(REGEX_MATCH(version, '4ubunt * 1000
+ COALESCE(CAST(REGEX_MATCH(versiGER), 0) >= 29005
WHEN version LIKE '%7.2.24-0ubuntu0.18.0
THEN CAST(REGEX_MATCH(version, '18\.04 1000
+ COALESCE(CAST(REGEX_MATCH(version, 'esm([0-9]+)', 1) AS INTEGER), 0) >=
17014
WHEN version LIKE '%7.0.33-0ubuntu0.16.0
THEN CAST(REGEX_MATCH(version, '16\.04\.([0-9]+)', 1) AS INTEGER) * 1000
+ COALESCE(CAST(REGEX_MATCH(versiGER), 0) >= 16022
WHEN version LIKE '%5.5.9+dfsg-1ubuntu4.
THEN CAST(REGEX_MATCH(version, '1ubunt * 1000
+ COALESCE(CAST(REGEX_MATCH(version, 'esm([0-9]+)', 1) AS INTEGER), 0) >=
29018
ELSE 0
END AS ubuntu_fixed
FROM LINUX
)
SELECT name, source, version
FROM PARSED
WHERE ubuntu_fixed = 0
AND (
(major = 8 AND minor = 5 AND patch < 12)
OR
(major = 8 AND minor = 4 AND patch < 27)
OR
(major = 8 AND minor = 3 AND patch < 36)
OR
(major = 8 AND minor = 2 AND patch < 35)
OR
(major = 8 AND minor IN (0, 1))
OR
(major BETWEEN 4 AND 7)
)
WITH WINDOWS AS (
SELECT DISTINCT name, version, install_location AS source
FROM programs
WHERE publisher = 'The PHP Group'
OR (name LIKE 'XAMPP%' AND publisher IN ('Bitnami', 'Apache Friends'))
UNION ALL
SELECT DISTINCT path AS name, product_version AS version, 'php.exe' AS source
FROM file
WHERE path LIKE 'C:\php%\php.exe'
OR path = 'C:\Program Files\PHP\php.exe'
OR path LIKE 'C:\Program Files\PHP\%\php.exe'
OR path = 'C:\Program Files (x86)\PHP\php.exe'
OR path LIKE 'C:\Program Files (x86)\PHP\%\php.exe'
OR path = 'C:\xampp\php\php.exe'
OR path LIKE 'C:\wamp%\bin\php\%\php.exe'
OR path LIKE 'C:\laragon\bin\php\%\php.exe'
OR path LIKE 'C:\tools\php%\php.exe'
OR path = 'C:\ProgramData\chocolatey\lib\php\tools\php.exe'
OR path LIKE 'C:\Users\%\scoop\apps\php\current\php.exe'
),
PARSED AS (
SELECT name, source, version,
CAST(REGEX_MATCH(version, '([0-9]+)\.([0-9]+)\.([0-9]+)', 1) AS INTEGER) AS
major,
CAST(REGEX_MATCH(version, '([0-9]+)\.([0-9]+)\.([0-9]+)', 2) AS INTEGER) AS
minor,
CAST(REGEX_MATCH(version, '([0-9]+)\.([0-9]+)\.([0-9]+)', 3) AS INTEGER) AS
patch
FROM WINDOWS
)
SELECT name, source, version
FROM PARSED
WHERE
(major = 8 AND minor = 5 AND patch < 12)
OR
(major = 8 AND minor = 4 AND patch < 27)
OR
(major = 8 AND minor = 3 AND patch < 36)
OR
(major = 8 AND minor = 2 AND patch < 35)
OR
(major = 8 AND minor IN (0, 1))
OR
(major BETWEEN 4 AND 7)
合稱 PHP2Shell的兩項重大漏洞(CVE-2026-109618與CVE-2026-109619),可讓攻擊者在執行PHP 5 或更新版本的伺服器上達成 RCE。請立即修補 PHP。
Akamai 資安情報團隊(Security Intelligence Group)將持續監控、報告這類威脅,並為客戶及整體資安社群制定緩解措施。如欲掌握團隊的最新消息,請造訪研究首頁,並追蹤我們的社群媒體。