php2shell-audit 實戰:Windows 用 exe、Linux 用 py,十分鐘摸清 PHP 那兩個 RCE 有沒有打到你
這篇不繞彎子,全部是能直接敲的東西:裝工具、跑本機、掃目標、讀結論、判斷要不要打補丁,一條龍。十分鐘能查完。
先說清前提:CVE-2026-109618 和 CVE-2026-109619 是 PHP 引擎本身的未授權 RCE,官方修復版本是 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12。評分有兩套口徑——PHP 官方用 CVSS 4.0 打 9.2,NVD 用 CVSS 3.1 打 9.8,標題裏的 9.8 是後者,兩個都指同一對漏洞,別以爲是兩回事。
工具在這兒:viviopenclaw-code/php2shell-audit,Apache-2.0,今天剛發的。它做兩件事:查本機裝了什麼 PHP、掃遠端暴不暴露版本。它不發攻擊流量、不寫盤、也不改任何配置,這一點我在源碼裏核過,遠程模式真的只發一個 HEAD 請求讀響應頭。
一、裝:兩條路,看你在哪個系統
Linux / macOS 走 Python 版,零依賴,標準庫就夠,不用 pip 裝任何東西:
git clone https://github.com/viviopenclaw-code/php2shell-audit.git
cd php2shell-audit
chmod +x php2shell_audit.py
python3 php2shell_audit.py --local
Windows 直接下 exe,不用裝 Python。在 release 頁面拿 php2shell-audit.exe:
certutil -hashfile php2shell-audit.exe SHA256
php2shell-audit.exe --local
exe 是單文件控制檯程序,Win10/11 x64 可用,沒有代碼簽名,所以第一次跑 SmartScreen 會攔。先跑 certutil 算一下哈希,跟同一個 release 裏的 SHA256SUMS.txt 對一遍——這一步別省,網上流傳的“破解版”“綠色版”一律不要碰。
兩個版本的參數完全一致,教程後面只寫一套,Windows 把 python3 php2shell_audit.py 換成 php2shell-audit.exe 就行。
二、跑本機:–local 到底查了什麼
python3 php2shell_audit.py --local
它會做四件事,一件不落:
- 找 PHP:
/usr/bin/php*、/usr/sbin/php-fpm*、/opt/*、/usr/lib/php/*/sapi/*,Homebrew 也算。Windows 上查C:\php、XAMPP、WAMP、Laragon、Chocolatey、Scoop、Program Files 下的 PHP。 - 讀版本:跑
php -v拿 banner,跟 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 比。 - 查配置:解析 php.ini 裏那幾條臨時緩解指令。
- 列進程:把打完補丁必須重啓的 worker 全列出來。
我在一臺沒裝 PHP 的機器上跑了一遍,輸出是這樣的:
php2shell-audit 1.1.0 - local audit
Host : admin
System : Linux 7.0.0-30-generic (x86_64)
------------------------------------------------------------------------
[UNKNOWN] No PHP installation detected.
------------------------------------------------------------------------
php.ini mitigation directives
[WARNING] session.upload_progress.enabled = (unset) (recommended 0)
[WARNING] fatal_error_backtraces = (unset) (recommended 0)
[WARNING] expose_php = (unset) (recommended 0)
------------------------------------------------------------------------
Running services that must be restarted after patching
[INFO] apache2 - 3 process(es)
------------------------------------------------------------------------
[INFO] No PHP binary, FPM daemon or Apache PHP module was found on this host.
------------------------------------------------------------------------
Remediation
[UNKNOWN] No PHP install was audited, so nothing can be confirmed safe.
注意最後那行:沒裝 PHP 它報 UNKNOWN,不報 PASS。這個設計我很認可——沒查到東西和查了沒問題,是兩碼事,混成一個“綠色勾”就是耍流氓。
順帶說,它連 expose_php 都提示了。這條跟漏洞本身沒關係,但直接決定了遠程掃描能不能讀到你的版本號——關掉它,攻擊者的指紋階段就瞎一半。當然這不叫修復,叫減少信息暴露。
三、參數就這幾個
| 參數 | 作用 | 默認 |
|---|---|---|
--local / --remote | 二選一,決定查本機還是查目標 | 必填 |
--file targets.txt | 批量目標,一行一個,# 開頭爲註釋 | 無 |
--workers N | 遠程併發線程數 | 10 |
--json report.json | 輸出完整 JSON,- 打到 stdout | 無 |
--timeout N | 單請求超時(秒) | 8 |
--insecure | 跳過 TLS 校驗,僅用於指紋 | 關閉 |
退出碼:能直接當 CI 卡點
| 退出碼 | 含義 |
|---|---|
0 | 沒發現受影響版本 |
2 | 至少發現一個受影響版本 |
1 | 工具自身出錯(比如 JSON 寫不出來) |
退出碼 2 這一條是重點。意味着你可以把它塞進流水線,構建階段先掃一遍,命中就 fail,別等上線後靠人肉發現。
四、掃遠程:–remote 怎麼用
# 单个目标
python3 php2shell_audit.py --remote example.com
# 批量,20 个并发,出 JSON
python3 php2shell_audit.py --remote --file targets.txt --workers 20 --json out.json
# 也可以直接写在命令行里
python3 php2shell_audit.py --remote https://a.example 10.0.0.5
targets.txt 怎麼寫
# production fleet
example.com
https://shop.example.net
10.0.0.5:8080
# 開頭是註釋,一行一個,倉庫裏有 targets.example.txt 可以直接抄。
它到底發了什麼
每個目標只發一個 HEAD 請求,只有服務器拒絕 HEAD 時纔會退化成一次 GET。然後讀 Server:、X-Powered-By: 和 Set-Cookie,從裏面摳出 PHP/x.y.z。
不發 multipart、不發超大 body、不觸發 OOM、不碰 use-after-free。它在源碼註釋裏把這條當成設計約束寫死了,不是隨口承諾。
代價也得說清楚:遠程模式證明不了可利用性,它只能報告服務器自願吐出來的信息。隱藏版本號的目標,結果永遠是 UNKNOWN。
五、怎麼讀結論
[VULNERABLE] https://shop.example.net
http : 200
server : Apache/2.4.62 (Debian)
powered : PHP/8.4.16
php : 8.4.16
verdict : PHP 8.4.16 < fixed 8.4.27
- [VULNERABLE] 版本讀到了,且低於修復版本。
- [PASS] 版本讀到了,且大於等於修復版本。
- [WARNING] 臨時緩解指令沒按建議設置。
- [UNKNOWN] 證據不足。不是安全,是不知道。
這個區分是整個工具最值錢的地方。絕大多數掃描器只會給你紅黃綠三色,然後你對着滿屏綠色以爲天下太平。UNKNOWN 必須當成待辦,要麼去關掉版本隱藏、要麼進容器裏跑 --local、要麼登錄機器看。
六、四個坑,一個比一個實在
坑 1:UNKNOWN ≠ 安全
藏了版本號的主機,永遠只會報 UNKNOWN。真要確認,只能在主機上跑 --local。
坑 2:發行版 backport 會誤報
Debian / Ubuntu / RHEL / Alpine 經常把安全補丁打進去但不跳版本號。結果就是補過了的機器,照樣顯示 [VULNERABLE]。
所以看到紅字先別慌,先查發行版 changelog:
apt changelog php8.4
rpm -q --changelog php-8.4 | head -50
這一步不做就升級,等於白折騰一輪;做了還誤判,那就是真需要升級。
坑 3:一個 HEAD 也是請求
它會進對方日誌,會被 WAF 記下來。沒拿到書面授權就掃,性質上是未授權探測——CFAA、英國 CMA 1990 這類法條都管得着。
掃自己公司的資產也得先拿授權,尤其是跨部門、跨子公司、雲上多賬號的場景。別覺得“我只是看個響應頭”就不算。
坑 4:--local 只查本機
PHP 跑在容器裏,就進容器裏跑,或者在宿主機上用 --remote 打容器映射出來的端口。宿主機上跑 --local 查不到容器裏的 PHP,會給你一個 UNKNOWN——那個 UNKNOWN 是真的不知道,不是容器安全。
七、查出來有問題,然後呢
打補丁是唯一完整修復。其他全是止血。
# Debian / Ubuntu
sudo apt update && sudo apt install --only-upgrade php
# RHEL / Fedora
sudo dnf upgrade 'php*'
# Alpine
sudo apk upgrade php83 php84
升級版本對照:8.2 分支到 8.2.35,8.3 到 8.3.36,8.4 到 8.4.27,8.5 到 8.5.12。8.1 及更早已停更,只有源碼級 backport。
上不了補丁就先做這幾條,然後重啓所有 worker
; CVE-2026-109619
session.upload_progress.enabled = 0
; CVE-2026-109618
memory_limit = -1
; 仅 PHP 8.5
fatal_error_backtraces = 0
session.upload_progress.enabled=0要寫在 php.ini、FPM pool 或服務器配置裏,必須早於請求解析生效。ini_set() 和 .user.ini 都太晚。memory_limit=-1要用php_admin_value這類應用改不動的級別設,同時保證沒有 pool / 目錄 / 應用把它改回有限值。- 千萬別用 RLIMIT_AS 或 RLIMIT_DATA——那倆會讓 mmap 失敗,正好重新走進那條危險的 longjmp 路徑。
- 改完必須重啓每一個 worker:
systemctl restart php*-fpm apache2 nginx。不重啓等於沒改。
這四條沒有一條能讓你變安全,官方自己都說得很明白。它們的價值只有一個:在你打補丁之前,把門先虛掩上。
八、完整流程串一遍
- 本機先自查:
--local,三五秒出結果,看版本和配置。 - 遠端批量掃:整理 targets.txt,
--remote --file併發跑,出 JSON。 - 分類處理:VULNERABLE 排期升級,WARNING 補配置,UNKNOWN 逐個查清——UNKNOWN 不是結案,是待辦。
- 升級 + 重啓 worker:打補丁,然後重啓所有 FPM / Apache / nginx。
- 復掃驗證:再跑一遍,確認全綠。注意 backport 誤報,必要時看 changelog。
- 接進流水線:退出碼 2 直接 fail 構建,下次就不用靠人記着。
整個過程十分鐘夠了,真正花時間的其實是第三步——把 UNKNOWN 一條條查清。但這一步不能省,因爲跳過它,你拿到的就是一張漂亮的、假的、全綠報表。
掃描器的價值不在給你綠燈,在逼你承認哪些地方你其實不知道。
項目地址再放一次:github.com/viviopenclaw-code/php2shell-audit。用之前把 DISCLAIMER.md 讀一遍,中文的,寫得很實在。
最後一句提醒:這工具今天是 1.1.0 首個公開版本,一星零 fork,代碼量不大,你可以自己通讀一遍 php2shell_audit.py——跑在服務器上的東西,看一眼源碼不算過分要求,尤其是這種要拿管理員權限去讀 php.ini 和進程列表的。