当社のAIエンジンNeutronが、UCバークレーのCyberGymベンチマークで97.1%のスコアを記録しました。 詳細を見る

ディープダイブ

ディープダイブ

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ボディを含むリクエストが一つ必要です。

  1. 攻撃者はContent-Type: application/jsonとボディ{"api_token": [0]}を付けてPOST /api/userを送ります。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のラボでは4人のユーザーを用意しており、有効なトークンを一度も送っていないのに、最初に作成した管理者として認証されたレスポンスが返ります。
  6. そのセッション内の後続リクエストはすべてそのユーザーの権限で実行されるため、ガードが保護するすべてのルートで、バイパスがアカウント乗っ取りにつながります。

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ボディです。

攻撃フロー

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["そのユーザーとして
リクエストが認証される"]

攻撃者に可能なこと

攻撃者はトークンガードの既存ユーザーの一人として認証され、そのユーザーの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日確認)