PHP2Shell三更:哪些打法能落地?附带防御规则"

admin 2026-10-24 15:01:21 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

PHP2Shell 三更:哪些打法能落地?附带防御规则

原创

messfree
messfree

MessFreeSecurity

2026年10月22日 12:15
北京

去阅读

一个只写了 <?php echo \"OK\"; ?> 的页面,没有登录、没有数据库、没有 session_start(),也可能被人用几个 POST 请求打成 shell。你写的代码没问题,不代表服务器没问题。

这不是段子。10 月 21 日,PHP 官方修了 CVE-2026-109618 和 CVE-2026-109619,CVSS 9.2;公开 PoC 在 lab 里把从 multipart 表单到命令执行的链路走通了,全程不用上传 webshell。

但先别急着转发“PHP 全完了”。CVSS 9.2 的分数很吓人,但分数不等于实战成功率。我的判断很直接:洞是真的,但没那么神;利用链也是真的,但没那么好复现。这条链很挑环境,地址泄漏也还没做稳。更值得看的是,洞在 PHP 引擎处理请求体和内存的那一层,不在框架里;Web 安全的战场,正在往下沉。

这条链很挑环境

公开链一句话能概括:换 worker、漏堆地址、漏 libphp 基址、伪造 HashTable 调 zif_system。全程只用 multipart POST,单次命令大约 14 字节。细节前两篇讲过,这里不重复。

它挑什么 公开链的情况 对防守方意味着什么
运行方式 Apache prefork + mod\_php PHP-FPM 的 worker 生命周期不同,不能直接套这条链;但官方通告确认过 FPM 侧另有路径
display_errors 完整链需要 On;Off 下只完成第一阶段堆泄漏 生产多数是 Off;但 Off 不是安全边界:第二阶段的 libphp 泄漏和命令执行还没接上
内存和请求体 lab 是 memory_limit=8M、请求体上限 64M 默认 128M 内存加 8M 请求体,按 lab 的形状打不进来;把上传配置放得很宽的站点要小心
worker 数量 单 worker A/B 两个请求要落在同一个进程;仓库没给出多 worker 的同等验证
构建 PHP 8.4.26 的特定构建 偏移跟版本、编译选项有关,换个发行版要重新校准;公开 exp 不是通用一键工具

所以,看到公开 PoC 就以为“所有 PHP 站点一键 RCE”,不对;觉得“默认配置打不了”就拖着不升,也不对。这张表说明的是,公开链现在更像一个 lab 复现,不是武器化 EXP。

漏地址这一步还没做稳

更麻烦的是,泄漏地址还没泛化。公开 exp 依赖 A/B 请求的字段顺序、大小、worker 历史,以及 Zend MM 的页分配;chunk 偏移和 libphp 偏移都是按 PHP 8.4.26 这套构建和请求形状校准的,远程模式默认用 lab 偏移,换应用、换构建就要重测。

display_errors=On 时,lab 里 ASLR 开启大约 9/10,失败靠重试;Off 时只完成 stage-1 堆泄漏,stage-2 读 libphp 和 stage-3 还没接上。要读真正的 HashTable,得让泄漏窗口和真实结构落在同一页,这需要三段式堆整形,不是调一个参数能解决的。

所以,窄的根因不只是配置,而是泄漏还绑在具体堆布局上。公开 exp 的窄,不是因为防守方做对了什么,而是因为攻击方还没把地址泄漏做成通用原语。看到公开 exp 别以为所有站点都能一键打;但也不能因为自己的环境不一样,就赌它打不中。

先看 mod\_php 老部署

最该先看 Apache mod\_php 老部署:prefork、单 worker、display_errors 开着、上传配置放得很宽。FPM 不能因为公开链没直接套上就放心;PHP 8.1 及更早已经 EOL,是否有回移要问供应商。

入口、主机、配置

  • 入口:请求体里的 PHP_SESSION_UPLOAD_PROGRESS、响应里异常长的 PHPSESSID、PHP 在 POST 阶段的 OOM 日志。

  • 主机:www-data 下出现 sh、bash、whoami、uname,父进程是 apache2、httpd 或 php-fpm。

  • 配置:不用上传进度就关 session.upload_progress;请求体和上传大小按业务收紧;ASLR 不要关;display_errors 关掉,log_errors 留着。

先做这几件事

  1. 盘点 PHP 版本、SAPI 和 MPM,升级到 8.5.12、8.4.27、8.3.36、8.2.35 或供应商回移版,重启 Apache/FPM。
  2. 不用上传进度条就关掉 session.upload_progress;要用,就限制请求体和上传大小。
  3. 上入口和主机告警,把请求、响应、PHP 错误日志和进程树关联起来。不要把 display_errors=Off 或一条 WAF 规则当成修复。

最后聊两句:底层、AI 和一场能力秀

这次漏洞出在 PHP 引擎处理请求体和内存的那一层。公开链要拼堆布局、漏地址、伪造 HashTable,都是二进制利用的手艺。漏洞挖掘正在往底层走:语言运行时、内存分配器、JIT、浏览器引擎、内核,会越来越多地成为入口。AI 把“找 bug”的成本打下来,但没把利用、修复、推动升级的成本打下来;同一套能力,攻击者也能用。

这次事件的背景也很有意思。公开报道里,Calif 用 OpenAI 的 GPT-5.6 Sol 发现了其中一个缺陷,Wiz 独立发现并报告;从发现、协调、修复到对外发布,OpenAI 几乎全程在场,但不是唯一的发现者。它牵头协调了约两个月,对外称为“防御者之窗”,还发布了版本检查工具、Codex 技能和 10 亿美元计划。

这些动作本身没问题,漏洞是真的,补丁也是真的。但传播节奏很像一次精心安排的能力展示:发现、协调、修复、博文、基金、工具,全挤在同一周;Wiz 的“影响 70% 互联网”标题很抓眼,统计口径却没解释清楚;披露还因为 CVE 信息提前流传、Chainguard 提前发布修复镜像而被迫提前,PHP 公开指责对方破坏协调披露。

我不认为漏洞是编的,也不认为补丁是演的。但说这次事件完全没有品牌叙事的考虑,我是不信的。说得直白一点,我不排除这是一次借真实漏洞做的能力营销:漏洞是真的,补丁是真的,但叙事被用到了极致。 我的立场很简单:这个洞值得修,但不值得封神。它的技术含量在运行时内存管理,不在“一键 RCE”;CVSS 9.2 是严重性评分,“七成互联网”是传播语言,都不是实战成功率。技术事实和公关叙事可以同时为真。判断它是不是纯炒作,不看发布会,看后续:方法论能不能复现,修复能不能持续,小站点有没有真的被覆盖。

还有一件事,这几天的国内安全公众号,我看到的讨论大多没提:ThinkPHP、Discuz(DZ)这些国内部署量很大的 PHP 应用,只要跑在未修复的 PHP 上,同样在这条链的覆盖范围里。漏洞不在框架里,但框架跑在 PHP 上;升级 ThinkPHP 或 Discuz,不等于升级了 PHP。可公开传播里,不少厂商公众号更多在转 OpenAI、GPT-5.6 和“防御者之窗”,国内的应用栈反而没人提。节奏是别人带的,风险是自己扛的。

洞是真的,但没那么神;补丁不能拖。先升级,再谈规则。


附:规则和配置怎么落地

公开规则不少,但不是每条都适合直接上生产。下面按“先看规则、再上网络侧、再补主机侧、最后调配置”的顺序整理,规则都先当告警用。

1. 公开规则先筛一遍

来源 能用的 要注意的
Corelight / Suricata 3000650 请求体里出现 PHP_SESSION_UPLOAD_PROGRESS,覆盖面最广 没限制 Content-Type、没阈值,误报高;只告警
Corelight 3000651 / 3000652 PHP 版本头、phpinfo 的版本发现 这是资产发现,不是攻击检测;发行版回移会误判
Snort 67360 multipart + 标记,5 秒 10 次 单请求、慢速会漏;正常上传进度条会误报
Snort 67375 响应 Cookie 带 NUL 强特征;写死 PHPSESSID,换 cookie 名或编码会漏
Snort 67359 序列化整数碰撞样本 只覆盖特定样本,换组整数就绕过
SigmaHQ 通用 webshell 规则 父进程 web server + 子进程侦察工具 没覆盖 php-fpm 父进程和 sh/bash,需要补
ikow ModSecurity 9900100 标记阻断 用原始 body,Content-Type 大小写敏感;正常上传进度会误伤
ikow ModSecurity 9900104 / 9900105 OOM、session 异常 display_errors=Off 时响应里没有,只能查日志
ikow ModSecurity 9900106 封 chunked multipart 不建议直接上线,正常流式上传也会用
ikow OpenResty Lua 请求体标记 只扫前 64KB、Content-Type 区分大小写、临时文件只读第一段
Cloudflare 托管 WAF 托管规则 只公开 ID,表达式不可调;注意 body 检查上限

2. 网络侧:先上三条告警

第一条,请求体标记。 ModSecurity 用解析后的字段名,别直接匹配原始 body;Content-Type 要忽略大小写;先 pass,log。

# 仅告警。需要 SecRequestBodyAccess On;依赖 multipart 解析。 SecRule REQUEST_METHOD \"@streq POST\" \\   \"id:1001001,phase:2,pass,log,t:none,severity:'WARNING',msg:'PHP2Shell heuristic: upload-progress multipart field',tag:'php2shell-heuristic',chain\"   SecRule REQUEST_HEADERS:Content-Type \"@rx (?i)^multipart/form-data(?:[[:space:]]*;|$)\" \"t:none,chain\"   SecRule ARGS_POST_NAMES \"@streq PHP_SESSION_UPLOAD_PROGRESS\" \"t:none\"

Suricata 可以在 Corelight 3000650 的基础上加 multipart 和阈值,减少噪声:

alert http $EXTERNAL_NET any -> $HOME_NET any (msg:\"PHP2Shell upload progress marker\"; flow:established,to_server; http.method; content:\"POST\"; http.content_type; content:\"multipart/form-data\"; nocase; http.request_body; content:\"PHP_SESSION_UPLOAD_PROGRESS\"; fast_pattern; threshold: type limit, track by_src, count 1, seconds 30; classtype:web-application-attack; sid:1009201; rev:1;)

Snort 3 直接用社区规则 67360。正常上传进度条会误报,先 alert,不要直接 drop。

第二条,响应侧异常 Cookie。 Snort 67375 已经覆盖 PHPSESSID 加 NUL。Suricata 可以写:

alert http $HOME_NET any -> $EXTERNAL_NET any (msg:\"PHP2Shell encoded NUL in PHPSESSID response\"; flow:established,to_client; http.response_header; content:\"PHPSESSID=\"; fast_pattern; pcre:\"/PHPSESSID=[^;\\r\
]*%00%00/\"; classtype:web-application-attack; sid:1009202; rev:1;)

正常 PHPSESSID 很短;超过 1KB、含大量百分号编码、含 NUL,都值得看。不要为了“防泄漏”去剥离 Set-Cookie,会把业务会话打坏。

第三条,OOM 和 session 异常。 日志或响应里看:

  • Allowed memory size ... exhausted

  • Session ID is too long or contains illegal characters

  • Failed to read session data

  • Session object destruction failed

单独出现可能是业务问题,和上传进度字段、异常 Cookie、worker 崩溃一起看,才有判断价值。

另外两条补充:Corelight 3000651 / 3000652 用来盘版本和 phpinfo,不是攻击检测;Snort 67359 是特定样本特征,保留但不指望它覆盖所有变体。

3. 主机侧:盯进程树,不要只看告警

SigmaHQ 的通用规则抓父进程 web server、子进程 whoami/uname 等,但没覆盖 php-fpm 和 sh/bash。补一条:

title: PHP2Shell PHP Worker Spawns Shell Or Recon Utility id: 8f3c2a1e-7b64-4d2a-9c5e-1a2b3c4d5e6f status: experimental logsource:   product: linux   category: process_creation detection:   parent_web:     ParentImage|contains:       - '/php-fpm'       - '/apache2'       - '/httpd'   child_shell:     Image|endswith:       - '/sh'       - '/bash'       - '/dash'   child_recon:     Image|endswith:       - '/whoami'       - '/id'       - '/uname'       - '/cat'       - '/curl'       - '/wget'       - '/nc'   condition: parent_web and (child_shell or child_recon) falsepositives:   - Web applications that legitimately shell out to system tools level: high

业务本来就会调系统命令的站点会吵;和请求日志、异常 Cookie、OOM 日志关联。没有进程创建日志(auditd、eBPF、Falco)这条规则就是摆设。

文件和外连也要看:公开链当前版本不要求写目标文件,但命令执行后可能落脚本、写 proof、下载二阶段。盯 www-data 对 /tmp、/dev/shm、webroot 的异常写入,以及出站连接。容器环境可以再收紧:只读根文件系统、最小镜像、去掉不必要的 shell 和包管理器。

4. OpenResty:别只扫前 64KB

公开 Lua 只读前 64KB,Content-Type 区分大小写,临时文件只读第一段。建议改成下面这个形状,先 log-only:

local marker = 'name=\"PHP_SESSION_UPLOAD_PROGRESS\"' local limit = 16 * 1024 * 1024  local function is_multipart()     local ct = ngx.req.get_headers()[\"content-type\"]     return type(ct) == \"string\"         and ct:lower():find(\"multipart/form-data\", 1, true) ~= nil end  local function scan_body()     ngx.req.read_body()     local body = ngx.req.get_body_data()     if body then         return body:sub(1, limit):find(marker, 1, true) ~= nil, #body > limit     end     local path = ngx.req.get_body_file()     if not path then return false, true end     local fh = io.open(path, \"rb\")     if not fh then return false, true end     local read, tail, hit = 0, \"\", false     while read < limit do         local part = fh:read(math.min(8192, limit - read))         if not part then break end         read = read + #part         local window = tail .. part         if window:find(marker, 1, true) then hit = true break end         tail = window:sub(-(#marker - 1))     end     local incomplete = (not hit) and (read == limit and fh:read(1) ~= nil)     fh:close()     return hit, incomplete end  if ngx.req.get_method() == \"POST\" and is_multipart() then     local hit, incomplete = scan_body()     if hit then         ngx.log(ngx.WARN, \"PHP2Shell heuristic: upload progress; uri=\", ngx.var.request_uri)     elseif incomplete then         ngx.log(ngx.WARN, \"PHP2Shell heuristic: body scan incomplete; uri=\", ngx.var.request_uri)     end end

放到 PHP 转发 location 的 access_by_lua_file。扫描不完整要单独记日志,否则漏检看起来像没攻击。

5. 配置:哪些真的影响堆喷射和地址泄露

配置 影响 建议
session.upload_progress.enabled 决定 RINIT 前的 session 初始化路径 业务不用就设 0,这是最直接的一条
post_max_size / upload_max_filesize 限制 OOM 和堆喷射可用的请求体大小 按业务最大上传设置,不要无限
memory_limit OOM 阈值 按业务设置;太低反而帮攻击者,不要照抄 lab 的 8M
max_input_vars 限制字段数量 按表单需要设置;只影响一条触发路径,不能挡住整条链
display_errors / log_errors 警告输出和泄漏辅助 Off + On;是卫生习惯,不是修复
ASLR kernel.randomize_va_space 地址随机化 保持 2;不要用 setarch -R 启动 Apache
Apache LimitRequestBody 入口请求体上限 默认 0 是不限;设成业务上限,记录 413
Nginx client_max_body_size 入口请求体上限 默认 1m;按业务设置,记录 413
ModSecurity SecRequestBodyLimit / SecRequestBodyNoFilesLimit / LimitAction WAF 能看多少 body 默认约 12.5MB / 128KB;普通字段超过 128KB 后规则可能看不到;确认 Reject 还是 ProcessPartial
Suricata request-body-limit / request-body-inspect-window IDS 能看多少请求体 默认约 100KB / 4KB;调到覆盖业务上传,回放验证
OpenResty max_body_read Lua 扫描长度 公开示例 64KB;调到业务上限,临时文件分块读
worker 数量 / KeepAlive A/B 是否落同一进程 单 worker 是公开链条件,不是防御;不要为了防攻击改单 worker

堆喷射这条线,最有用的是:关掉不需要的 session.upload_progress,每一层都限制请求体,WAF/IDS 的 body 上限要能看见大字段。ModSecurity 的 SecRequestBodyNoFilesLimit 默认只有 128KB 量级,而公开链用十几 MB 的普通字段做 OOM;规则要么看不到,要么被引擎拒绝,观察期别把“没报”当成“没攻击”。

另外,ModSecurity v3 在 DetectionOnly 下会自动把 SecRequestBodyLimitAction 设为 ProcessPartial,超出上限的字段不会被检查。观察期更要留意这个盲区。

地址泄漏这条线,最有用的是:ASLR 保持开启,display_errors=Off 加 log_errors=On,响应侧盯异常长的 PHPSESSID。不要剥离 Set-Cookie,会打坏会话;也不要靠“我的构建不一样”来赌安全。

不建议做的“防御”:把 Apache 改单 worker、关 ASLR、把 memory_limit 调到 8M、一刀切封 chunked multipart、全封文件名里的 <?php、只加 WAF 不升级。

6. 上线顺序

  1. 先观察:ModSecurity 用 DetectionOnly 或 pass,log,Suricata/Snort 先 alert,OpenResty 先 log-only,Sigma 只告警。跑 24 到 72 小时,收集误报。
  2. 加例外:正常使用上传进度条的业务、大文件上传端点,逐条加白名单,不要全局关规则。
  3. 再拦截:只对高置信度组合拦截,比如“上传进度字段 + 大请求体”“异常 Cookie + OOM 日志”“PHP 父进程拉起 shell”。响应侧规则保持告警。
  4. 做验证:用授权流量回放,确认规则能命中,确认 403/413 没有打崩业务,确认 WAF/IDS 的 body 上限足够。
  5. 看日志:modsec_audit.log、Suricata fast.log、Snort alert、PHP error log、Apache error log、进程树和容器运行时事件。

规则语法检查:

snort -T -c /path/to/snort.lua -R snort3.rules suricata -T -S suricata.rules -c /path/to/suricata.yaml luac -p openresty_php2shell.lua

ModSecurity 和 OpenResty 还需要在对应测试环境做配置加载和授权流量回放。规则 ID 先查冲突;上面的 100xxxx 只是示例。

PHP2Shell三更:哪些打法能落地?附带防御规则" 网络安全文章

PHP2Shell三更:哪些打法能落地?附带防御规则"

PHP2Shell漏洞利用链依赖特定环境如Apacheprefork与display_errors开启,需堆泄漏及地址偏移校准,非通用一键RCE。建议立即升级PHP至安全版本,关闭session.upload_progress,收紧上传配置,并部署针对上传进度标记、异常Cookie及进程树的检测规则,同时关注底层运行时安全。
别光顾着修PHP漏洞,这三个严重漏洞也需要关注一下" 网络安全文章

别光顾着修PHP漏洞,这三个严重漏洞也需要关注一下"

2026年10月21日披露三起严重安全事件:GitLab正则引擎整数溢出漏洞CVE-2026-110264影响跨度12年需立即升级;LiteLLM1.107.0遭供应链投毒已确认失陷需排查凭据;Node.jsBrotli解码器堆越界写CVE-2026-111497存在远程执行风险。建议立即升级GitLab至指定版本,轮换所有CI/CD及模型调用凭据,部署Sigma规则监控异常日志,并检查Node服务解压路径是否被利用。
从“人海战术”到“智能对抗”:默安科技CEO聂万泉为发展中国家考察团授课" 网络安全文章

从“人海战术”到“智能对抗”:默安科技CEO聂万泉为发展中国家考察团授课"

默安科技CEO聂万泉为发展中国家考察团授课,探讨AI时代攻防从人海战术转向智能对抗。核心观点包括漏洞批量发现、攻击机器化、人力防御失效,提出以AI对AI的应对思路,强调技术、治理与人的角色对齐。对发展中国家而言,AI既是风险源也是追赶机会,需加强安全建设与国际经验共享。
PHPJsonSerializerUAF漏洞:绕过disable_function本地提权" 网络安全文章

PHPJsonSerializerUAF漏洞:绕过disable_function本地提权"

本文分析PHPJsonSerializerUAF漏洞,源于json_decode处理特定对象时释放后重用,可绕过disable_function实现本地提权。文章详述触发点、内存布局布置、伪造zend\\_function结构及调用伪函数触发执行等利用步骤,并给出概念性利用代码。建议开发者关注PHP版本更新,修复反序列化漏洞,加强输入过滤与安全配置。
评论:0   参与:  0