欢迎访问 AI Skills Video ! 海量优质视频教程,助你提升技能。

PHP2Shell 复盘:AI 源码审计如何压缩漏洞攻防窗口

老张 2026年10月23日 0 次阅读
PHP2Shell 复盘:AI 源码审计如何压缩漏洞攻防窗口
php2shell,本文复盘PHP2SHELL漏洞是如何被AI审计发现的。AI 源码审计把"看得懂但打不通"的漏洞变成可执行攻击链,代价是攻防两侧的准备时间被同步压缩。

PHP2Shell 复盘:AI 源码审计如何压缩漏洞攻防窗口

一个存在十余年、被认定"理论上可能、实际打不通"的内存缺陷,在 2026 年 8 月被模型跑通成未认证远程代码执行。AI 源码审计把"看得懂但打不通"的漏洞变成可执行攻击链,代价是攻防两侧的准备时间被同步压缩。 本文按三条线拆这件事:起因(Calif、Wiz、OpenAI 如何用 GPT-5.6 Sol 与 Gemini 4 Argon 找到 CVE-2026-109618 / CVE-2026-109619)、防御(五步把 AI 审计接进自己的仓库)、攻击(补丁公开后 Shodan 类指纹库与扫描器如何定位你)。文中给出可直接接入 CI 的 Python diff 审计脚本,先划清边界:这套流程不做动态验证、不覆盖二进制与内核层,也不能替代升级到 PHP 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35。以下事实基于 PHP 官方 GHSA 公告、PHP Foundation 复盘与 2026 年 10 月 21—22 日的公开报道,模型能力迭代后需重新评估。


1.00

一、起因:AI 把"理论上可能"的缺陷跑成了攻击链

触发点在 PHP 引擎自己的错误处理里,不在任何应用代码里。 官方公告写明:PHP 在内存耗尽(OOM)时用 longjmp() 跳出当前上下文,某些上下文里请求内存"可访问但未完全初始化",一旦此时分配失败,PHP 没能正确记账,请求以无效状态进入关闭流程,后续清理流程却把它当合法数据读——这是 CVE-2026-109618 的根因;CVE-2026-109619 则是 worker 保留了指向上一请求内存的全局状态(错误、会话、对象、扩展),下一个请求在正常初始化覆盖它之前就消费了这个悬空指针。

编号 类型 触发条件 已验证环境
CVE-2026-109618 OOM 后的状态不一致 multipart 解析中触发 OOM 提前退出 mod_php 默认限制下确认 RCE
CVE-2026-109619 跨请求全局状态残留 致命错误后 worker 沿用旧指针 mod_php 与 FPM 均确认

两者 GHSA 评级均为 Critical,CVSS v4.0 9.2;受影响版本回溯到 PHP 5.4,PHP 项目公布 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 四个修复版本(8.1 及更早为 EOL,仅 7.4/8.0/8.1 有最小源码回移)。风险最高的是 Apache mod_php:攻击在任何应用代码执行之前就完成,修好 WordPress 插件没有意义。技术细节建议直接读官方公告原文:GHSA-w8hw-w9fx-j79w(RCE via OOM corruption) 与 GHSA-6wmf-4rv5-p3rp(RCE via stale global state),两份都写清了 producer/consumer 链路和缓解参数,比二手报道可靠。

时间线才是重点:发现是旧的,利用是新的。

  • 2024 年,Quarkslab 在德国 Sovereign Tech Agency 资助的外部审计中首次看到 OOM 线索,但没找到具体利用路径,同时有"影响更明确"的问题插队。

  • 修复方案三条路都带代价:破坏向后兼容、引入新的 DoS 面、或要求改动大量第三方扩展。安全团队按共识制推进,方案反复被搁置——PHP Foundation 自己把这 20 个月定性为"结构性失败"(structural failure)。

  • 2026 年 6 月 22 日,OpenAI 启动 Patch the Planet(与 Trail of Bits 合作,HackerOne、Calif 参与),PHP 于 7 月加入。

  • 2026 年 7 月 30 日 Calif 开始审计;8 月 1 日确认第一个 OOM fatal;8 月 11 日正式报告;8 月 15 日 PHP 侧确认复现。

  • Wiz 独立推进:用 Wiz Atlas + Gemini 4 Argon + GPT-5.6 Sol,8 月 19 日找到第一个洞,8 月 20 日在 Drupal 上拿到未认证 RCE,8 月 24 日提交报告。

  • 9 月 21 日 PHP 确认并启动协调修复;10 月 21 日补丁与公告发布。

注意别把这件事简化成"ChatGPT 发现 PHP 漏洞"。 按官方披露,AI 是研究工具的一环,出问题的是"人没能把理论缺陷变成可执行路径",模型补上的正是这段。Calif 的研究员也公开说明:他依赖的是自己对 PHP 历史漏洞的先验知识再加上定向提问,用通用提示词让模型"扫一遍代码"是找不到这类洞的。


1.00

二、为什么"AI 先发现、修复滞后"是结构性风险

瓶颈已经从"发现漏洞"转移到"决定怎么修"。 2024 到 2026 这两年里,PHP 团队缺的不是线索,是能判断"哪种修法不会撕裂扩展生态"的资深工程师和时间。AI 把这个环节的等待成本原样留着,只把前面的发现成本压到接近零。

AI 把漏洞发现的边际成本压到接近零,但没有降低维护者的决策成本——这个错配就是风险的来源。

换到攻击侧看,同一批能力是双向的。据韩国媒体경향신문报道,首尔女子大学教授金明柱(Kim Myung-Joo)的说法是:AI 一旦把上万个漏洞挖出来,"落到黑客手里可以全部变成攻击代码";安全公司 Stealien CEO 朴赞岩的比喻更直白——"AI 快速找问题,也能被滥用,是把双刃剑"。这些是行业判断,不是实测量化值,但方向与 Cloudflare 的数据一致(见第四节)。

对自建项目来说,这条结论可以落成一句可操作的判断:你的安全预算里,"谁来复核 AI 报出来的问题"必须单独占一行,否则审计工具只是把噪声搬到你的 issue 列表里。OpenAI 网络安全技术负责人 Fouad Matin 的说法是维护者"因为热爱开源在做这件事,现在却要审一堆 slop CVE"。PHP Foundation 的数字更具体:自 2026 年 7 月 1 日起发布 15 个 CVE,同时处理了 100 多个无效或重复报告。

把这条风险接进日常研发流程的做法。

三、防御:五步把 AI 源码审计接进项目

这一节解决的是"从等披露变成自查"。 核心判断先说:AI 源码审计必须挂在 diff 上,而不是靠季度扫描;季度扫描假设"今天发现的弱点是明天的攻击门槛",而 PHP2Shell 证明这个门槛已经降到小时级。

先看三类手段的定位差异,别指望单个工具覆盖:

维度 规则型 SAST LLM 语义审计 本文组合流水线
判断依据 人工规则 + 数据流分析 语义与攻击者可控性推理 规则兜底 + 模型推理
擅长 已知模式、可重复、毫秒级 跨函数逻辑、跨请求状态残留 两者覆盖不到的长尾
短板 规则外的新模式直接漏报 幻觉、误报、结论不确定 依赖人审兜底
成本结构 边际成本接近零 按 token 计费 主要成本在复核人力
适合时机 每次提交 每次 PR + 一次基线 CI 常驻

五步速查(详细说明见下方各步):

步骤 产出物 卡点
第一步 清点资产 PHP 版本 + SAPI 清单 漏掉容器与托管面板
第二步 接 diff 审计 CI 里的审计作业 只扫增量会漏历史债
第三步 基线扫描 全仓首轮报告 不做可达性过滤会烧钱
第四步 人审验证 去重后的可利用清单 误报直接进 issue
第五步 情报打通 SBOM + 公告订阅 只订阅不映射到资产

第一步:清点 PHP 资产与 SAPI

这步决定你到底是"高危"还是"待确认"。 至少要拿到三件事:PHP 精确版本(含 patch 号)、SAPI 类型(mod_php / php-fpm / php-cgi / FrankenPHP / LiteSpeed)、是否有网络可达入口。mod_php 优先级最高,因为公告确认攻击在应用代码执行前完成。容器镜像、托管面板、遗留仪表盘是最容易漏的三类。

第二步:把 AI 审计挂到 diff 上

只审变更行,是让成本和噪声都可控的唯一办法。 一个中型项目全仓几十万行,一次全量送模型既贵又会产生大量历史遗留误报;PR 级 diff 通常只有几百行,模型上下文吃得下、结论也更容易定位到行号。落地形态就是 git diff 切块 → 模型输出结构化 JSON → 按严重度决定是否阻断合并,代码见第五节。

第三步:做一次全仓基线扫描

基线扫描只做一次,而且要带可达性过滤。 直接对每个函数调用模型是最贵的做法。arXiv 上 OpenAnt 的预印本给了一组可参考的成本口径(预印本,未经同行评审,以论文原文为准):八个开源项目(含 OpenSSL、WordPress、n8n、Rails)总评估成本 1,461.25 美元;其中 OpenSSL 442.66 美元、n8n 392.84 美元、WordPress 243.84 美元,而 Rails 仅 23.25 美元。论文同时说明:如果不做可达性过滤,按中位数成本估算要花约 23,700 美元,静态过滤把总成本降低 96% 以上。

第四步:给人审留出验证环节

AI 报"有洞"和"这个洞能被利用"是两件事。 OpenAnt 那组数据同样说明了落差:376 条初始标记 → 190 条经对抗验证确认 → 144 条产出可用 exploit(占确认数的 75.8%)。这意味着盲目把模型结论直接对外提交,制造的就是 slop CVE。Patch the Planet 的做法值得抄:安全工程师先复核,再交给维护者,而不是把原始发现直接倒过去。

据公开报道,Apple 走的是同一条路:2026 年 4 月加入 Anthropic 发起的 Project Glasswing,拿到未公开的 Claude Mythos Preview 用于 iOS、iPadOS、macOS、Safari 等代码的漏洞发现;随后 iOS 26.6 与 macOS Tahoe 26.6 的安全公告里,至少有 9 个已修复漏洞被署名致谢给 AI 模型或使用模型的安全团队(涉及 Anthropic Claude、OpenAI Codex Security、NVIDIA 红队、Z.AI)。这条路径的共同点是:模型做发现,人做确认与归因。

第五步:把审计结论接进情报与补丁流程

审计结果必须能对上"哪台机器受影响"。 三个动作:维护 SBOM/依赖清单、订阅 GHSA 与发行版安全公告、把"外部资产盘点"做成周期任务(第四节会讲为什么)。PHP Foundation 自己也在改流程:2026 年 5 月成立 Ecosystem Security Team、增聘工程师、把安全决策从共识制改为多数表决,并在今年先用 Anthropic 的 Claude Mythos 扫过 PHP,随后接入 OpenAI 的 Daybreak 来验证报告。

四、攻击:20 分钟不是段子,是可核对的时间线

先给可核查的数字,再谈"20 分钟"。 Cloudflare CEO Matthew Prince 公开的观测是:漏洞公开前 10 小时,全网已出现数千次利用尝试;公告后 45 分钟内升到数万次;2 小时内数十万次;之后累计到数百万次。Amazon 侧更早,有报道称 10 月 17 日就捕获过一次尝试,比公开披露早四天。GreyNoise 的实验则说明"被动发现"有多快:新上线的传感器节点在约 1 小时内就被已知扫描器扫到(该实验还注明,74 个已知扫描器中只有 18 个在一周内接触过节点,说明这是"部分扫描器"的速度下限)。加上 masscan 这类工具在高速率下扫完整个 IPv4 空间只需数分钟,"暴露即被发现"是当下的默认状态,不是极端情况。

"20 分钟"在工程上是一个合理的经验口径——公开信息落进指纹库、扫描器更新指纹、payload 开始试探,这条链路今天的量级就是分钟到小时。据作者观察,多数团队真正的暴露时间不是被扫到的时间,而是"从被扫到到有人开始处理"的间隔。

Shodan 这类指纹库让"谁在跑 PHP、跑哪个版本"从猜测变成一条查询。它就是攻击方的资产测绘层,防守方必须同样会用:

# 字段名以 Shodan 官方 Filter Reference 为准
http.component:"WordPress"            # 按 Web 技术栈定位
product:"Apache httpd"                # 按服务产品名定位
vuln:CVE-2021-44228                   # 按其 CVE 索引查(依赖 Shodan 自身收录)
org:"YourOrg" ssl.cert.subject.cn:"example.com"  # 收敛到自有资产范围

字段清单见 Shodan Filter Reference,http.component、product、vuln、ssl.cert.subject.cn 都在其中;vuln 过滤器的覆盖范围以 Shodan 自身的 CVE 索引为准,不代表新披露漏洞会立刻可查。

这次真正的加速器不是扫描器,是信息提前泄漏。 按 PHP Foundation 复盘:

时间(2026) 事件
10-17 Amazon 捕获一次利用尝试
10-20 15:00 UTC 两个 CVE 编号在论坛与社媒流传(分数还写错了)
10-20 约 20:00 UTC 报告方误发 PoC 视频,删后仍在传播
10-21 07:41 UTC 一家发行版提前发布含补丁镜像
10-21 14:00 UTC 补丁与公告正式发布(比原计划早一天)

三处外泄叠在一起,等于把"补丁差异分析"这份工作免费送给了攻击方。NSA 罕见公开发出警告,建议无法立即升级的站点先下线;英国有 30 多个政府服务下线,荷兰、芬兰、法国、德国等多国政府站点预防性关闭。

防守侧能做的动作很具体:mod_php 优先升级并重启 PHP-FPM 与所有长驻 worker;无法立即升级时按公告收紧配置(memory_limit、post_max_size、max_input_nesting_level、session.upload_progress.enabled=0、PHP 8.5 的 fatal_error_backtraces=0),但公告明确写了没有完整的通用配置缓解,升级才是唯一完整方案;检测侧看两件事——异常 multipart 请求后紧跟的内存超限错误与连续 5xx,以及 PHP worker 派生 sh、python、curl 这类子进程。WAF 只是兜底,Cloudflare 已观测到攻击方改 payload 绕过新规则。

把这类外部情报接进自动化流程。

五、完整可运行示例:audit_diff.py

这步解决"模型审计怎么落地成 CI 作业"。 下面两段是同一个文件 audit_diff.py 的上半段与下半段,按顺序拼接后可直接运行。环境:Python 3.9+、pip install requests、一个 OpenAI 兼容的 chat completions 端点(模型名请换成你自己账号下可用的型号,此处 gpt-5.5-cyber 仅作占位,以服务商文档为准)。

第一段做三件事:切 diff、发请求、解析结果;含 429/5xx 的指数退避。

# audit_diff.py 上半段:切块 + 调用 + 解析
import json, os, subprocess, time
import requests

MODEL = os.environ.get("AUDIT_MODEL", "gpt-5.5-cyber")   # 占位,换成实际可用型号
BASE  = os.environ.get("AUDIT_BASE_URL", "https://api.openai.com/v1")
KEY   = os.environ["AUDIT_API_KEY"]

PROMPT = """你是代码安全审计员。只报告攻击者可控输入能触发的漏洞。
每处输出一个对象:{"file","line","cwe","severity":"critical|high|medium|low","reason","fix"}。
没有把握就不要报。只输出 JSON 数组。"""

def git_diff_chunks(repo, rev="HEAD~1", size=8000):
    """按字符切分 diff,保证每块能塞进模型上下文"""
    diff = subprocess.run(["git", "-C", repo, "diff", "-U5", rev],
                          capture_output=True, text=True, check=True).stdout
    for i in range(0, len(diff), size):
        yield diff[i:i + size]

def ask(chunk, retries=4):
    body = {"model": MODEL, "temperature": 0,
            "messages": [{"role": "system", "content": PROMPT},
                         {"role": "user", "content": chunk}]}
    for attempt in range(retries):
        r = requests.post(f"{BASE}/chat/completions", json=body, timeout=120,
                          headers={"Authorization": f"Bearer {KEY}"})
        if r.status_code == 200:
            raw = r.json()["choices"][0]["message"]["content"].strip()
            if raw.startswith("```"):                 # 兼容模型包代码块的习惯
                raw = raw.split("```")[1].removeprefix("json").strip()
            return json.loads(raw)                    # 生产环境建议改用结构化输出
        if r.status_code in (429, 500, 502, 503, 504):
            time.sleep(2 ** attempt)                  # 指数退避:2/4/8/16 秒
            continue
        r.raise_for_status()                          # 其他 4xx 直接失败,不重试

第二段做汇总与退出码:critical/high 阻断,单块失败只记录不吞掉整轮。

# audit_diff.py 下半段:汇总 + 退出码(0 通过 / 1 发现高危 / 2 审计未跑完)
FAIL_ON = {"critical", "high"}

def audit(repo):
    findings, errors = [], []
    for idx, chunk in enumerate(git_diff_chunks(repo)):
        try:
            findings += ask(chunk)
        except Exception as e:                 # 单块失败不阻塞整轮
            errors.append(f"chunk {idx}: {e}")
    with open("audit-report.json", "w") as f:
        json.dump({"findings": findings, "errors": errors}, f,
                  ensure_ascii=False, indent=2)
    for f in findings:
        print(f"{str(f.get('severity','?')):8} {f.get('file')}:{f.get('line')} "
              f"{f.get('cwe')} {str(f.get('reason',''))[:60]}")
    blocking = [f for f in findings
                if str(f.get("severity", "")).lower() in FAIL_ON]
    if blocking:
        return 1
    return 0 if not errors else 2   # 2 = 没审完,交人工复核,别当通过

if __name__ == "__main__":
    raise SystemExit(audit(os.environ.get("REPO", ".")))

运行与输出:

pip install requests
AUDIT_API_KEY=sk-xxx REPO=/path/to/repo python audit_diff.py; echo "exit=$?"
# critical src/Upload/Handler.php:142 CWE-416 未初始化状态在请求关闭时被当作有效数据读取
# high     src/Session/Store.php:88 CWE-416 致命错误后残留指针指向已释放的请求内存
# exit=1

两点工程约定,属于实现建议而非本文实测值:把结果按 commit SHA 落盘,同一 SHA 已有 status == "ok" 的记录就跳过,避免重跑重复计费;git diff HEAD~1 在仓库首个提交上会失败,CI 里用 ${{ github.event.before }} 之类的基线 SHA 代替。成本量级可以这样估(示例口径,非实测):一次 PR diff 约 300 行 ≈ 6,000 输入 tokens、800 输出 tokens,每天 20 次;按输入 1 美元/百万 tokens、输出 4 美元/百万 tokens 假设,日成本 ≈ 20 ×(6000×1 + 800×4)/1,000,000 ≈ 0.18 美元,月均约 5 美元。真实账单以你所用模型的定价页为准。

六、坑与局限:AI 审计现在还做不到什么

第一,它证明不了可利用性。 静态推理给出的是"可疑路径",PHP2Shell 的价值恰恰在于 Wiz/Calif 把四个变体串成了可执行链并在 WordPress、Drupal、Joomla 默认镜像上验证过。你的流水线如果没有沙箱复现环节,输出就只能算线索。

第二,上下文截断会切断跨文件调用链,这是最容易忽视的漏报来源。 按 8,000 字符切 diff 的代价就是跨文件的污点流可能被切成两半。缓解办法是给模型附带调用点摘要,或退回 CodeQL 这类工具做数据流、只用模型判语义。

第三,误报处理不当会反噬社区。 把模型结论直接提给上游,等于给维护者加活。前面引用的 100 多个无效/重复报告就是这条成本的具体形状。

第四,成本随仓库规模非线性上升。 全仓扫描不做可达性过滤,按 OpenAnt 论文的估算会差两个数量级(约 23,700 美元 vs 1,461.25 美元);这套数字来自其八个项目的评估口径,不能直接套到你的仓库。结论仍是:先做 diff,再做一次带过滤的基线。

**本文方案的边界:**不覆盖二进制、内核与容器逃逸分析;不做 FPM 池配置的合规审计(这块要靠配置基线扫描);不替代 SAST 的规则兜底;模型输出走的是普通 JSON 解析,模型不规范输出时需改用结构化输出或 function calling。另外,对不属于自己的代码做审计必须取得授权,这是法律边界不是技术边界。七、总结

  • 触发点在引擎:PHP2Shell 两个 CVE 都在内存管理与错误处理里,mod_php 上应用代码执行前即完成,修插件无用。

  • 发现降本、决策没降本:2024 年就有线索,卡在"选哪种修法",Foundation 自认是结构性失败。

  • 审计要挂 diff:全仓扫描只做一次且必须带可达性过滤,日常只审变更行才能控制成本与噪声。

  • 暴露即索引:Cloudflare 观测到公告后 45 分钟内数万次尝试,Shodan 类指纹库让"谁在跑 PHP"变成一条查询。

  • 人审不可省:模型报告的是线索,不是可利用性;直接外提就变成 slop CVE。

AI 源码审计改变的只是漏洞的可见性,真正的胜负手仍是补丁到机器的最后一百米。