Laravel Token Guardの認証バイパス(CVE-2026-114509)
LaravelはCVE-2026-114509をクリティカル、CVSS v4.0 9.2と評価しています。MySQLまたはMariaDB上の旧式のtoken APIガードでは、未認証の攻撃者が有効なAPIトークンを知らなくても、{"api_token": [0]}のようなJSONボディを送るだけで既存ユーザーとしてサインインできます。12.68.0と13.29.0で修正済みで、11.56.1タグにも修正が含まれます。2026年10月22日から公開PoCが存在しますが、実環境での悪用は確認されていません。
要点
- Laravel組み込みのトークンガードである
token認証ドライバーは、usersテーブルでapi_tokenの値を検索してリクエストを認証します。修正前はトークンが文字列か確認していなかったため、細工したJSONボディによって、検索が攻撃者の選んだ値を含むWHERE api_token IN (...)クエリに変わりました。 - MySQLとMariaDBでは、
{"api_token": [0]}のようなボディにより、データベースが保存された各テキストトークンを数値に変換します。その結果、英字で始まるトークンは0と等しいと判定されます。クエリはそのユーザーを返し、有効なトークンや総当たり攻撃なしで、そのユーザーとしてリクエストが認証されます。 - Laravelは重大度をクリティカル、CVSS v4.0 9.2と評価し、2026年10月22日に概念実証(PoC)が公開されています。2026年10月24日時点でCISA KEVカタログには登録されておらず、実環境での悪用も確認されていません。
- すぐに12.68.0、13.29.0、または11.56.1タグにアップグレードするか、文字列以外の
api_token値を拒否するミドルウェアを追加してください。修正は2026年8月25日にリリース済みのため、それ以降の依存関係の更新によって、すでに保護されている可能性もあります。
影響を受けるか
以下の条件をすべて満たす場合に影響を受けます。
config/auth.phpが'driver' => 'token'のガードを定義し、エンドポイントがそれを使用している(対象ガードがトークンガードであるauth:apiまたはauth:<guard>のルート)。- フレームワークが各ブランチの修正バージョン未満。Laravel 12では12.68.0未満、Laravel 13では13.29.0未満、Laravel 11では11.56.1タグ未満、またはサポート終了済みのブランチ(10.x以前)。
- usersテーブルがMySQLまたはMariaDBにある。公開PoCによれば、PostgreSQLは型が一致しない比較を拒否し、SQLiteは行を返しません。
- PHP 8でガードに
'hash' => trueを設定していない。Laravelのガード生成処理は、キーがなければhashをfalseにし、トークンガードを含むスケルトン設定も明示的にfalseにしています。
Laravel Sanctum、Passport、その他のtokenドライバー以外のガードを使っていれば影響はありません。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以前 | トークンガードを備える全バージョン | なし(サポート終了) |
実際のデプロイ環境を確認してください。
# 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';"
ガードの履歴も重要です。Laravel 6.xと7.xのスケルトンは、デフォルトで'hash' => falseのトークンガードを含んでいました。一方、8.xと9.xのスケルトンにはAPIガード自体がありません。composer updateはconfig/auth.phpを上書きしないため、古いスケルトンからアップグレードしたアプリケーションには、気付かないままトークンガードが残っている可能性があります。公開PoCはこの設定の履歴を説明しており、6.xと7.xのスケルトンファイルでも確認できます。Packagistによるlaravel/frameworkの直近1か月のダウンロード数は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の推奨に従い、文字列以外のトークンがガードに届く前に拒否するミドルウェアを追加します。
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);
}
このミドルウェアはこの種の攻撃を防ぎます。ただし、トークンガードに到達できるすべてのルートやグループに登録する必要があり、根本の型混同は修正しません。api_tokenが配列であるJSONやクエリ値を拒否するWebアプリケーションファイアウォールのルールでも同じ効果が得られ、PoCにはそのためのクエリとJSONのパターンが掲載されています。恒久的な修正は、アップグレードするか、この検索処理を使わないLaravel SanctumにAPIを移行することです。
根本原因
トークンガードは空でない認証情報をすべて文字列として扱い、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]が後続処理に流れます。さらに2つの事実によって、この連鎖が完成します。
src/Illuminate/Auth/AuthManager.php(166行目)のガード生成処理は$config['hash'] ?? falseを渡すため、hashキーがないガード設定ではトークンがそのまま使われます。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);
}
結果として、ガードの認証情報は「比較するトークン」から「一致が許されるトークンのリスト」に変わります。2026年8月24日付の、どちらも「fix token retrieval」と題されたパッチコミットは、検索前に文字列であることを要求し、型混同を解消しています。
- if (! empty($token)) {
+ if (is_string($token) && ! empty($token)) {
ハッシュ化せずに認証情報をretrieveByCredentialsに渡す2つ目の入口であるvalidate()にも、同じチェックが加わっています。
悪用の仕組み
公開PoCが示すとおり、攻撃にはJSONボディを含むリクエストが一つ必要です。
- 攻撃者は
Content-Type: application/jsonとボディ{"api_token": [0]}を付けてPOST /api/userを送ります。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のラボでは4人のユーザーを用意しており、有効なトークンを一度も送っていないのに、最初に作成した管理者として認証されたレスポンスが返ります。- そのセッション内の後続リクエストはすべてそのユーザーの権限で実行されるため、ガードが保護するすべてのルートで、バイパスがアカウント乗っ取りにつながります。
PoCにあるそのままの攻撃リクエストです。同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["そのユーザーとして
リクエストが認証される"]
攻撃者に可能なこと
攻撃者はトークンガードの既存ユーザーの一人として認証され、そのユーザーのAPIアクセスが許すすべての操作を取得します。APIルートを介した読み取り、書き込み、削除と、ルート層から到達できるデータが対象です。アドバイザリとPoCが示す条件と制約は次のとおりです。
- 認証情報、ユーザー操作、APIへの到達性以外のネットワーク上の位置は不要
- 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を返します。これはガードが元から意図していた結果です。この修正により、トークンガード経由で文字列以外の認証情報がretrieveByCredentialsに到達できなくなり、ガード層では問題が完全に解消します。retrieveByCredentials自体は変更されておらず、意図的に使用するプロバイダー向けに、配列の認証情報を引き続きINリストへ変換します。そのため、認証検索に配列を渡すアプリケーションコードには固有のリスクが残ります。
侵害されたかを確認する方法
Laravelは侵害指標を公開していません。PoCは悪用時のパターンを記載しています。正規のクライアントは文字列トークンを送るため、これらのパターンは明確です。
- アクセスログのクエリ文字列:
api_token[]=0とapi_token[0]=0 - アプリケーションログやWAFログのJSONボディ:
"api_token":[0]と"api_token":[false] - パターンにかかわらず確認すべき異常:対応するトークンの発行がないセッションからトークンガードのルートへ成功レスポンスが返ること、新しい送信元アドレスから管理者アカウントが操作していること
経緯
- 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:Dockerラボ付きのPoCがGitHubで公開。
- 2026-10-23:GitHub Advisory Databaseがアドバイザリを公開・レビューし、CWE-287とCWE-1287を追加。
- 2026-10-24:本記事を公開。
よくある質問
Laravel SanctumやPassportも影響を受けるか?
いいえ。Sanctumは常に文字列であるBearerヘッダーを読み取り、SHA-256ハッシュを別のpersonal access tokensテーブルと比較するため、配列が検索に届きません。Passportは異なる保存方法と検索を用いたOAuth2フローで認証します。アドバイザリの対象は組み込みのトークンガードだけです。
MySQLまたはMariaDBでしか攻撃が機能しない理由は?
配列の認証情報は最終的に数値0として比較されます。MySQLとMariaDBは、その比較のためにテキスト列を数値に変換し、実際のトークンの多くを0にします。PoCのラボが示すとおり、同じクエリに対してPostgreSQLは型エラーを起こし、SQLiteの型アフィニティでは行が返りません。ガードの不具合はどの環境にも存在しますが、悪用可能な比較には暗黙の型変換が必要です。
'hash' => trueで保護されるか?
PHP 8では、攻撃がエラーになります。hash('sha256', [0])がTypeErrorを投げ、誰かを認証する代わりにリクエストが失敗します。PHP 7ではハッシュ関数が警告を出してnullを返すだけのため、PoCではこの組み合わせも依然として危険としています。ハッシュ化だけに防御を頼っている場合でもアップグレードしてください。6.xと7.xのスケルトン設定に含まれたトークンガードは、'hash' => falseだった点にも注意が必要です。
攻撃者が特定のアカウントを選べるか?
直接は選べません。検索はデータベースが最初に一致した行を返すため、攻撃者は最初に一致するユーザーを取得します。PoCのラボでは最初に作成したユーザーであり、多くの場合は管理者です。攻撃者がJSON配列に複数の数値を入れ、型変換による一致範囲を広げることもできますが、選ばれるのはあくまでデータベースが最初に返すユーザーです。
サポート終了済みのLaravelを使っている場合は?
文字列以外のapi_token値を拒否するミドルウェア、または上記パターンに対応したWAFルールを追加し、PoCが移行先に推奨するSanctumへの移行を計画してください。サポート終了済みブランチには修正が提供されないため、設定されている限りガード自体の脆弱性は残ります。
OstorlabでWebアプリケーションをスキャンし、公開されているLaravelのフレームワークバージョンとトークンガード設定を確認してください。tokenドライバーが設定されたままの旧式のデプロイ環境も対象です。
参考資料
- 2026年10月19日公開のLaravelアドバイザリGHSA-f9x3-7vq9-cph6: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日確認)
- 2026年8月24日付の12.xブランチ修正コミット:https://github.com/laravel/framework/commit/9b21ce0a9bb2b2599978bc0b0df4abea952ad31c (2026年10月24日確認)
- 2026年8月24日付の13.xブランチ修正コミット:https://github.com/laravel/framework/commit/8dbb08bccac1be095dba4a9d259a419620f1e8a5 (2026年10月24日確認)
- 2026年8月25日公開のv12.68.0リリース:https://github.com/laravel/framework/releases/tag/v12.68.0 (2026年10月24日確認)
- 2026年8月25日公開のv13.29.0リリース: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日確認)
- 2026年10月22日公開のDockerラボ付きPoC:https://github.com/tajulasri/-CVE-2026-114509-POC (2026年10月24日確認)
- トークンガードを含むLaravel 7.xスケルトンの認証設定:https://raw.githubusercontent.com/laravel/laravel/7.x/config/auth.php (2026年10月24日確認)
- トークンガードを含む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日確認)