先に要点
- Laravel のフレームワーク本体に、認証を回避される重大な脆弱性(CVE-2026-114509)が見つかりました。トークン方式の API の認証を担う TokenGuard が、細工された入力を文字列として確かめないことが原因です。深刻度は CVSS v4 で 9.2(Critical)とされています。
- 対象になるのは、Laravel 13.29.0 未満(13.x)と 12.68.0 未満のうち、標準のトークン方式のガード(driver が
token)を使い、データベースが MySQL または MariaDB の構成です。成功すると、他のユーザーのトークンを知らなくても、そのユーザーとして認証されます。 - 修正版は13.29.0 と 12.68.0(2026年8月25日公開)で、いまの最新は 13.39.0 です。公式が示している対処は更新だけです。Laravel 11 以前は公式のセキュリティ修正の期間が終わっていて、このアドバイザリにも修正版が書かれていません。
- 確かめるのは3点だけです:Laravel の版、config/auth.php に
tokenのガードがあるか、データベースの種類。Sanctum や Passport、セッション認証だけのアプリは、このアドバイザリの対象のガードを通りません。
Laravel で API を作っていて、「TokenGuard の認証回避」という話題を見かけた方向けの記事です。JPCERT/CC も2026年10月28日の Weekly Report で取り上げました。
この記事では、Laravel の GitHub の公式アドバイザリ(GHSA-f9x3-7vq9-cph6)、修正のコミット、Laravel の公式ドキュメント、MySQL の公式ドキュメント(いずれも2026年10月29日に確認)をもとに、何が起きるのか、自分のアプリが対象かの確かめ方、更新の手順、更新できないときに考えることを整理します。MySQL の動きは、使い捨てのコンテナで実際に確かめました。
何が起きるのか:トークンが文字列かを確かめていなかった
TokenGuard は、Laravel に昔からある「リクエストに付いた API トークンでユーザーを探す」認証方式です。リクエストの api_token の値(クエリ文字列、リクエストの本文、Bearer トークンのどれでもよい)を取り出し、users テーブルの api_token 列と照らし合わせて、一致したユーザーとして扱います。
アドバイザリによると、問題は次の流れです。
認証を回避されるまでの流れ(アドバイザリと修正コミットより)
- 1
- 2ガードがそのまま検索に使う
修正前の TokenGuard は、値が空でないことしか確かめず、文字列かどうかは確かめなかった。そのまま、ユーザーを探す処理に渡される。
- 3配列は IN での検索になる
ユーザーを探す EloquentUserProvider は、値が配列だと where ではなく whereIn で検索する。api_token が、渡された配列の値のどれかと一致するユーザーを探すことになる。
- 4
- 5最初に見つかったユーザーとして認証される
検索が意図しないユーザーに一致し、そのユーザーとして認証される。そのガードで守っているすべてのルートで、アカウントを乗っ取られる。
修正前の動きは、公開されている修正コミットで確かめられます。修正は TokenGuard の2か所で、トークンが文字列であることを条件に加えただけの小さなものです(is_string($token))。テストでは、api_token に配列 [0] を渡しても、ユーザーを探す処理が呼ばれないことを確かめています。
MySQL で起きていること:使い捨てのコンテナで確かめた
アドバイザリが「MySQL と MariaDB」と限定しているのは、データベースの比較のしかたの違いによります。MySQL の公式ドキュメントは、「文字列と数値の比較は、浮動小数点数の比較として行われる」と説明しています。
次は、MySQL 8.4 の使い捨てのコンテナに、トークンが入ったテーブルを作って確かめた結果です(実際の運用のデータベースには何もしていません)。
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(20), api_token VARCHAR(80));
INSERT INTO users VALUES
(1,"alice","k3Jx9aQ2mZ"), (2,"bob","Zp81LsTqW0"), (3,"carol","7f2d9a1c55");
-- 正しいトークン → alice が見つかる
SELECT id, name FROM users WHERE api_token = "k3Jx9aQ2mZ" LIMIT 1;
-- 間違った文字列 → 何も見つからない(認証に失敗する)
SELECT id, name FROM users WHERE api_token = "wrong" LIMIT 1;
-- 数値の 0 を IN で渡す → alice が見つかってしまう
SELECT id, name FROM users WHERE api_token IN (0) LIMIT 1;
-- 数値の 0 に一致する行の数
SELECT COUNT(*) FROM users WHERE api_token IN (0); -- 2(alice と bob)
正しいトークン
alice(id 1)が見つかります。ふつうの認証の動きです。
間違った文字列
何も見つからず、認証に失敗します。文字列どうしで比べているためです。
数値の 0 を IN で渡す
alice(id 1)が見つかります。トークンが k3J… や Zp8… のように数字以外で始まる行は、0 に一致する扱いになり、2行が該当しました。
数字で始まるトークン
carol のトークンは 7f2… と数字で始まるので 7 に変換され、0 には一致しませんでした。
つまり、トークンの文字列がどんなに長くランダムでも、強さは関係ありません。比較のしかたそのものが原因なので、トークンが推測されにくいから大丈夫、とは言えません。アドバイザリが MySQL と MariaDB を挙げているのは、この比較のしかたが背景にあるためです(ほかのデータベースでの挙動は、この記事では確かめていません)。
TokenGuard には、トークンを SHA-256 のハッシュにして保存する hash の設定があります。ハッシュにする場合、修正前のコードは配列を hash() に渡すことになり、PHP が型のエラーを出して処理が止まります。つまり認証を回避するのではなく、エラーになる形です。ただし、アドバイザリはこの設定の有無には触れていません。設定を理由に更新を見送らず、版を確認して更新してください。
自分のアプリは対象か:確かめる3点
次の3つがすべて当てはまると、対象です。どれか1つでも違えば、このアドバイザリの対象のガードを通りません。
| 確認すること | 対象になる条件 | 確かめ方 |
|---|---|---|
| Laravel の版 | 13.x で 13.29.0 未満、または 12.x で 12.68.0 未満。11.x 以前も範囲に入る | composer show laravel/framework |
| 認証のガード | config/auth.php の guards に、driver が token のガードがある |
config/auth.php を開く |
| データベース | MySQL または MariaDB | .env の DB_CONNECTION(mysql / mariadb) |
# 1. Laravel の版
composer show laravel/framework | grep versions
# 2. token ドライバーのガードを使っているか(見つかれば対象のガードがある)
grep -n "token" config/auth.php
# 3. そのガードを使うルート(auth:api など)
grep -rn "auth:" routes/ app/
Laravel の公式ドキュメントは、API トークンの認証には Sanctum か Passport を使うことを案内しています。これらは TokenGuard とは別の仕組みで、アドバイザリが対象とするのは TokenGuard です。ただし、古いアプリでは config/auth.php に token のガードが残っていたり、自作のガードが EloquentUserProvider に配列を渡していたりすることもあります。「Sanctum だから大丈夫」と決めつけず、上の3点を実際に確かめてください。
Laravel 11 以前を使っている場合
アドバイザリが示している修正版は 13.29.0 と 12.68.0 の2つだけです。影響を受ける版の範囲は「12.68.0 未満」と書かれているので、Laravel 11 以前も範囲に入りますが、修正版は用意されていません。Laravel の公式のサポート方針では、バージョン 11 のセキュリティ修正の期限は2026年3月12日で、すでに終わっています。11 以前で token ガードを使っているなら、ほかの脆弱性のためにも、サポート中の 12 か 13 への更新を計画します。サポート期限の読み方は EOLとLTSの違い で解説しています。
更新の手順
修正は小さく、同じメジャー版の中の更新(13.x の中、12.x の中)なので、破壊的な変更は入らない方針です。Laravel は、マイナー版とパッチ版には互換性を壊す変更を入れないと説明しています。
Laravel を修正版へ更新する手順
- 1更新前の状態を残す
composer.lock をコミットしてあることを確かめる。更新に失敗したときは、このコミットに戻せる。
- 2composer で更新する
composer.json の制約が ^13.0(または ^12.0)なら、composer update laravel/framework で修正版まで上がる。上がった版を composer show laravel/framework で確かめる。
- 3テストを全件流す
ふだんのテストを流し、認証まわり(API のルート)の動きが変わっていないか確かめる。
- 4本番へ反映して、キャッシュを入れ替える
本番で composer install を流し、設定やビューのキャッシュを作り直す。php-fpm や Octane・キューのワーカーを使っているなら再起動し、古いコードのプロセスを残さない。
- 5版が上がったことを本番で確認する
本番の composer show laravel/framework で、13.29.0 以上(または 12.68.0 以上)になっていることを確かめる。
composer update laravel/framework
composer show laravel/framework | grep versions # 13.29.0 以上 / 12.68.0 以上になっているか
php artisan test
2026年10月29日時点の最新は 13.39.0 です。脆弱性の修正は小さな変更なので、修正版ちょうどまで上げるより、同じメジャー版の最新まで上げるほうが、ほかの修正もまとめて取り込めます。
すぐに更新できないとき
公式が示している対処は、修正版への更新だけです。アドバイザリに回避策は書かれていません。更新を急ぐ前提で、そのうえで次の点を考えます。
token ガードを止められるか
そのガードで守っている API を使っていない(古い機能が残っているだけ)なら、ルートやガードの設定を外すだけでも経路を閉じられます。使っているかは、アクセスログで確かめます。
入口で配列を弾く
公式の暫定策ではなく、この記事の筆者の考えですが、api_token が配列で来たリクエストをミドルウェアで拒否する方法も考えられます。修正の中身(トークンが文字列かの確認)と同じ考え方です。ただし、取り出し方(クエリ文字列・本文・ヘッダー)をすべて覆えているかの確認が要るので、更新までのつなぎとして扱い、更新の計画は止めないでください。
すでに悪用されていないかを確かめる
修正は2026年8月25日に公開されていて、アドバイザリの公表は10月です。その間に更新していなかったアプリは、見直しておく価値があります。確実にできる範囲は限られますが、次の点を確かめます。
悪用の痕跡を確かめる
- 1アクセスログで api_token が配列の形になっていないか探す
Web サーバーのアクセスログに、クエリ文字列で api_token[ のような形のリクエストが無いか検索する。本文(JSON など)で送られた場合はアクセスログに残らない。
- 2
- 3心当たりが無いデータの変更が無いか見る
更新日時が不自然なレコード、身に覚えのない設定変更、エクスポートの履歴などを確かめる。
- 4疑わしければ、トークンを再発行する
悪用された疑いがあれば、影響しうるユーザーのトークンを再発行し、利用者に通知するかを判断する。個人データが含まれる場合は、報告の要否を専門家に相談する。
# アクセスログで、api_token が配列の形で送られたリクエストを探す例
# (URL のかっこは %5B %5D に変換されて記録されることが多いので、両方探す)
grep -E "api_token(\[|%5B)" /var/log/nginx/access.log
ログの場所と形式は環境によって違います。見つからなかったからといって、悪用が無かったとは言い切れません(本文で送られた場合は残らないため)。
今回の学び:入力の型を確かめる
今回の件は、Laravel に限らない設計上の注意を含んでいます。
認証の入力は型まで確かめる
トークンやパスワードのような入力は、「空でない」だけでなく「文字列である」ことまで確かめてからデータベースに渡します。PHP は、クエリ文字列の a[]=1 のような書き方で、配列を簡単に受け取れてしまうためです。
データベースの型の扱いの違い
MySQL は文字列と数値を比べるとき、暗黙に型を変換します。データベースによっては起きないことが、MySQL では起きます。開発環境と本番でデータベースが違うと、テストでは再現しないことがあります。
フレームワークは定期的に更新する
今回の修正は8月に出ていて、10月の公表の前から更新していた人は影響を受けませんでした。CVE の番号が付いてから慌てるより、マイナー版・パッチ版は定期的に上げる運用が安全です。
独自の認証コードを書かない
認証の仕組みは、Sanctum や Passport のように広く使われ、点検されているものを選ぶほうが、自作よりも安全です。
脆弱性の情報の集め方は 脆弱性情報の集め方、API キーとトークンの違いは APIキーとOAuthの違い で解説しています。Laravel の使いどころは Laravelとは?向いている案件 も参照してください。
よくある質問
悪用は確認されていますか?
2026年10月29日時点(KEV の2026.10.28版)で、CISA の「悪用が確認された脆弱性」の一覧には載っていません。ただし、修正の中身は公開されていて、仕組みも単純です。載っていないことを理由に、更新を後回しにしないでください。
Laravel のアプリを使っているだけで、自分は対象ですか?
Laravel を使っているだけでは対象にはなりません。トークン方式の標準のガード(driver が token)を使い、データベースが MySQL または MariaDB で、Laravel の版が修正版より古い、の3つがそろったときです。Sanctum や Passport、セッション認証だけのアプリは、通常は対象のガードを通りません。
13.29.0 より前の版なら、全部危ないですか?
版の範囲は「13.29.0 未満」ですが、上の3つの条件を満たさないアプリは影響を受けません。ただし、版が古いとほかの修正も入っていないため、対象かどうかにかかわらず、サポート中の最新のパッチ版まで上げておくのが基本です。
データベースを PostgreSQL にしていれば大丈夫ですか?
アドバイザリが対象としているのは MySQL と MariaDB で、文字列と数値の比較がゆるい挙動が背景にあります。PostgreSQL などでの挙動は、この記事では確かめていません。データベースだけを理由に更新を見送らず、版を上げてください。
まとめ
- TokenGuard が、トークンが文字列かを確かめていなかったため、MySQL・MariaDB では他のユーザーとして認証されうる脆弱性(CVE-2026-114509、CVSS v4 で 9.2)
- 対象は、13.29.0 未満の 13.x と 12.68.0 未満の 12.x のうち、
tokenガードを使い、MySQL か MariaDB の構成。11 以前には修正版が無い - 公式の対処は更新だけ。修正版は 13.29.0 と 12.68.0(2026年8月25日)で、最新は 13.39.0
- 確かめるのは、版・
config/auth.phpのガード・データベースの3点。アクセスログでapi_token[の形も探す - 認証の入力は、型まで確かめてからデータベースに渡す
参考リンク
- Laravel TokenGuard authentication bypass through non-string API credentials(GitHub Security Advisory GHSA-f9x3-7vq9-cph6)
- CVE-2026-114509(NVD)
- 修正のコミット(laravel/framework、13.x)
- v13.29.0・v12.68.0
- JPCERT/CC Weekly Report 2026-10-28
- Release Notes(Laravel 13.x・サポート方針)・Authentication(Laravel 13.x)
- Type Conversion in Expression Evaluation(MySQL 8.4 Reference Manual)