重大漏洞 CVE-2026-109618 CVE-2026-109619 RCE復現 PHP session.upload_progress 研究報告
CVE-2026-109618 / CVE-2026-109619 — 從原始碼分析到跨架構(x64 / ARM64)RCE 驗證
Gertech 耘鼎科技 · 資安研究報告
測試日期:2026-10-21 ~ 2026-10-22 · 全程於自有 lab(127.0.0.1 與授權的區網主機 192.168.9.41)執行
0. 摘要
PHP 的 session.upload_progress 機制(PHP 5.4.0 起預設啟用)在 multipart POST 解析階段、任何應用程式碼執行之前 就會運作。本研究在本地 lab 完整重現並驗證了三條獨立攻擊鏈,並將其中兩條升級為可觀察的真實 RCE(shell_exec() 輸出捕獲),最後在 ARM64(NVIDIA 平台 aarch64, PHP 8.4.4)上完整命中,證明此漏洞類別與 CPU 架構無關。
| # | 鏈 | 病根類型 | RCE 條件 | x64 | ARM64 |
|---|---|---|---|---|---|
| 1 | session injection + LFI | 邏輯(fixation + 檔名未消毒) | 應用有 include 面 | ✅ 真實 RCE | ✅ 真實 RCE |
| 3 | handler mismatch → __destruct | 反序列化(解碼失敗路徑) | 雙目錄 handler 不一致 + 類別已載入 | ✅ 真實 RCE | ✅ 解碼失敗命中* |
| 2 | var_destroy UAF reentry | 記憶體破壞 | Linux(/proc/self/mem) | ✅ 行為面命中 | 未測(同機制) |
* chain3 在 ARM64 上的 HTTP 端解碼失敗路徑已命中;CLI 級 shell_exec 證據可用 --docker 步驟補齊(見 §6)。
測試環境
| 節點 | 角色 | 規格 |
|---|---|---|
| 攻擊端 | 發動者 | Windows x64, Python 3.14(純 stdlib 腳本) |
| 本地 lab | x64 目標 | PHP 8.4.4 NTS x64(windows.php.net 官方 zip),兩台 php -S |
| 區網 lab | ARM64 目標 | php:8.4.4-cli 官方 image(NVIDIA aarch64 核心),docker compose |
1. 攻擊面與根因
1.1 upload_progress 為什麼危險
php_session_rfc1867_callback()(ext/session/session.c)註冊到 multipart 解析器,事件流為
START → FORMDATA → FILE_START → FILE_DATA → FILE_END → END。
它具有三個關鍵性質:
- 先於應用程式碼執行(SAPI 讀 body 階段)— 應用層防護完全插不上手
- 無需認證 — 任何接受 multipart POST 的 endpoint 都會觸發
- 攻擊者可控制 session 檔案內容(見下)
1.2 兩個原始碼事實(PHP 8.4.4 逐行核對)
- Bug 1 — session fixation:
MULTIPART_EVENT_FORMDATA分支直接接受 POST body 裡的PHPSESSID,沒有session.use_only_cookies檢查(修補 PR #22113 才加上!PS(use_only_cookies) &&guard)。攻擊者 → 任意指定 session 檔案路徑。 - Bug 2 — 檔名未消毒:
add_assoc_string_ex(&progress->current_file, "name", ..., *data->filename)把上傳檔名原封不動寫進 session 檔。|、序列化物件、甚至<?php ... ?>都會原樣落地。
1.3 serialize_handler 不一致 = split at |
session 檔案格式由 session.serialize_handler 決定:
php:key|serialized_value(以|分隔)php_serialize:整個$_SESSION一次serialize()(無|前綴)
同一台主機上若兩個目錄(.user.ini)或兩個服務使用不同 handler、共用同一個 session.save_path,php handler 會把注入檔名裡的第一個 | 當成 key/value 分隔符,將其後的 O:4:"Evil":... 當作 session 值反序列化。
1.4 解碼失敗 ≠ 沒事:__destruct 先行
php_session_decode() 失敗時 session 會被銷毀,但 PHP_VAR_UNSERIALIZE_DESTROY(var_hash) 清理在失敗路徑上照跑,此時物件已實例化、__destruct 已進入 dtor 串列 → 先執行、後銷毀。若類別帶 POP gadget(如 __destruct 內 shell_exec($this->cmd)),即為 pre-auth RCE。
1.5 (對照組)記憶體破壞路線
jackalsyn 的 php-up-2026 bundle 走另一條:post_bytes_processed / current_file_bytes_processed 兩個 cached bucket pointer 指向被 re-decode/track_init 釋放的樹,149 個 t[] 欄位 groom 出 320-byte bin 落點,偽造 zend_fcall_info(64B, param_count@+48)/ zend_internal_function(handler@+88)→ zend_call_function → system()。此路線需要 runtime zval_ptr_dtor 位址洩漏(per-build delta),且結構 offset 全為 x86-64 幾何 — 與本研究驗證的邏輯鏈(架構無關)形成對照。
2. Lab 拓撲與攻擊流程

兩台 php -S 共用 session.save_path、各持不同 serialize_handler(writer=php_serialize、reader=php),是 Chain 3 的必要條件;lfi.php 則是 Chain 1 的觸發面。五個根因切片(A–E)標註於圖下。
3. Chain 1 — Session Injection + LFI → RCE
原理:fixation(body SID)決定 session 檔案路徑 → 檔名 payload <?php system('...');echo 'MARK';?> 原樣寫入 session 檔 → 應用的 LFI include 該檔 → 序列化外殼是惰性文字、<?php ?> 執行 → 指令輸出直接出現在 HTTP 回應。

回應體中 s:55:"msi\user⏎RCE-ARM-OK"; 的位置 — system() 輸出與 echo 的標記精準嵌在序列化結構的檔名欄位,證明 include 執行的是攻擊者注入的代碼。三種 payload(normal / PHP 代碼 / |O:8:"stdClass")全部原樣落地的完整證據見 evidence/chain1-local.txt。
4. Chain 3 — Handler Mismatch → __destruct → RCE
原理:§1.3 + §1.4 的合成。payload 檔名為
x|O:4:"Evil":1:{s:3:"cmd";s:N:"<命令>";}(s:N 長度必須與命令完全一致,否則屬性賦值中斷 — 實測踩坑點)。php handler split 後 unserialize 實例化 Evil、隨即因尾隨殘骸失敗,但 __destruct 已執行 shell_exec()。

關鍵證據鏈(rce_proof.txt):
RCE executed! cmd=whoami && ver
output:
msi\user
Microsoft Windows [Version 10.0.26200.9711]
觸發點的 Warning 顯示 PHP Request Startup: — 證明執行發生在 rfc1867 內、任何應用程式碼之前。完整輸出見 evidence/chain3-local.txt。
5. Chain 2 — var_destroy __destruct Reentry UAF
原理:ext/standard/var_unserializer.re 的 var_destroy() 先釋放 var_hash 的 extension chunks(~line 245),再呼叫 i_zval_ptr_dtor(~line 310)觸發 __destruct — 此時 BG(serialize_lock)==0 且 BG(unserialize).level>0。destructor 內的巢狀 unserialize('R:1500;') 重入外層 parser、反引用落入已釋放的 var_hash 條目 → heap-use-after-free 讀取。

行為面判定:合法的越界 R: 應回 boolean false;實測 R:1500 resolved to: integer(讀到殘留 zval)。原報告的 ASAN trace 與 /proc/self/mem handler 覆寫為 Linux 專屬步驟,本 lab(Windows build)無 ASAN,故以行為面確認。
6. 跨架構驗證 — ARM64 完全命中
目標:php:8.4.4-cli 官方 multi-arch image 部署於區網 ARM64 主機(NVIDIA 核心,6.17.0-1026-nvidia aarch64),docker compose 兩台 php -S,由攻擊端跨網段發動。

| 檢查 | x64 Windows | ARM64 Linux |
|---|---|---|
| 指紋 | x-powered-by: PHP/8.4.4 | arch=aarch64、php=8.4.4 |
| chain1 純 HTTP RCE | msi\user + RCE-ARM-OK | root + aarch64 + RCE-ARM-OK ✅ |
| chain3 解碼失敗 warning | ✅ | ✅ |
| 執行身分 | 一般用戶 | root(容器預設,影響更大) |
結論:Chain 1 / Chain 3 純 PHP 邏輯層(無 struct offset、無 heap groom 依賴),架構無關。chain3 的 CLI 級 __destruct → shell_exec 證據可在目標機以
python3 exploit_remote.py --target http://127.0.0.1:18099 --reader http://127.0.0.1:18098 --mode chain3 --docker phpup-arm-lab 補齊。
7. 工具化驗證 — phpup_tool.py 21/21 PASS
三條鏈整併為自包含工具(自有 runtime、port 等待迴圈、證據歸檔、SUMMARY.md):

工具化過程中修正了一個僅在反覆執行時才會暴露的穩定性缺陷:Chain 3 的 HTTP reader POST 會以 php handler 重寫 session 檔,若先於 CLI 證據步驟執行,會把 php_serialize 格式證據覆蓋掉,造成 __destruct 不觸發的假陰性。已改為 CLI proof 先行(SID A)、HTTP reader 測試用獨立 SID B。
8. 影響與緩解
影響:PHP 5.4.0 – 8.5.11 全數受影響(PHP 5/7 EOL 無修補);CVSS v4.0 9.2(廠商)。修補版本:8.2.35 / 8.3.36 / 8.4.27 / 8.5.12。
緩解(修補前):
session.upload_progress.enabled = Off ; 關閉主要攻擊面(Chain 1/3)
session.serialize_handler = php_serialize ; 全站一致,消除 Chain 3 前提
; 另於邊界限制 multipart POST(Chain 2 的 parser 面不因上項關閉而消失)
時間線
| 日期 | 事件 |
|---|---|
| 2026-10-20 | 漏洞細節提前洩漏;PHP 預告 10-21 14:00 UTC 緊急修補 |
| 2026-10-21 | php-up-2026 bundle 靜態驗證(行號/offset/ELF delta);ikow/php2shell-poc 三鏈本地落地 |
| 2026-10-22 | 工具化(21/21 PASS)、Chain 3 升級為真實 RCE、ARM64 跨主機驗證命中 |
參考
- jackalsyn.com — PHP-UP-2026(php-up-2026.tar.gz bundle)
- qideng.substack.com — php2shell: Dissecting PHP's Critical Session Upload Progress Vulnerabilities(Qi Deng)
- github.com/ikow/php2shell-poc — 驗證 POC 與 docker lab
- php/php-src PR #22113 —
use_only_cookiesguard(fix)
授權聲明:全部測試於自有 lab 與明示授權的區網主機執行;僅供防禦性研究用途。
© 2026 Gertech 耘鼎科技|本報告僅供授權之資安研究與防禦用途,未經許可不得外流或作為攻擊用途。