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 请求读响应头。

php2shell-audit 终端运行效果

一、装:两条路,看你在哪个系统

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 就行。

Linux 与 Windows 两种安装方式

二、跑本机:–local 到底查了什么

python3 php2shell_audit.py --local

它会做四件事,一件不落:

  1. 找 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。
  2. 读版本:跑 php -v 拿 banner,跟 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 比。
  3. 查配置:解析 php.ini 里那几条临时缓解指令。
  4. 列进程:把打完补丁必须重启的 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 都提示了。这条跟漏洞本身没关系,但直接决定了远程扫描能不能读到你的版本号——关掉它,攻击者的指纹阶段就瞎一半。当然这不叫修复,叫减少信息暴露。

三、参数就这几个

php2shell-audit 参数一览
参数作用默认
--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、要么登录机器看。

六、四个坑,一个比一个实在

使用 php2shell-audit 的四个注意事项

坑 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。不重启等于没改。

这四条没有一条能让你变安全,官方自己都说得很明白。它们的价值只有一个:在你打补丁之前,把门先虚掩上。

八、完整流程串一遍

  1. 本机先自查:--local,三五秒出结果,看版本和配置。
  2. 远端批量扫:整理 targets.txt,--remote --file 并发跑,出 JSON。
  3. 分类处理:VULNERABLE 排期升级,WARNING 补配置,UNKNOWN 逐个查清——UNKNOWN 不是结案,是待办。
  4. 升级 + 重启 worker:打补丁,然后重启所有 FPM / Apache / nginx。
  5. 复扫验证:再跑一遍,确认全绿。注意 backport 误报,必要时看 changelog。
  6. 接进流水线:退出码 2 直接 fail 构建,下次就不用靠人记着。

整个过程十分钟够了,真正花时间的其实是第三步——把 UNKNOWN 一条条查清。但这一步不能省,因为跳过它,你拿到的就是一张漂亮的、假的、全绿报表。

扫描器的价值不在给你绿灯,在逼你承认哪些地方你其实不知道。

项目地址再放一次:github.com/viviopenclaw-code/php2shell-audit。用之前把 DISCLAIMER.md 读一遍,中文的,写得很实在。

最后一句提醒:这工具今天是 1.1.0 首个公开版本,一星零 fork,代码量不大,你可以自己通读一遍 php2shell_audit.py——跑在服务器上的东西,看一眼源码不算过分要求,尤其是这种要拿管理员权限去读 php.ini 和进程列表的。

暂无介绍....

延伸阅读:

php2shell-audit 实战:Windows 用 exe、Linux 用 py,十分钟摸清 PHP 那两个 RCE 有没有打到你

只读、不发攻击流量、一个文件零依赖。本机自查、远端批量、三种结论怎么读、四个必踩的坑,外加升级后必须重启 worker ...

itadol5j
2026年10月22日
Java 出了个 9.1 分漏洞 CVE-2026-91403,可我照着 Oracle 公告查了一圈,发现两个大坑

它不在这一季的 CPU 里,而那个 9.1 分里也没有 RCE 这一项。把 Oracle 的公告体系、CVSS 向量、供...

itadol5j
2026年10月22日
PHP2Shell 炸了:CVSS 9.2,公开前一天就有人在打,而这两个洞在代码里躺了十年

CVE-2026-109618 和 CVE-2026-109619,PHP 5.4 到 8.5 全线受影响,未授权 RC...

itadol5j
2026年10月22日
76.2 万条微信全导出来喂给 AI:最难的压根不是 AI

76.2 万条消息、3675 万字、十年记录。导出、清洗、分块、向量化、混合检索,五步管道全流程,外加五个必踩的坑。还有...

itadol5j
2026年10月20日
opencode-models-discovery 2.0.0 发布:配置形状全换,OpenCode v2 正式转正

九种元数据格式、原生 /connect 凭证、宿主托管缓存。从 v1 迁到 v2 的配置字段对照和四条检查清单都在这里。

itadol5j
2026年10月20日