1. 簡介

2026 年 10 月 21 日,PHP 專案發布了緊急版本(8.5.12、8.4.27、8.3.36、8.2.35),修補了 Zend request 生命週期中兩個嚴重的 記憶體安全缺陷 ,統稱為 PHP2Shell :CVE-2026-109618 是一種 損壞路徑(corruption path) ,當在 out-of-memory 中斷後針對部分初始化的記憶體執行請求清理時觸發;CVE-2026-109619 則是一個 stale-global-state 缺陷 ,其中 worker-shared state 的存活時間超過其參考的 per-request memory,讓同一個 worker 上的後續請求可以 dereference 已釋放的記憶體。這兩個缺陷都位於應用程式碼之下、引擎內部,因此修補框架或內容管理層並無法修復暴露問題;只有升級引擎才能解決。

幾天內,一個獨立的 adversary-infrastructure 研究團隊 Akatsuki Legion 發表了一份技術說明,追蹤多個*尚未歸類的威脅群組(UNC clusters) 如何將公開的 proof-of-concept code 整合到針對一般商業和教育 PHP 部署的主動掃描和入侵操作中 [1] 。

RCE 零日攻擊?PHP2Shell 揭開 Zend 記憶體安全缺陷面紗! | 資訊安全新聞

2. 根本原因解析

報告將這兩個 vulnerabilities 歸類為單一底層失敗模式的實例:PHP 的 request teardown path 假設它即將釋放的結構已完全建構,而攻擊者若能強制在建構中途異常退出,就能使這個假設不成立 [1] 。CVE-2026-109618 透過一般的 multipart form parsing 達到此條件 — 巢狀 array syntax 會迫使 PHP 丟棄字串值並將其重建為 array,這可能在重建開始時耗盡記憶體,導致內部 array slot 在填入之前就被標記為 live。CVE-2026-109619 則是透過 session upload-progress mechanism 達到:progress-tracking hash table 以 raw pointer 快取,如果 fatal interruption 或稍後的 shutdown routine 在原始 table 已被銷毀後重新產生該狀態,則由同一個 worker 處理的下一個請求會繼承 dangling reference。

報告中 exploitation path 將這些 primitives 鏈結成三個階段。

sequenceDiagram participant A as Attacker client participant W as PHP worker (mod_php / FPM) participant H as Heap allocator (ZendMM) A->>W: POST with PHP_SESSION_UPLOAD_PROGRESS field (session.upload_progress enabled) W->>H: Allocate progress HashTable, cache raw pointer in session vars Note over W,H: Progress update cycle re-reads session data W->>H: zend_hash_update() destroys prior data tree (zval_ptr_dtor) H-->>W: Memory block freed, pointer now dangling A->>W: Heap-spray follow-up requests (nested array fields) W->>H: Allocator reclaims freed bucket for attacker-controlled bytes Note over W,H: Dangling pointer now resolves into forged structure A->>W: Trigger write through stale cached pointer W->>H: Forged HashTable destructor pointer set to zif_system Note over W: Session shutdown invokes destructor W->>W: zif_system(command) executes with web-server privilege W-->>A: Command output captured via proof file / response leak

圖 1 — Session-Upload-Progress Chain Exploitation Sequence

3. 觀察到的 Exploitation Tooling 和 Infrastructure

報告將特定的 exploit-chain engineering 歸因於一位具名的個人研究者,並指出 AI-assisted proof-of-concept variants 在 coordinated release window 關閉後幾乎立即開始在公開 code-hosting platforms 上流傳 [1] 。報告中引用的 Telemetry 將第一個標記為 PHP2Shell 的 scanning request 定位在 10 月 21 日 UTC 03:59,大約比預定的 14:00 UTC disclosure 早九小時,顯示至少有一位 operator 在公開發布之前就擁有 working exploit logic [2] 。

在追蹤的活動中觀察到的一個重複出現的 request signature 使用了固定的 user-agent string 並針對固定的 relative path,以下根據報告列出的 indicators 重構:

  1. # Reconstructed HTTP request signature for the dominant PHP2Shell
  2. # scanning kit, built from indicators listed in the primary report.
  3. POST /lib/phpunit/phpunit/Util/PHP/eval-stdin.php HTTP/1.1 # fixed probe path reused across hosts
  4. Host: <target> # targets addressed by raw IP, not hostname
  5. User-Agent: php2shell-109618 # self-identifying UA hard-coded into the public PoC
  6. Content-Type: multipart/form-data; boundary=B7kQpoc618 # fixed multipart boundary, usable as a detection pivot
  7. Content-Length: <n>
  8. --B7kQpoc618
  9. Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
  10. # triggers the upload-progress code path under test
  11. <crafted progress value>
  12. --B7kQpoc618--

程式碼 1 — Exploit Request Signature Indicators

成功 exploitation 是透過一個短 shell command 進行操作驗證,其輸出被寫入固定路徑然後由 tooling 檢查:

  1. # Verification step used by the exploit tooling once code execution
  2. # is believed to have succeeded (per the primary report's indicator list).
  3. id > /out/p1 # run 'id' as the web-server user, redirect to a proof file
  4. # /out/p1 or /tmp/p1 is then read back by the operator's harness
  5. # to confirm the destructor overwrite actually reached zif_system.

程式碼 2 — exploitation proof-of-success command。

除了直接 exploitation 之外,報告還描述了一個 exposed operator directory,託管 listener processes,同時在兩個 ports 上記錄 inbound exploit traffic,暗示 shared tooling 在多個 operating cluster 之間重用 [1] :

  1. # Conceptual reconstruction of the dual-port listener behaviour
  2. # described for the exposed directory in the primary report.
  3. import socket, threading, logging
  4. def serve(port):
  5. # one listener instance per port; both log inbound hits identically
  6. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  7. s.bind(("0.0.0.0", port)) # bind the listener to the given port
  8. s.listen(100) # queue depth for inbound scan/exploit traffic
  9. while True:
  10. conn, addr = s.accept() # accept a connecting scanner or exploit client
  11. logging.info("hit %s:%s", *addr) # record source IP and port for later triage
  12. conn.close() # connection is only used to record the attempt
  13. for p in (80, 8888): # the two ports named in the primary report
  14. threading.Thread(target=serve, args=(p,), daemon=True).start()

程式碼 3 — dual-port exploit-traffic listener

報告還列出了一個 fingerprinting utility,用於在送出 exploit attempt 之前透過 rotating proxy pool 對可達的 PHP endpoints 進行 triage,而更廣泛的 exploitation tempo — 在過濾已修補版本後的三十天觀察窗口內記錄了 274 個 distinct events [3] — 表明這是作為 persistent reconnaissance pass 運行,而非單次爆發:

  1. # Conceptual reconstruction of a fingerprinting pass, based on the
  2. # behaviour attributed to the proxy-pool scanner named in the report.
  3. def fingerprint(target, proxy_pool):
  4. for proxy in proxy_pool: # rotate egress IP per attempt
  5. resp = send_probe(target, via=proxy) # low-impact request, not full exploit payload
  6. if looks_like_php(resp): # check headers / error signature for PHP SAPI
  7. version = infer_php_version(resp) # best-effort version inference
  8. record(target, version, proxy) # persist candidate for later exploitation pass

程式碼 4 — fingerprinting script logic

最後,報告指向的共同 interim mitigation — 在引擎升級之前關閉 upload-progress feature — 被捕捉在單一 configuration directive 中:

  1. ; php.ini directive that disables the session upload-progress
  2. ; mechanism exploited by the CVE-2026-109619 chain. This narrows
  3. ; exposure for that specific path only; it is not a substitute
  4. ; for upgrading to a patched PHP release.
  5. session.upload_progress.enabled = 0

程式碼 5 — interim mitigation directive

4. Indicators 和 Detection Surface

除了程式碼 1 中的 request signature 之外,報告還列出了一組與其所追蹤 campaign 相關的主機和網路層級 indicators,總結如下。

類型 Indicator 角色
User-Agent php2shell-109618 在主導公開 proof-of-concept 中 hard-coded 的自我識別字串
URI path /lib/phpunit/phpunit/Util/PHP/eval-stdin.php 在觀察到的 exploit POST requests 中重用的 probe path
Form field PHP_SESSION_UPLOAD_PROGRESS 啟用 vulnerable upload-progress path 的 multipart field
Proof file /out/p1, /tmp/p1 用於確認 code execution 成功的固定路徑檢查
Open directory Listener scripts on ports 80 and 8888 調查期間識別的 shared exploit-traffic logging infrastructure

報告將整體情況描繪為機會主義的 address-space scanning,並疊加了至少一個已確認的 botnet-recruitment payload,而非單一 coordinated actor,並建議將任何命中此 signature set 的事件與 PHP-FPM error logs 和來自 PHP worker processes 的意外 outbound connections 進行關聯,然後再將請求視為 confirmed exploitation [1] 。

5. 結論

報告的核心技術貢獻在於證明 coordinated disclosure 和 durable patch adoption 之間的差距是在數小時而非數週內被利用的:公開 exploit code、AI-assisted PoC variants 和 reused operator infrastructure 幾乎立即匯聚於一般用途的 PHP deployments。由於兩個 root causes 都位於 engine-level request-lifecycle handling 內部,防禦者不能僅依賴 application-layer filtering;完整的 remediation 仍然是升級到 fixed release 的引擎,而程式碼 5 中的 upload-progress directive 僅作為 narrowing one exploitation path 的狹窄臨時措施。