FIXED BEFORE DISCLOSED公表までの時系列
修正版は8月25日に出ていた。公表は約8週間後
-
08/25
12.68.0・13.29.0 などが出る
TokenGuard に「文字列でなければ照会しない」変更
-
10/19
アドバイザリ GHSA-f9x3 を公開
Critical・CVSS 4.0 で 9.2。CVE は未採番
-
10/20
composer audit にはまだ出ない
Packagist の公開 DB に未収録(本記事の確認時点)
30秒でわかる
・何が起きたか:Laravel 脆弱性 GHSA-f9x3-7vq9-cph6(Critical・CVSS 4.0 で 9.2)。本体に組み込まれた TokenGuard で、API トークンを知らない攻撃者が既存の別の利用者として認証されうる
・条件:TokenGuard(config/auth.php で driver が token のガード)で API を守り、DB が MySQL か MariaDB のアプリ
・直った版:laravel/framework 12.68.0、13.x は 13.29.0。どちらも 2026-08-25 に出ていた
・読み違えやすい点:Laravel Passport の別件(GHSA-349c-2h2f-mxf6、CVE-2026-39976)とは別物。CVE は未採番で、composer audit は 2026-10-20 時点でこの件を出さない
まずやること
・Laravel アプリを運用している人:確認コマンドで、composer.lock の laravel/framework の版と、token ドライバーのガードを使っているかを見る
・版が影響範囲なら:TokenGuard を使っていなくても 12.68.0 か 13.29.0 以降へ上げる。同じ日に公表された別の1件も同時に直る
・開発を外部に任せている人:版と、token ガードを使っているかを委託先に確かめる
Laravel は Composer で取り込む依存パッケージの一つです。依存関係から入る攻撃と防御の全体像はサプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説にまとめています。本記事は、この件で自分のアプリが当てはまるかを確かめ、当てはまったら何をするかに絞ります。
Laravel 脆弱性 GHSA-f9x3 の時系列(タイムライン)
日時は GitHub のアドバイザリと、laravel/framework のタグが指すコミットの日時(UTC)です。
| 日時 | 出来事 | 出典 |
|---|---|---|
| 2026-08-25 | TokenGuard の修正コミット「fix token retrieval」(12.x は 8dbb08b、13.x は 9b21ce0)を含む 12.68.0・13.27.0・13.29.0 が出る。11.x にも同じ変更が 11.56.1 で入った | タグとコミット |
| 2026-10-19 | アドバイザリ GHSA-f9x3-7vq9-cph6(Critical)を公開。同じ日に GHSA-5wcm-3v2w-8362(Moderate)も公開 | GitHub のアドバイザリ |
| 2026-10-20 | 本記事の確認時点。CVE は未採番。Packagist の公開データベースに2件とも載っていない。最新版は 12.70.0 と 13.38.0 | アドバイザリ・Packagist API・タグ |
アドバイザリには、悪用の有無と公開された攻撃コードの記載がありません。本記事が確かめた範囲でも、悪用の報告は見当たりませんでした。修正版の公開から公表まで約8週間ありました。12.x・13.x で 8月26日以降に laravel/framework を更新していたアプリは、公表の前に修正版になっています。
何が起きたか:Laravel TokenGuard が文字列でないトークンを照会に渡していた
TokenGuard はリクエストから受け取った API トークンで利用者のテーブルを照会し、一致した行の利用者として認証するガードです。アドバイザリによれば、MySQL/MariaDB の構成では、攻撃者が細工した「文字列でない」資格情報の値が照会の中で誤って扱われ、認証が失敗するはずのところで意図しない利用者の行に一致しえました。
WHAT THE FIX CHANGED修正の中身
照会の前に文字列かどうかを確かめるようにした
12.67.0 まで
TokenGuard::user()
- トークンが空でなければ照会へ
- 型は確かめない
- MySQL/MariaDB で別人の行に一致しうる
12.68.0 から
コミット 8dbb08b
- 文字列で、空でないときだけ照会へ
- validate() も同じ条件で失敗させる
- 文字列でなければ照会しない
修正はガードの2か所で、照会の前に値が文字列かどうかを確かめるものです(コミット 8dbb08b の差分から抜粋)。
- if (! empty($token)) {
+ if (is_string($token) && ! empty($token)) {
$user = $this->provider->retrieveByCredentials([
validate() も同じく、文字列でない値なら照会せずに false を返すようになりました。どの値でどの行に一致するのかは、アドバイザリが公表していないため本記事も書きません。
攻撃者が得るものと、できることは次のとおりです。アドバイザリは影響を「影響を受けるガードで守られたすべてのルートでの、認証回避とアカウントの乗っ取り」としています。
| 攻撃者が得るもの | できること |
|---|---|
| 既存の別の利用者としての認証(トークンは知らないまま) | そのガードで守られた API で、その利用者に許された操作すべて |
| 管理者の行に一致した場合 | 管理用 API があれば、その権限での操作。どの行に一致するかを攻撃者が選べるかは公表されていない |
認証が通った後の認可の作りによって、被害の広さが変わります。利用者ごとのデータの切り分けはIDOR(認可漏れ)の見つけ方|2アカウントのテストとコードレビューで「他人のデータが見える」を潰すで扱っています。
Laravel 認証まわりの別件との見分け方
検索すると、名前の似た別件が混ざって出てきます。Passport にも TokenGuard という同じ名前のクラスがあり、取り違えやすい点です。
| 本件 GHSA-f9x3 | 同日の GHSA-5wcm | Passport の GHSA-349c | |
|---|---|---|---|
| パッケージ | laravel/framework | laravel/framework | laravel/passport |
| 場所 | Illuminate\Auth\TokenGuard |
JSON:API リソースの関連の読み込み | Laravel\Passport\Guards\TokenGuard |
| 何が起きる | トークン無しで別の利用者として認証されうる | 読み取りだけの API への要求で、関連テーブルのデータが消されうる | client_credentials のトークンが無関係の利用者に解決されうる |
| 深刻度 | Critical・9.2(CVSS 4.0) | Moderate・6.5(CVSS 3.1) | High・7.1(CVSS 3.1) |
| CVE | 未採番 | 未採番 | CVE-2026-39976 |
| 影響する版 | 12.68.0 未満、13.x は 13.29.0 未満 | 12.45.0〜12.67.x、13.x は 13.27.0 未満 | 13.0.0〜13.7.0 |
| 修正版 | 12.68.0 / 13.29.0 | 12.68.0 / 13.27.0 | 13.7.1 |
| 公表 | 2026-10-19 | 2026-10-19 | 2026-04-08 |
GHSA-5wcm の修正コミット(12.x は 50a7970)は、12.45.0 で入った JSON:API リソースの関連を読み込む処理を変えています。framework の 2 件はどちらも 12.68.0 で直るので、12.x は更新を一度で済ませられます。
影響を受ける条件:Laravel API 認証に token ドライバーを使っているか
ARE YOU AFFECTED当てはまるか
条件が全部そろうと影響を受ける
-
1.laravel/framework が修正版より前
12.68.0 未満、または 13.x の 13.29.0 未満
-
2.driver が token のガードでルートを守っている
auth:api など。2021年8月までの雛形は api ガードが token だった
-
3.DB が MySQL か MariaDB
アドバイザリが条件に挙げる構成
4つの条件がそろうと影響を受けます。迷いやすいのは2つ目と3つ目です。
・token ガードはいまの雛形には無い:laravel/laravel の config/auth.php は、2021年8月のコミット(v8.6.0)で api ガードの token ドライバーを外し、Sanctum に置き換えた。それより前に作ったアプリを版だけ上げてきた場合は、設定が残っていることがある
・定義と利用は別:ガードは呼ばれたときに作られる。auth:api のミドルウェア、既定のガード(defaults.guard)、Auth::guard('api') のどれかで使っていれば当てはまる
・Sanctum と Passport は別のガード:Sanctum はアドバイザリに挙がっていない。Passport は上の表の別件を確かめる
対策:Laravel アップデートで 12.68.0 か 13.29.0 以降へ
| いまの版 | 上げる先 |
|---|---|
| 12.x(12.68.0 未満) | 12.68.0 以降。2026-10-20 時点の最新は 12.70.0 |
| 13.x(13.29.0 未満) | 13.29.0 以降。同じく最新は 13.38.0 |
| 11.x | アドバイザリは「12.68.0 未満」を影響範囲とし、11.x の修正版を挙げていない。11.x のブランチには同じ変更が 11.56.1 で入っているが、アドバイザリに沿うなら 12.x 以降へ移る |
| 10.x 以前 | 10.x のブランチにはこの変更が入っていない(2026-10-20 時点)。12.x 以降へ移る |
composer.json の制約が ^12.0 や ^13.0 なら、composer update laravel/framework で修正版まで上がります。上げた後は composer.lock をコミットし、本番に配備された版を次の節のコマンドで確かめます。
更新できない間の考え方
・token ガードを使わない:そのガードで守る API を止めるか、Sanctum などへ移す。設計の変更なので、すぐには難しいことが多い
・ガードの手前で拒む:トークンとして読む入力が文字列でない要求を、TokenGuard より前のミドルウェアで拒む。修正と同じ考え方だが、本記事はこの方法を試していない
・WAF で稼ぐ:期限を切った仮想パッチの運用は仮想パッチとは|修正版を当てるまでの数日をWAFで稼ぐ手順と、IDORやカーネルなどWAFでは守れない脆弱性にまとめた
トークンの再発行では塞がりません。攻撃者は相手のトークンを知らずに認証できるので、塞ぐのは本体の更新です。アドバイザリは侵害の痕跡(IOC)を示していません。影響範囲の版で token ガードを公開していたなら、そのガードで守られた API の監査ログを見て、利用者が覚えのない操作をしていないかを確かめます。
自システムの確認コマンド:版と token ガードの使い方を見る
アプリのリポジトリのルート(composer.json のあるディレクトリ)で実行します。どれも読み取りだけのコマンドで、見ているのは「影響を受ける版か」と「token ガードを使う構成か」です。攻撃を受けたかどうかの判定ではありません。
1. laravel/framework の版
git fetch --all --prune
composer show --locked laravel/framework | grep -E '^versions'
git show origin/main:composer.lock | grep -A1 '"name": "laravel/framework"'
php -r '$v = ltrim($argv[1], "v"); $fixed = version_compare($v, "13.0.0", ">=") ? "13.29.0" : "12.68.0"; echo $v, (version_compare($v, $fixed, "<") ? " は影響範囲({$fixed} 未満)" : " は {$fixed} 以降"), PHP_EOL;' "$(composer show --locked laravel/framework | awk '/^versions/ {print $NF}')"
2行目は作業ツリーの composer.lock の版、3行目は配備するブランチの版です(origin/main は自分の配備ブランチに置き換える)。4行目はアドバイザリの修正版と比べた結果を1行で出します。
2. token ガードを使っているか、DB は何か
sed -n "/'defaults' => \[/,/^ \],/p; /'guards' => \[/,/^ \],/p" config/auth.php | grep -E "=> \[|'guard'|'driver'"
grep -rnE "auth:api|guard\(['\"]api['\"]\)|TokenGuard" routes app bootstrap
grep -nE '^DB_CONNECTION=' .env
1行目は既定のガードと、各ガードのドライバーを出します。'driver' => 'token' があれば、その上の行がガード名です。2行目はルートとコードの中で api ガードを使っている箇所を探します。ガード名が api でなければ、2行目の api を自分のガード名に置き換えます。
結果の読み方と、このコマンドの限界
ダミーのアプリ2つ(12.67.0・2021年8月より前の雛形の config/auth.php・auth:api のルート・DB_CONNECTION=mysql の陽性対照と、12.70.0・いまの雛形・auth:sanctum・sqlite の陰性対照)で、掲載したままのコマンドを走らせました。
| コマンド | 陽性対照 | 陰性対照 |
|---|---|---|
| 版(2・3行目) | v12.67.0 |
v12.70.0 |
| 版の比較(4行目) | 12.67.0 は影響範囲(12.68.0 未満) |
12.70.0 は 12.68.0 以降 |
| ガードの一覧 | 'api' => [ の下に 'driver' => 'token' |
session だけ |
| ガードの利用 | routes/api.php:17 の auth:api |
0行 |
| DB | DB_CONNECTION=mysql |
DB_CONNECTION=sqlite |
読むときの注意は3つです。
・陰性でも安全とは限らない:本番の DB 接続は .env でなく環境変数で渡していることがある。パッケージ(vendor/)の中のルートは2行目で探していない。composer.lock と本番に配備された vendor/ がずれていれば、版の結果も外れる
・陽性でも危ないとは限らない:token ガードを定義していても、どこからも呼んでいなければこの経路は通らない。11.56.1 以降の 11.x は、4行目では影響範囲と出るがコードには同じ変更が入っている
・composer audit では分からない:2026-10-20 時点で、12.67.0 の composer.lock に composer audit --locked をかけると別件(GHSA-jh5r-qr3c-85q8)だけが出て、本件と GHSA-5wcm は出なかった。Packagist の公開データベースに載るまでは版を自分で比べる
依存パッケージの警告をどれから直すかの考え方は、npm の例でnpm audit の結果はどれから直す?依存関係の脆弱性を到達性・EPSS・KEVで仕分ける実践手順にまとめています。
影響範囲:Laravel 脆弱性 GHSA-f9x3 の対象になるアプリ
| 対象 | 影響 |
|---|---|
| 修正版より前で、token ガードで API を守り、DB が MySQL/MariaDB | 対象。トークン無しで別の利用者として認証されうる |
| 同じ版・token ガードあり・DB が PostgreSQL や SQLite | アドバイザリの条件の外。ほかの DB で起きないとは書かれていないので、更新する |
| 修正版より前だが token ガードを使っていない | 本件の対象外。GHSA-5wcm(12.45.0 以降)が当てはまりうるので更新する |
| Sanctum だけで API を守っている | アドバイザリに挙がっていない。本体の版は上げる |
| Passport 13.0.0〜13.7.0 | 本件とは別に GHSA-349c の対象。Passport 13.7.1 以降へ |
| 12.68.0 以降・13.29.0 以降 | 本件の対象外 |
参照ソース
・Token-based API guard Auth Bypass(GHSA-f9x3-7vq9-cph6) — 深刻度・CVSS・影響する版と修正版・条件(MySQL/MariaDB)・影響
・Unauthorized Data Destruction(GHSA-5wcm-3v2w-8362) — 同日公表の別件の版と影響
・TokenGuard Authenticates Unrelated User for Client Credentials Tokens(GHSA-349c-2h2f-mxf6) — Passport の別件(CVE-2026-39976)
・fix token retrieval(コミット 8dbb08b) — 12.x の修正の差分
・laravel/laravel の config/auth.php の変更(コミット 226d1bf) — 雛形の api ガードを token から外したコミット





