PHP2Shell:PHP引擎级双UAF,从5.4潜伏到8.5"

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

PHP2Shell:PHP 引擎级双 UAF,从 5.4 潜伏到 8.5

原创

APT-101
APT-101

APT-101

2026年10月22日 18:39
陕西

去阅读

漏洞分析#PHP安全#CVE#RCE#安全研究 #PHP2SHELL


01 平地惊雷:洞在 PHP 引擎里,5.4 就埋下了

PHP2SHELL 这个名字点得很直白:PHP 到 shell。它不是哪个框架的锅,是 PHP 引擎自己的两个漏洞,编号 CVE-2026-109618 和 CVE-2026-109619,横跨从 PHP 5.4.0 到 8.5.11 的所有版本,修复落在 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 四条维护分支上。

两个数字先记一下:受害面从 5.4.0 一直到 8.5.11,修复只落在 8.2 系往上。十来个大版本跨度,说明这缺陷在引擎里潜伏了很久,跟大多数跑在 web 上的 PHP 站都有关系。麻烦的是 5.x/7.x/8.0/8.1 早就停止维护,永远等不到补丁,老站只能裸奔。

严重在哪:PHP 的主流部署方式是 nginx/Apache + PHP-FPM,发行版、官方 php:fpm 镜像、宝塔/1Panel/Plesk 这类面板基本都是它。mod\_php 的存量主要在共享主机和老 LAMP。官方实测下来,mod\_php 上两个洞的验证最充分,FPM 上 619 已经跑通完整 RCE。

02 揭开盖子:一次 OOM,把请求生命周期拦腰截断

两个洞的根子是同一条路径。PHP 处理一次 HTTP 请求时,可能在应用代码还没执行、光解析请求体(multipart body)的阶段就内存耗尽(OOM),引擎会走 zend_bailout() 做 longjmp 中断。问题在于:longjmp 会跳过中断点之后还没做完的初始化,扩展的请求清理(RSHUTDOWN)也会被跳过。

正常流程里,请求结束应该先重置各类状态,再释放请求内存。OOM 一打断,顺序就乱了,于是出现两类独立缺陷:

CVE-2026-109618,同请求内:longjmp 后请求内存可能压根没初始化完成,request shutdown 阶段却把这份无效状态当成有效对象去访问。这是\"一次请求从启动到 shutdown\"内部的状态错乱,完全不依赖应用代码——你在应用层写多好的代码都管不了它。

CVE-2026-109619,跨请求:一次请求发生 fatal 或 OOM 后,worker 进程里存着指向请求堆的全局状态(session、错误回溯这类),本该先重置再释放堆。RSHUTDOWN 被跳过,重置没发生,堆却照常释放了,指针悬空。同一 worker 处理下一个请求时,在正常初始化之前就可能访问到这些过期指针——Use-After-Free。FPM 的 worker 常驻进程池天然满足\"同一 worker 接下一请求\"这个前提。

图1 一次 OOM 打断请求生命周期:跳过未完成的初始化和 RSHUTDOWN,618 同请求内错乱、619 下一请求踩悬空指针

03 618 的钥匙:upload\_progress + LFI,三把锁都开才进得去

618 的实际利用面,挂在 PHP 一个老功能上:session.upload\_progress(上传进度)。PHP ≥ 5.4 默认开启。它想解决的是\"大文件上传时给个进度条\":浏览器在 multipart 里塞一个魔术字段 PHP_SESSION_UPLOAD_PROGRESS,PHP 就把上传进度写进 session。

攻击者手里其实握着三个可控点:

  1. PHPSESSID 可控——cookie 里的值自己定,session 文件名就是 sess_<PHPSESSID>;
  2. multipart 里的 filename 可控——它会被原样写进 session 文件;
  3. 落盘位置已知——session.save_path 默认 /tmp。

组合起来:发一个带特制 PHPSESSID 和 PHP_SESSION_UPLOAD_PROGRESS 的 multipart 请求,PHP 就把塞了攻击载荷(filename 字段)的 session 文件写进 /tmp/sess_<自己编的id>。

但这只是\"能写一个内容可控的文件\",离执行代码还差一步。链路能不能走通,看三把锁:

  • session.upload_progress.enabled = On(默认开,多数配置直接满足);
  • session.upload_progress.cleanup = Off 或存在竞态窗口(cleanup 默认是 On,请求完就把文件删了,要赶在删除前包含它);
  • 目标存在 LFI,能 include /tmp/sess_<PHPSESSID>。

缺任何一把,618 这条链在目标上就断。尤其 LFI 是硬前提,没有 LFI,这个洞对你来说只是\"丢一个 session 文件\"。

图2 618 三把锁:enabled=On、cleanup=Off 或竞态、有 LFI 包含 sess 文件,缺一把链就断——LFI 是硬前提

04 619 的另一面:不用文件系统,unserialize 也能 RCE

619 走的是另一条路,干净得多。unserialize 的释放路径里有个 var_destroy() UAF:攻击者控制一个序列化对象,就能在对象释放阶段触发任意 destructor(__destruct 类魔术方法),配合 POP(Property Oriented Programming)链直接构成 RCE。整个过程不需要任何文件系统交互——不落盘、不需要 LFI。

它要的两个前提:目标存在可控的反序列化入口(入参来自 $_POST/$_COOKIE/外部数据源,进 unserialize()),以及应用里有可用的 POP gadget(框架自带的魔术方法类通常是最现成的,Laravel、PHPUnit 这些组件都有过经典链)。

版本探测也省事:X-Powered-By: PHP/x.y.z 响应头会直接泄漏 PHP 版本,落在受影响区间内,目标就进了潜在名单。

口径说明:公开材料对 619 的机制描述有两种说法——一说跨请求全局状态(session、错误回溯)指针悬空,一说 unserialize 路径的 var\_destroy UAF。这两条对着同一类失效现象,但官方还没给正式定稿,本文都摆出来,以 CVE 详情为准。

图3 619:控制序列化对象 → unserialize 释放 → var\_destroy UAF → destructor gadget → POP 链,全程不碰文件系统

05 谁中招了:一张矩阵对号入座

CVE 机制 受影响版本 修复版本
109618 session.upload\_progress UAF / OOM 5.4.0 – 8.5.11 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12
109619 var\_destroy UAF via unserialize 5.4.0 – 8.5.11 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12

部署方式 × 验证效果(官方口径):

部署方式 618 619
mod\_php CRASH PoC(首版口径为完整 RCE) 完整 RCE 已验证
PHP-FPM 源码分析认为受影响,利用链分析中 完整 RCE 已验证

判定就一句:版本在 [5.4.0, 8.5.11] 区间、且低于对应分支修复号的,潜在受影响;8.2.35+ / 8.3.36+ / 8.4.27+ / 8.5.12+ 已修复;低于 8.2 的老分支没有补丁,一律视为受影响。

图4 版本对号入座:5.4.0–8.5.11 受影响,修复线 8.2.35+ / 8.3.36+ / 8.4.27+ / 8.5.12+,EOL 旧分支无补丁视为受影响

06 白盒自查:六条线,分清\"受影响\"和\"可利用\"

先分清楚两件事:中不中看引擎版本,能不能打成 RCE 看配置和应用把前置凑齐没。自查动作照下面的线走:

① 版本与补丁状态(权威判据):

php -vphp -r 'echo PHP_VERSION;'                    # 运行时常量,防伪装dpkg -l | grep -i php                          # 包管理部署apt-get changelog php8.2 | grep -iE \"109618|109619\"   # 包是否已 backport 修复git -C /path/php-src log --oneline | grep -iE \"109618|109619|var_destroy\"  # 源码编译看补丁

注意以 Web SAPI 实际加载的引擎为准,CLI 和 php-fpm 版本可能不一样。

② SAPI 形态(决定哪个 CVE 适用面):

php -r 'echo php_sapi_name();'                 # apache2handler / fpm-fcgi / cliapache2ctl -M | grep -i php                    # mod_php 是否加载

③ 618 配置三件套:

php --ini && php -i | grep -iE \"upload_progress|save_path\"

enabled=On 是前提(默认);cleanup=On 需要打竞态窗口、Off 条件最优;save_path=/tmp 是 LFI 的包含目标。用 Laravel 这类框架时注意 session 可能被框架接管,upload\_progress 是否仍生效要实测。

④ LFI 面(618 的另一半):

grep -rnE \"include|require|file_get_contents\" --include=\"*.php\" /app | grep -v vendor/

找\"include/require + 用户可控变量\"且没有规范化/白名单的点。没有 LFI,618 链在目标上就是断的。

⑤ 619 入口:

grep -rn \"unserialize(\" --include=\"*.php\" /app | grep -v vendor/

入参来自外部数据源的调用才有意义,同时盘一下框架里有没有现成 gadget。

⑥ 合流判定:

版本命中 [5.4.0, 8.5.11] ?  ├─ 否 → 不受影响  └─ 是 → 618:enabled=On 且 (cleanup=Off 或竞态) 且 有 LFI               619:存在可控 unserialize 入口 且 有 gadget         缺任一前置 → 该链当前配置下不可利用(引擎仍要升级)

补充一点:UAF 类的 PoC 验证在测试集群上做,别在生产打——619 崩的是 worker,可能连累一片请求。

07 收束:升级四条分支,老版本别指望救

修复动作其实很整齐:

  1. 升级:8.2.35+ / 8.3.36+ / 8.4.27+ / 8.5.12+(四条分支已全部出包);
  2. 应用层兜底(升不了级时):618 锁 LFI(路径规范化/白名单)、session.upload_progress.enabled 可整关;619 收敛 unserialize() 入口、禁危险魔术方法类;
  3. 现实是:5.x/7.x/8.0/8.1 早已 EOL,官方不会出补丁,停更老站等于把这两个洞常驻在自己系统里——该迁移迁移,该隔离隔离。

PHP2Shell:PHP引擎级双UAF,从5.4潜伏到8.5" 网络安全文章

PHP2Shell:PHP引擎级双UAF,从5.4潜伏到8.5"

本文分析php引擎两个UAF漏洞CVE-2026-109618与CVE-2026-109619,影响php5.4.0至8.5.11所有版本,修复版本为8.2.35/8.3.36/8.4.27/8.5.12。618利用session.upload_progress与LFI实现RCE,619通过unserialize的var_destroyUAF触发POP链。建议升级至修复版本,老版本无补丁需迁移或隔离。
Gartner发布2027年十大战略技术趋势" 网络安全文章

Gartner发布2027年十大战略技术趋势"

Gartner发布2027年十大战略技术趋势,涵盖大型定量模型、语音推理模型、世界模型、AI上下文平台、编码智能体框架、AI智能体身份、自主式暴露修复、主权技术、可抛弃型应用及湿件即服务。强调企业需从被动观察者转变为主动构建者,应对AI系统架构风险,关注智能体身份安全与自主修复能力,并建议企业关注主权技术以防供应商锁定。
专题·网暴治理|李晓东:以多元主体协同推进网络暴力治理" 网络安全文章

专题·网暴治理|李晓东:以多元主体协同推进网络暴力治理"

本文提出以多元主体协同推进网络暴力治理,主张从信息处置走向全过程治理,按风险控制能力明确政府、平台、社会力量等主体责任,建立跨平台协作规范与证据保全机制,细化社会服务接入与转介,并将治理成效落实到权利保护,强调协同机制需保留纠错空间,防止治理失误产生新伤害。
评论:0   参与:  0