RCE 零日攻擊?
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] 。
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 鏈結成三個階段。
圖 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 重構:
- # Reconstructed HTTP request signature for the dominant PHP2Shell
- # scanning kit, built from indicators listed in the primary report.
- POST /lib/phpunit/phpunit/Util/PHP/eval-stdin.php HTTP/1.1 # fixed probe path reused across hosts
- Host: <target> # targets addressed by raw IP, not hostname
- User-Agent: php2shell-109618 # self-identifying UA hard-coded into the public PoC
- Content-Type: multipart/form-data; boundary=B7kQpoc618 # fixed multipart boundary, usable as a detection pivot
- Content-Length: <n>
- --B7kQpoc618
- Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"
- # triggers the upload-progress code path under test
- <crafted progress value>
- --B7kQpoc618--
程式碼 1 — Exploit Request Signature Indicators
成功 exploitation 是透過一個短 shell command 進行操作驗證,其輸出被寫入固定路徑然後由 tooling 檢查:
- # Verification step used by the exploit tooling once code execution
- # is believed to have succeeded (per the primary report's indicator list).
- id > /out/p1 # run 'id' as the web-server user, redirect to a proof file
- # /out/p1 or /tmp/p1 is then read back by the operator's harness
- # 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] :
- # Conceptual reconstruction of the dual-port listener behaviour
- # described for the exposed directory in the primary report.
- import socket, threading, logging
- def serve(port):
- # one listener instance per port; both log inbound hits identically
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.bind(("0.0.0.0", port)) # bind the listener to the given port
- s.listen(100) # queue depth for inbound scan/exploit traffic
- while True:
- conn, addr = s.accept() # accept a connecting scanner or exploit client
- logging.info("hit %s:%s", *addr) # record source IP and port for later triage
- conn.close() # connection is only used to record the attempt
- for p in (80, 8888): # the two ports named in the primary report
- 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 運行,而非單次爆發:
- # Conceptual reconstruction of a fingerprinting pass, based on the
- # behaviour attributed to the proxy-pool scanner named in the report.
- def fingerprint(target, proxy_pool):
- for proxy in proxy_pool: # rotate egress IP per attempt
- resp = send_probe(target, via=proxy) # low-impact request, not full exploit payload
- if looks_like_php(resp): # check headers / error signature for PHP SAPI
- version = infer_php_version(resp) # best-effort version inference
- record(target, version, proxy) # persist candidate for later exploitation pass
程式碼 4 — fingerprinting script logic
最後,報告指向的共同 interim mitigation — 在引擎升級之前關閉 upload-progress feature — 被捕捉在單一 configuration directive 中:
- ; php.ini directive that disables the session upload-progress
- ; mechanism exploited by the CVE-2026-109619 chain. This narrows
- ; exposure for that specific path only; it is not a substitute
- ; for upgrading to a patched PHP release.
- 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 的狹窄臨時措施。
參考資料
- PHP2Shell (CVE-2026-109618/9) Havoc: UNC actors leveraging Public Exploit Code Against Public, Business And Educational Infrastructure
- Hackers Launch Mass Scanning Campaigns Using Public PHP2Shell Exploit Scripts
- Hackers Weaponize Public PHP2Shell Exploit Code to Target Universities and Business Websites