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