我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

Laravel Token Guard 身份验证绕过(CVE-2026-114509)

Laravel 将 CVE-2026-114509 评为严重,CVSS v4.0 9.2:在 MySQL 或 MariaDB 上,未通过身份验证的攻击者可发送类似 {"api_token": [0]} 的 JSON 请求体,在不知道任何有效 API 令牌的情况下,以旧版 token API guard 的现有用户身份登录。12.68.0 和 13.29.0 已修复,11.56.1 标签中也包含该修复。公开概念验证自 2026 年 10 月 22 日起可用,目前没有已知在野利用。

要点速览

  • Laravel 内置的 token guard,即 token 身份验证驱动,通过在用户表中查找 api_token 值来验证请求。修复前,guard 从不检查令牌是否为字符串,因此特制 JSON 请求体会将查找转变为使用攻击者所选值的 WHERE api_token IN (...) 查询。
  • 在 MySQL 和 MariaDB 上,{"api_token": [0]} 这样的请求体会让数据库将每个存储的文本令牌转换为数字,因此任何以字母开头的令牌都等于 0。查询返回该用户,请求随即以其身份通过验证,无需有效令牌,也无需暴力破解。
  • Laravel 将该漏洞评为严重,CVSS v4.0 9.2,公开概念验证于 2026 年 10 月 22 日发布。截至 2026 年 10 月 24 日,漏洞未列入 CISA KEV 目录,也没有已知在野利用。
  • 立即升级至 12.68.0、13.29.0 或 11.56.1 标签,或添加拒绝非字符串 api_token 值的中间件。修复于 2026 年 8 月 25 日发布,因此之后的依赖更新可能已经保护了您的应用。

我是否受到影响?

以下条件全部成立时,您就受到影响:

  • config/auth.php 定义了 'driver' => 'token' 的 guard,并有端点使用它(当该 guard 为 token guard 时,使用 auth:api 或 auth:<guard> 的路由)。
  • 框架版本低于所在分支的修复版本:Laravel 12 低于 12.68.0,Laravel 13 低于 13.29.0,Laravel 11 低于 11.56.1 标签,或使用任何已终止支持的分支(10.x 及更早版本)。
  • 用户表位于 MySQL 或 MariaDB。公开 PoC 演示了 PostgreSQL 会拒绝类型不匹配的比较,SQLite 则不返回任何行。
  • 在 PHP 8 上,guard 未设置 'hash' => true。当缺少该键时,Laravel 的 guard 创建器默认将 hash 设为 false;附带 token guard 的项目骨架配置也显式将其设为 false。

如果您使用 Laravel Sanctum、Passport,或 token 驱动以外的任何 guard,就不受影响。Sanctum 读取始终为字符串的 Bearer 请求头,使用 SHA-256 哈希,并将令牌存储在独立表中。

分支 受影响版本 修复版本
13.x >= 13.0.0, < 13.29.0 13.29.0
12.x < 12.68.0 12.68.0
11.x < 11.56.1 11.56.1 标签
10.x 及更早版本 所有使用 token guard 的版本 无(已终止支持)

检查部署中运行的配置:

# Is the token guard configured at all?
grep -n "'driver' => 'token'" config/auth.php

# Which framework version is installed?
composer show laravel/framework | grep versions

# Which database backend?
grep DB_CONNECTION .env

# Does composer already flag it?
composer audit

在容器镜像中:

docker run --rm <image> php -r "echo shell_exec('composer show laravel/framework 2>/dev/null | grep versions') ?: 'composer not in image';"

guard 的历史很重要:Laravel 6.x 和 7.x 项目骨架默认附带 token guard,并设置 'hash' => false;8.x 和 9.x 项目骨架则完全不含 API guard。composer update 永远不会覆盖 config/auth.php,因此从旧项目骨架升级的应用可能仍在不知不觉中保留 token guard。公开 PoC 记录了这段配置历史,6.x 和 7.x 骨架文件也证实了这一点。Packagist 记录 laravel/framework 过去一个月有 19,679,151 次下载(2026 年 10 月 24 日核查),因此需要执行该检查的应用数量很大。

如何修复?

将框架升级至当前分支的首个修复版本:

# Laravel 12
composer require "laravel/framework:^12.68.0" --update-with-dependencies

# Laravel 13
composer require "laravel/framework:^13.29.0" --update-with-dependencies

修复也存在于 laravel/framework 的 v11.56.1 标签中,这一点已通过读取该标签下的 src/Illuminate/Auth/TokenGuard.php 验证。公告本身仅列出 12.x 和 13.x 范围,因此如果固定为 11.56.1,请检查代码确认结果。

如果无法升级,按 PoC 建议添加中间件,在非字符串令牌到达 guard 前将其拒绝:

public function handle($request, Closure $next)
{
    $token = $request->input('api_token');
    if ($token !== null && !is_string($token)) {
        abort(400, 'Invalid token format');
    }
    return $next($request);
}

该中间件可阻止此类攻击。其局限是:必须在所有可访问 token guard 的路由或路由组上注册,而且它并未解决底层类型混淆。通过 Web 应用防火墙规则拒绝 api_token 为数组的 JSON 或查询值也有同样效果,PoC 列出了对应的查询和 JSON 模式。持久的修复方式是升级,或将 API 迁移到完全不使用此查找方式的 Laravel Sanctum。

根本原因是什么?

token guard 将任何非空凭据都视为字符串,而 Eloquent 会将数组凭据转为 IN 列表,因此 JSON 数组变成了针对攻击者所选值列表的 SQL 比较。

v12.67.0 的 src/Illuminate/Auth/TokenGuard.php 第 83 行中的漏洞检查:

$token = $this->getTokenForRequest();

if (! empty($token)) {
    $user = $this->provider->retrieveByCredentials([
        $this->storageKey => $this->hash ? hash('sha256', $token) : $token,
    ]);
}

非空数组的 empty() 结果为 false,因此 ! empty([0]) 通过检查,数组 [0] 继续流转。还有两个事实构成完整链条:

  • src/Illuminate/Auth/AuthManager.php(第 166 行)中的 guard 创建器传递 $config['hash'] ?? false,因此不含 hash 键的 guard 配置会保留原始令牌。在 PHP 8 上设置 'hash' => true 时,hash('sha256', [0]) 会抛出 TypeError,请求因错误终止,而不会绕过验证。PoC 指出,PHP 7 只发出警告并继续,因此旧技术栈即使开启哈希仍会暴露。
  • EloquentUserProvider::retrieveByCredentials(第 129 行)按设计将数组凭据视为列表,以便其他身份验证流程查找多个候选值:
foreach ($credentials as $key => $value) {
    if (is_array($value) || $value instanceof Arrayable) {
        $query->whereIn($key, $value);
    }

guard 的凭据由此不再是“待比较的令牌”,而变成“允许用户匹配的令牌列表”。两个补丁提交的标题均为“fix token retrieval”,日期为 2026 年 8 月 24 日,通过在查找前要求字符串来消除类型混淆:

-        if (! empty($token)) {
+        if (is_string($token) && ! empty($token)) {

同样的检查也添加到了 validate() 中。它是第二个入口,会在不执行任何哈希的情况下将凭据传给 retrieveByCredentials。

如何利用?

公开 PoC 展示,攻击只需一个带 JSON 请求体的请求:

  1. 攻击者发送 POST /api/user,包含 Content-Type: application/json 和请求体 {"api_token": [0]}。Laravel 的 request->input() 解码 JSON,因此凭据变为 PHP 数组 [0],即包含一个整数的列表。
  2. TokenGuard::getTokenForRequest() 从请求输入中返回 [0],! empty([0]) 检查通过。
  3. 当 hash 为 false 时,原始数组到达 retrieveByCredentials,后者因为凭据是数组而构建 WHERE api_token IN (0)。
  4. MySQL 和 MariaDB 通过将每个存储的令牌转换为数字,来比较文本列与数字 0。以字母开头的令牌是 API 令牌的常见情况,它会转换为 0,因此比较相等。
  5. ->first() 返回首个匹配行。PoC 实验环境预置了四个用户,请求返回时以最先创建的管理员用户身份通过验证,整个过程中未发送任何有效令牌。
  6. 该会话中的每个后续请求都以该用户权限运行,因此绕过演变为针对 guard 保护的所有路由的账户接管。

以下是 PoC 中的完整攻击请求,可在其实验环境复现:

curl -s http://target/api/user \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -X POST -d '{"api_token": [0]}'

?api_token[]=0 这样的查询字符串也会产生数组,但其中是字符串 "0",数据库会按文本比较,因此使用裸数字的 JSON 请求体才是有效路径。

攻击流程

flowchart LR A["发送带 JSON 请求体的 POST
{"api_token": [0]}"] --> B["getTokenForRequest()
返回数组 [0]"] B --> C["! empty([0]) 通过,
没有 is_string 检查"] C --> D["数组凭据
变成 WHERE api_token IN (0)"] D --> E["MySQL 将文本令牌转换为数字,
字母开头的令牌转换为 0"] E --> F["first() 返回
首个匹配用户"] F --> G["请求以该用户
身份通过验证"]

攻击者能做什么?

攻击者以 token guard 的某个现有用户身份通过验证,获得该用户 API 访问权允许的一切能力:通过 API 路由读取、写入和删除,以及访问路由层可触及的任何数据。公告和 PoC 列出的条件及局限如下:

  • 不需要凭据、用户交互,除了能访问 API 外,也不需要特定网络位置。
  • guard 必须是在 MySQL 或 MariaDB 上使用的 token 驱动,且 hash 为 false。PostgreSQL 和 SQLite 部署不受这种整数比较影响。
  • 攻击者不能选择账户。查询返回首个匹配行;在 PoC 实验环境中,它是管理员,因为该用户最先创建。
  • Sanctum 和 Passport 部署不受影响,其令牌查找方式不同。

为什么修复有效?

补丁在 user() 和 validate() 两个入口的空值检查前加入 is_string():

-        if (! empty($token)) {
+        if (is_string($token) && ! empty($token)) {
             $user = $this->provider->retrieveByCredentials([
-        if (empty($credentials[$this->inputKey])) {
+        if (! is_string($credentials[$this->inputKey] ?? null) || empty($credentials[$this->inputKey])) {

JSON 数组现在无法通过字符串检查,身份验证返回 null,这正是 guard 原本期望的结果。修复在 guard 层面彻底解决了此问题:非字符串凭据再也无法经由 token guard 到达 retrieveByCredentials。它不改变 retrieveByCredentials 本身;对于有意使用数组的 provider,它仍将数组凭据映射为 IN 列表,因此向身份验证查找中传入数组的应用代码仍需自行承担风险。

如何检查是否已遭入侵?

Laravel 未发布入侵指标。PoC 记录了利用表现,由于正常客户端发送字符串令牌,这些模式非常明确:

  • 访问日志中的查询字符串:api_token[]=0 和 api_token[0]=0。
  • 应用日志或 WAF 日志中的 JSON 请求体:"api_token":[0] 和 "api_token":[false]。
  • 无论是否命中上述模式,都值得检查的异常:没有对应令牌签发记录的会话在 token guard 路由上获得成功响应,以及管理员账户从新的源地址执行操作。

时间线

  • 2026-08-24:Taylor Otwell 向两个受支持分支提交“fix token retrieval”修复。
  • 2026-08-25:Laravel 发布包含修复的 v12.68.0 和 v13.29.0。
  • 2026-10-19:Laravel 发布公告 GHSA-f9x3-7vq9-cph6,评级为严重,CVSS v4.0 9.2,并向 KIKOmanasijev 致谢。
  • 2026-10-21:GitHub CNA 发布 CVE-2026-114509 的 CVE 记录。
  • 2026-10-22:GitHub 上发布带有 Docker 实验环境的公开概念验证。
  • 2026-10-23:GitHub Advisory Database 发布并评审该公告,添加 CWE-287 和 CWE-1287。
  • 2026-10-24:本文发布。

常见问题

Laravel Sanctum 或 Passport 受影响吗?

不受影响。Sanctum 读取始终为字符串的 Bearer 请求头,并将 SHA-256 哈希与独立的个人访问令牌表比较,因此数组不会到达查找流程。Passport 通过 OAuth2 流程验证身份,使用不同的存储和查找方式。公告仅涵盖内置 token guard。

为什么攻击只在 MySQL 或 MariaDB 上有效?

数组凭据最终以数字 0 参与比较,而 MySQL 和 MariaDB 为此会将文本列转换为数字,这会将大多数真实令牌变成 0。对于同一查询,PostgreSQL 抛出类型错误,SQLite 则因类型亲和性而不返回任何行;PoC 实验环境演示了这两种情况。guard 漏洞在所有环境中都存在,但可利用的比较依赖隐式转换。

'hash' => true 能保护我吗?

在 PHP 8 上,它会将攻击降级为错误:hash('sha256', [0]) 抛出 TypeError,请求失败,不会以任何用户身份通过验证。在 PHP 7 上,哈希函数只发出警告并返回 null,因此 PoC 将这一组合标为仍有暴露。如果哈希是您唯一的防护,仍应升级;还要注意,6.x 和 7.x 骨架配置附带的 token guard 设置为 'hash' => false。

攻击者能选择特定账户吗?

不能直接选择。查找返回数据库匹配的首行,因此攻击者获得排在最前面的匹配用户。PoC 实验环境显示,这是最先创建的用户,通常是管理员。攻击者也可以在 JSON 数组中发送多个数字,以扩大转换后的匹配范围,但最终仍由数据库返回的首行决定。

我的应用使用已终止支持的 Laravel 版本,怎么办?

添加拒绝非字符串 api_token 值的中间件,或使用上述模式建立 WAF 规则,并规划迁移至 PoC 推荐的 Sanctum。已终止支持的分支不会收到修复,因此只要仍配置该 guard,它本身就仍然存在漏洞。

使用 Ostorlab 扫描 Web 应用,识别其暴露的 Laravel 框架版本和 token guard 配置,包括仍配置 token 驱动的旧部署。

参考资料

  • Laravel 公告 GHSA-f9x3-7vq9-cph6,2026 年 10 月 19 日发布:https://github.com/laravel/framework/security/advisories/GHSA-f9x3-7vq9-cph6 (2026 年 10 月 24 日核查)
  • 包含 CVSS v4.0 9.2 向量的 CVE-2026-114509 记录:https://cveawg.mitre.org/api/cve/CVE-2026-114509 (2026 年 10 月 24 日核查)
  • 包含 CWE-287 和 CWE-1287 分类的 GitHub Advisory Database 条目:https://github.com/advisories/GHSA-f9x3-7vq9-cph6 (2026 年 10 月 24 日核查)
  • 12.x 分支的修复提交,日期为 2026 年 8 月 24 日:https://github.com/laravel/framework/commit/9b21ce0a9bb2b2599978bc0b0df4abea952ad31c (2026 年 10 月 24 日核查)
  • 13.x 分支的修复提交,日期为 2026 年 8 月 24 日:https://github.com/laravel/framework/commit/8dbb08bccac1be095dba4a9d259a419620f1e8a5 (2026 年 10 月 24 日核查)
  • v12.68.0 版本,2026 年 8 月 25 日发布:https://github.com/laravel/framework/releases/tag/v12.68.0 (2026 年 10 月 24 日核查)
  • v13.29.0 版本,2026 年 8 月 25 日发布:https://github.com/laravel/framework/releases/tag/v13.29.0 (2026 年 10 月 24 日核查)
  • v11.56.1 标签中已修复的 TokenGuard 源码:https://raw.githubusercontent.com/laravel/framework/v11.56.1/src/Illuminate/Auth/TokenGuard.php (2026 年 10 月 24 日核查)
  • v12.67.0 中存在漏洞的 TokenGuard 源码:https://raw.githubusercontent.com/laravel/framework/v12.67.0/src/Illuminate/Auth/TokenGuard.php (2026 年 10 月 24 日核查)
  • 带 Docker 实验环境的公开概念验证,2026 年 10 月 22 日发布:https://github.com/tajulasri/-CVE-2026-114509-POC (2026 年 10 月 24 日核查)
  • 包含 token guard 的 Laravel 7.x 骨架身份验证配置:https://raw.githubusercontent.com/laravel/laravel/7.x/config/auth.php (2026 年 10 月 24 日核查)
  • 包含 token guard 的 Laravel 6.x 骨架身份验证配置:https://raw.githubusercontent.com/laravel/laravel/6.x/config/auth.php (2026 年 10 月 24 日核查)
  • laravel/framework 的 Packagist 下载统计:https://packagist.org/packages/laravel/framework.json (2026 年 10 月 24 日核查)