はじめに
セキュリティ関連対応に追われている @___nix___ です。
背景
2026/10/19 に Laravel からこんなアドバイザリが出ました。
MySQL / MariaDB 環境で token guard を使っていると、API トークンを知らなくても既存ユーザーとしてログインできてしまう、という Critical なやつです。
影響範囲は < 12.68.0 と < 13.29.0 で、修正版は 12 と 13 しか載っていません。
範囲には下限が無いので 11 以下も数字の上では含まれますが、じゃあ 11 以下は修正版があるの?全部ダメなの?と聞かれて、答えられなかったので調べてみました。
概要
先に答えを書いておきます。
11 以下も同じコードを持っています。11 系は v11.56.1 でこっそり(?)修正されていて、10 系以下では修正を確認できませんでした。
調べたのは laravel/framework のソースだけです。実際に攻撃が通るかは試していないので、「コード上こうなっている」という話として読んでくださいね。
詳細
問題の箇所は src/Illuminate/Auth/TokenGuard.php です。11.x の修正コミットを見るとこうなっています。
$ git show 5056ca5379 -- src/Illuminate/Auth/TokenGuard.php
@@ -81,7 +81,7 @@ class TokenGuard implements Guard
$token = $this->getTokenForRequest();
- if (! empty($token)) {
+ if (is_string($token) && ! empty($token)) {
$user = $this->provider->retrieveByCredentials([
$this->storageKey => $this->hash ? hash('sha256', $token) : $token,
]);
@@ -122,7 +122,7 @@ class TokenGuard implements Guard
*/
public function validate(array $credentials = [])
{
- if (empty($credentials[$this->inputKey])) {
+ if (! is_string($credentials[$this->inputKey] ?? null) || empty($credentials[$this->inputKey])) {
return false;
}
修正前は「空でなければ OK」だったので、user() で hash を使っていない場合と validate() では、配列のような文字列以外の値も検索条件に渡っていました。
標準の provider(Eloquent / Database)は値が配列だと whereIn を組み立てるので、MySQL / MariaDB の緩い型変換と組み合わさると、トークンと関係ない行に一致してしまう、という流れのようです。
修正は user() と validate() の両方に is_string を足しただけ。1行ずつです!
調査
1. 修正がどのタグから入ったか
13・12・11 の各ブランチに同じ修正コミットがありました。含まれる最初のタグはこちら。
$ git tag --contains 5056ca5379
v11.56.1
v11.57.0
13 系は v13.27.0、12 系は v12.68.0 からです。どれも 2026/08/25 のリリースでした。
13 系はアドバイザリだと >= 13.29.0 が修正版ですが、コード上は v13.27.0 で既に直っています。(何故?)
理由は分からなかったので、更新するなら素直に 13.29.0 以上にしておきましょう。
2. 古い系列の最新タグ
10 系の最新は v10.48.29…と思いきや、2026/08/11 に v10.50.3 が出ていました!9 系も同じ日に v9.52.22 が出ています。
なのでここに修正が入っていないかも見ておきます。
$ for t in v11.56.0 v11.56.1 v10.50.3 v9.52.22 v8.83.29 v7.30.7 v6.20.45; do
> printf '%-10s ' "$t"; git show "$t:src/Illuminate/Auth/TokenGuard.php" | grep -F 'empty($token)' | head -1
> done
v11.56.0 if (! empty($token)) {
v11.56.1 if (is_string($token) && ! empty($token)) {
v10.50.3 if (! empty($token)) {
v9.52.22 if (! empty($token)) {
v8.83.29 if (! empty($token)) {
v7.30.7 if (! empty($token)) {
v6.20.45 if (! empty($token)) {
修正の2週間前にリリースされた 10 / 9 系にも入っていません。惜しい!
3. 全タグを総なめ
最新タグだけでは途中の版が分からないので、5〜11 系の正式リリースのタグ(ベータ等は除く)を全部見てみます。
$ cat sweep.sh
for t in $(git tag | grep -E '^v?(5|6|7|8|9|10|11)\.[0-9]+\.[0-9]+$' | sort -V); do
src=$(git show "$t:src/Illuminate/Auth/TokenGuard.php" 2>/dev/null) || continue
if grep -qF 'is_string($token)' <<< "$src"; then echo "$t fixed"; else echo "$t no-check"; fi
done | awk '{split($1, v, "."); sub(/^v/, "", v[1]); c[v[1] " " $2]++} END {for (k in c) print k, c[k]}' | sort -V
$ bash sweep.sh
5 no-check 276
6 no-check 123
7 no-check 81
8 no-check 154
9 no-check 106
10 no-check 124
11 fixed 2
11 no-check 109
※ TokenGuard が無い 5.1 以前は continue で飛ばしています。
11 以下で今回の型チェックを持つのは v11.56.1 と v11.57.0 の2つだけでした!(取得した時点の正式タグでの話です)
5 系の 276 個には注意があって、provider が whereIn を使うようになったのは v5.6.26 からです。v5.2.0〜v5.6.25 の 192 個は型チェックが無いのは同じですが、検索の組み立て方が違うので、今回と同じ流れで通るかは見ていません。
| 範囲 | タグ数 | 状態 |
|---|---|---|
| v11.56.1 / v11.57.0 | 2 | 今回の型チェックあり |
| v11.0.0〜v11.56.0 | 109 | 今回と同じ経路あり |
| 6〜10 系(全版) | 588 | 今回と同じ経路あり・修正確認できず |
| v5.6.26〜v5.8.38 | 84 | 今回と同じ経路あり |
| v5.2.0〜v5.6.25 | 192 | 型チェック無し・経路は未評価 |
4. hash を有効にしていれば安心?
token guard には 'hash' => true の設定があって、user() ではトークンを hash('sha256', ...) に通します。PHP 8 なら配列を渡した時点で TypeError になるので、user() 側は止まります。
ところが validate() は hash の設定を見ずに、受け取った値をそのまま provider に渡しています。
validate() は true / false を返すだけなのでログインまではしませんが、アプリ側でこの結果を使って認証の判断をしている場合、hash の設定はここでは効かず、文字列以外の値が provider に渡る経路が残ります。実際に影響が出るかは DB やアプリの使い方次第で、ここでは試していません。
あと、TokenGuard には strict_types の宣言が無いので、整数などのスカラー値は hash() に渡すと文字列に変換されます。文字列以外が全部 TypeError で弾かれるわけではないですね。
対応
まず config/auth.php で 'driver' => 'token' の guard を探しましょう。Sanctum や Passport だけなら、この件の TokenGuard は通りません。
使っていたら、系列ごとにここまで上げます。
11 系: 11.56.1 以上
12 系: 12.68.0 以上
13 系: 13.29.0 以上
10 系以下は修正版が見つからなかったので、修正の入った系列への移行を考えることになります。
すぐ上げられない場合は、TokenGuard が認証する前に、guard の input_key(既定は api_token)がクエリ・フォーム・JSON のどこから来ても「あるなら文字列であること」を確認しておきましょう。認証の middleware より後で見ても間に合いません。
validate() を自分で呼んでいる箇所があれば、渡す値にも同じチェックを入れておきます。両方を確実に塞ぐなら、やっぱり更新が一番ですね。
アドバイザリの範囲は < v12.68.0 と書かれているので、修正済みの 11.56.1 / 11.57.0 も範囲上は含まれてしまいます。composer audit で引っかかっても、TokenGuard のコードを見れば修正済みかどうか確認できます。(実際の composer audit の出力は確認していません)
終わりに
表に載っていない 11 は 11.56.1 からちゃんと型チェックが入っていて、10 以下の正式タグでは同じ修正を確認できませんでした。
アドバイザリの表だけで判断せず、手元のバージョンのソースを一度覗いてみると安心ですね。
もっと良い調べ方がある方は是非教えてくださいませ。
一言
今の勢いだと何をやっておくというのも大事ですが、それ以上に「狙われない」ことの方が重要になってる気がしますね。狙われたら最後って気がしてます。(本来はダメなんですけどね)
もし気に入って頂けたらここでのいいね&フォローとX( @___nix___ )でのフォローもお願い致します。(少し元気を貰えますw)