本文へ移動
技術を、自社の仕事に。
判断と実行を助けるメディア

記事を検索

Nuxtの管理画面の保護がすり抜ける パスの書き方5件と修正版4.6.4

目次 · 7項目

会員だけが見られるページや、社内向けの管理画面。Nuxt で作ったサイトでは、server/middleware で「パスが /admin で始まるなら認証を求める」、routeRules で「/members/** はログインのページへ転送する」といった書き方で守ることがあります。

2026年10月に公開された Nuxt のアドバイザリのうち3件は、まさにこの「パスを見て守る」書き方が、パスの書き方を少し変えるだけで外れるというものです。/%2e/admin や /Admin のように要求すると、守っているはずのページが、サーバーで描画されて返ります。さらに11月2日には、このうち2件の修正が不完全だったとして、//x/admin と %2F の書き方を扱う2件が追加で公開されました。

本記事では、当たる条件と修正版を整理し、修正前後の Nuxt を手元で動かして確かめた結果を示します。Nuxt 3 のままのサイトには修正が出ないものがあるため、上げられない場合の回避策も原文に沿って示します。

5件の内容

5件とも、「ページを描画する側」と「サーバーで守る側」で、パスの解釈が食い違うことが原因です。深刻度はいずれも Moderate(中)です。

アドバイザリすり抜ける書き方外れる保護影響を受ける版修正版
GHSA-9v7c-q64q-vv33/%2e/admin、/x/%2e%2e/adminパスを見る server/middleware、redirect・headers のルール4.6.0〜4.6.24.6.3
GHSA-r99w-jh2x-3mg9(9v7c の修正漏れ)//x/admin同上4.6.0〜4.6.34.6.4
GHSA-vjh5-4x84-xrqp/Admin、/MEMBERS/secretredirect・headers・proxy・ssr のルール4.0.0〜4.6.1、すべての3系4.6.2(3系はなし)
GHSA-6gq8-jgmh-3ffp/users/%61dmin(動的・キャッチオールのルート)appMiddleware などのルール4.0.0〜4.4.3、3.11.0〜3.21.34.4.4・3.21.4
GHSA-f3x9-2chm-73c8(6gq8 の修正漏れ)/admin%2Fsecret(キャッチオールのルート)redirect・headers・appMiddleware などのルール4.0.0〜4.6.3、すべての3系4.6.4(3系はなし)

ドットのセグメント(9v7c):4.6.0 から、Nuxt はページを描画する前にパスの . や ..(%2e のような書き方も含む)を解決するようになりました。一方、server/middleware やサーバー側のルールは、解決する前のパスを見ています。そのため /%2e/admin は /admin のページとして描画されるのに、/admin を見る保護は働きません。Content-Security-Policy などをルートごとに付けている場合、そのヘッダーも付きません。ブラウザはこうしたパスを送る前に解決するため、悪用には「パスを書いたとおりに送る」クライアントが要る、とアドバイザリは書いています。server/api と server/routes の API、definePageMeta で付けたミドルウェア、appMiddleware のルールは対象外です。

先頭の //(r99w):4.6.3 の修正はドットのセグメントだけを対象にしていました。4.6.0 以降の Nuxt は、// で始まるパスをホスト名つきの URL として読むため、//x/admin は /admin のページとして描画されます。サーバー側の判定は //x/admin のまま見るので、9v7c と同じ保護が外れます。こちらはブラウザも書いたとおりに送るため、普通のリンクで届くとアドバイザリは書いています。

大文字・小文字(vjh5):Nuxt のページのルーターは、既定(router.options.sensitive: false)では大文字と小文字を区別しないため、/Admin でも /admin のページが出ます。ところがサーバー側のルールは大文字・小文字を区別して照合していました。'/admin/**': { redirect: '/login' } と書いても、/Admin/secret ではページが描画されてしまいます。

パーセントエンコード(6gq8):app/pages/users/[id].vue のような動的なルートで、'/users/admin': { appMiddleware: 'auth' } のように特定のパスにだけルールを付けている場合です。/users/%61dmin(%61 は a)と要求すると、ページは id が admin として描画されるのに、/users/admin のルールが一致せず、auth が動きません。4.4.4・3.21.4 以降は依存の部品がパスを正規化するため既定の構成では起きませんが、依存の版を古く固定している場合は当たりうるとされています。比較そのものは 4.5.2・3.21.11 で直っており、アドバイザリはこれらへの更新を勧めています。

エンコードしたスラッシュ(f3x9):6gq8 の修正は、%2F をわざとエンコードしたまま残していました。そのため [...slug].vue のようなキャッチオールのルートでは、/admin%2Fsecret のようにスラッシュを %2F と書くと、/admin/** の redirect などのルールが外れます。こちらもブラウザは書いたとおりに送ります。保護されたページの中身が出るかどうかは、ページがパスをどう読むかによる、とアドバイザリは書いています。

Nuxt 3 には修正が出ないものがある

Nuxt の公式サイトのロードマップには、「Nuxt 3 は2026年7月31日にサポートを終了し、バグ修正やセキュリティの修正はもう出ない」と書かれています。3系の最新の版は 3.21.11(8月5日公開)で、その後は出ていません。大文字・小文字の件(vjh5)と、エンコードしたスラッシュの件(f3x9)は、アドバイザリでも Nuxt 3 には修正が出ないとされています。Nuxt 3 のままのサイトは、後述の回避策で守るか、Nuxt 4 への移行を検討することになります。ドットのセグメントと先頭の // の件(9v7c・r99w)は、アドバイザリによれば、Nuxt 3 と 4.5 系以前では該当する要求が 404 になるため対象外です(編集部では試していません)。

手元で確かめた結果

2026年11月11日に、最小のアプリを作って2回に分けて確かめました。どちらも nuxt build したものを localhost で起動し、curl に --path-as-is(パスを書いたとおりに送る)を付けて要求しています。1回目と2回目は別の環境です。

パスの書き方を変えた要求の結果を、Nuxtの版ごとに4つの箱で示した図。localhostでパスを書いたとおりに送るcurlを使い、2026年11月11日に2回実行した結果。4.6.1では/%2e/adminと/Members/secretが200で返った。4.6.3では/%2e/adminが404、/Members/secretが/loginへの307になったが、//x/adminは200で管理画面が返り、/admin%2Fsecretは200でキャッチオールのページが描画された。4.6.4では//x/adminが404、/admin%2Fsecretが/loginへの307だった。4.6.3にdouble-slash.tsとencoded-slash.tsの2つの回避策を入れた構成では、//x/adminも/admin%2Fsecretも404だった

1回目:ドットのセグメントと大文字(4.6.1・4.6.3)

環境は Linux、Node.js v22.23.4 です。/admin のページ、server/middleware で event.path が /admin で始まると 401 を返す処理、'/members/**': { redirect: '/login' } のルールを持つ構成で、6種類の要求を比べました。

  • 4.6.1:/admin は 401 でしたが、/%2e/admin と /x/%2e%2e/admin は 200 で管理画面が返りました。/members/secret は /login へ転送(307)されたのに、/Members/secret と /MEMBERS/secret は転送されず、200 でページが返りました。
  • 4.6.3:ドットのセグメントの要求は 404、大文字の要求は /login へ転送(307)されました。
  • 4.6.1 に回避策を入れた構成:下の回避策のうち、大文字・小文字とドットのセグメントの2つを入れると、ドットのセグメントの要求も /Members/secret も 404 になりました。

1回目では、//x/admin と %2F の書き方は送っていません。

2回目:先頭の // と %2F(4.6.3・4.6.4)

環境は macOS(Darwin arm64)、Node.js v22.23.4 です。11月2日に追加された2件の書き方を、2つのアプリで比べました。

  • アプリA:1回目と同じ構成です。1回目の6種類に、//x/admin、///x/admin、//x/members/secret を加えました。
  • アプリB:ルートにキャッチオールのページ(app/pages/[...slug].vue)を置き、'/admin/**' と '/members/**' に redirect: '/login' のルールを付けた構成です。/admin%2Fsecret、/admin%2fsecret、/members%2Fsecret、//x/admin/secret などを要求しました。

結果は次のとおりです。

  • 4.6.3:アプリA では、//x/admin と ///x/admin が 200 で管理画面を返し、//x/members/secret も転送されず 200 でページを返しました。アプリB では、/admin%2Fsecret、/admin%2fsecret、/members%2Fsecret が転送されずに 200 を返し、キャッチオールのページが admin/secret を1つの要素として描画しました。アプリA の6種類の要求の結果は、1回目の 4.6.3 と同じでした。
  • 4.6.4:アプリA の // で始まる3つの要求は 404 になりました。アプリB の %2F を含む3つの要求は /login へ転送(307)されました。アプリA で1回目と同じ6種類の要求を送った結果は、4.6.3 と同じでした。
  • 4.6.3 に回避策を2つ入れた構成:下の先頭の // とエンコードしたスラッシュの回避策(double-slash.ts と encoded-slash.ts)を入れると、アプリA・B とも、// で始まる要求と %2F を含む要求は 404 になりました。

ただし、キャッチオールのページがあるアプリB では、//x/admin/secret の結果が違いました。4.6.3 では 200 が返り、redirect のルールは働きませんでした。このときサーバーで描画した本文は空で、ページの中身は出ていませんでした(ページのデータに記録されたパスは /admin/secret)。ブラウザで開いたときの動きは確かめていません。4.6.4 でも 200 が返り、キャッチオールのページが /x/admin/secret を4つの要素として描画しました。ルールは当たりませんが、ページはこの要求を admin/secret ではなく /x/admin/secret として受け取っています。つまり「4.6.4 では // で始まる要求が 404 になる」は、キャッチオールのページがないアプリA での結果です。

試していないこと

4.6.1 での // と %2F の書き方、パーセントエンコードの件(6gq8)、Nuxt 3 での動きと回避策、「Nuxt 3 と 4.0〜4.5 系では // やドットのセグメントの要求が 404 になる」というアドバイザリの記述は確かめていません。ブラウザでの動き、Vercel や Netlify の設定に変換されたルールも試していません。

更新と回避策(上げられない場合)

まず、4.6.4 以上(11月11日時点の最新)に上げるのが基本です。npm ls nuxt で実際に入っている版を確かめます。11月11日の時点では、追加の2件は GitHub Advisory Database にまだ載っておらず、npm audit が使う npm registry の問い合わせ先に 4.6.3 を送っても、該当するアドバイザリは返りませんでした。npm audit で何も出ないことを、4.6.4 に上げなくてよい理由にはしないでください。

4.6.4 は、Nuxt の8件のアドバイザリを直すセキュリティの更新です。本番でのエラーの詳細の扱いなど、5つの動作の変更がリリースノートに挙がっているため、上げる前に目を通します。

上げられない場合、アドバイザリは次の回避策を示しています。

大文字・小文字の件:nuxt.config で、ページを大文字・小文字を区別して照合するようにします。既存のリンクやURLに大文字が混ざっていると、そのページは見つからなくなるため、事前に確かめます。

export default defineNuxtConfig({
  router: { options: { sensitive: true } },
})

ドットのセグメントの件:他のミドルウェアより前に動く server/middleware で、. や .. のセグメントを含む要求を 404 にします。ミドルウェアはファイル名の順に動くため、0. で始まる名前にしています。

// server/middleware/0.dot-segments.ts
export default defineEventHandler((event) => {
  if (/(?:^|\/)\.{1,2}(?:\/|$)/.test(event.path.split('?')[0])) {
    throw createError({ statusCode: 404 })
  }
})

アドバイザリは、event.path の代わりに getRequestURL(event).pathname を見るだけでは、パーセントエンコードが残るため不十分だとも書いています。

この2つの回避策は、1回目の検証で 4.6.1 に入れて動かしました(結果は前の節のとおり)。そのとき // と %2F の書き方は送っていません。アドバイザリの影響範囲では、4.6.0〜4.6.3 は先頭の // の件にも、4.6.3 以前と Nuxt 3 はエンコードしたスラッシュの件にも当たります。そのため、回避策で守る場合は、この2つだけでは足りず、次の2つも入れることになります。

先頭の // の件:// で始まる要求を 404 にします。

// server/middleware/double-slash.ts
export default defineEventHandler((event) => {
  if (event.path.startsWith('//')) {
    throw createError({ statusCode: 404 })
  }
})

エンコードしたスラッシュの件:パスに %2F を含む要求を 404 にします。@scope%2Fname のようなパッケージ名を受け取る API など、正しい要求も断るため、必要ならルールで守っているパスの先頭だけに絞ります。

// server/middleware/encoded-slash.ts
export default defineEventHandler((event) => {
  if (/%2f/i.test(event.path.split('?')[0])) {
    throw createError({ statusCode: 404 })
  }
})

2回目の検証では、4.6.3 にこの2つの回避策を入れると、// で始まる要求と %2F を含む要求は 404 になりました。通常の /admin/secret と /members/secret は /login への転送(307)のままで、アプリA の /admin も 401 のままでした。パスを正規化してから転送するリバースプロキシや CDN を前に置くことも、食い違いを解消するとされています。

守り方そのものを見直す(編集部の提案)

ここからは編集部の提案です。5件に共通するのは、「URL の文字列で守る」書き方の弱さです。大文字・小文字の件のアドバイザリには、ページに直接付けたミドルウェア(definePageMeta({ middleware: 'auth' }))や、ページのデータを取る処理での確認は影響を受けない、と書かれています。ドットのセグメントと先頭の // の件でも、definePageMeta で付けたミドルウェアは対象外です。一方で、ドットのセグメントの件のアドバイザリは、server/middleware でパスを判定するなら、ページのルーターが大文字・小文字を区別しないことも考えるよう書いています。

  • 認証が必要なページは、ページ側のミドルウェアと、データを返すAPI側の確認の両方で守る。
  • server/middleware で自分でパスを判定している場合は、/Admin のような大文字の書き方も考える。
  • Content-Security-Policy などのセキュリティのヘッダーは、ルートごとではなく、すべての応答に付ける。
  • 保守の点検の手順に、/%2e/admin、//x/admin、%2F、大文字の書き方で守っているページを要求する確認を加える。

同じ時期には、サーバーアイランドで useHeadSafe の安全な処理が効かない件(GHSA-xfq5-w38q-pc2x、10月16日、4.6.2 で修正、Nuxt 3 は修正なし)も公開されています。Nuxt Content(@nuxt/content)では、10月21日に、認証なしで呼べる問い合わせの API(/__nuxt_content/:collection/query)について深刻度 High の3件(サービス妨害の GHSA-xxrh-5hgg-gj4h ほか)が公開され、3.15.2 以上ですべて修正済みです。Nuxt Content を使うサイトは、あわせて版を確かめてください。

フレームワークの脆弱性に、保守の契約の中でどう対応するかは、パッチ適用の時間枠と保守契約の記事で扱いました。Next.js の場合の確かめ方は、Next.jsの10月のアドバイザリの記事を参考にしてください。

確認した範囲

2026年11月11日に、GitHub Advisory Database と、nuxt/nuxt・nuxt/content のリポジトリの Security Advisories(GitHub API で取得)、nuxt/nuxt の v4.6.4 のリリースノート、nuxt.com のロードマップ、npm registry の公開日時と npm audit の問い合わせ先の応答を確認しました(資料調査)。あわせて、同じ日に最小のアプリを localhost で2回動かしました(実機検証。手順と出力は記事の根拠記録に保存)。1回目は Linux・Node.js v22.23.4 で、Nuxt 4.6.1・4.6.3 と、4.6.1 に大文字・小文字とドットのセグメントの回避策を入れた構成に、パスの書き方を変えた6種類の要求を送りました。2回目は macOS(Darwin arm64)・Node.js v22.23.4 で、Nuxt 4.6.3・4.6.4 と、4.6.3 に先頭の // とエンコードしたスラッシュの回避策を入れた構成に、// と %2F の書き方を加えて送りました。2回目はキャッチオールのページがあるアプリでも比べています。4.6.1 での // と %2F、パーセントエンコードの件、Nuxt 3 と 4.0〜4.5 系、ブラウザでの動き、Vercel や Netlify などのデプロイ先は試していません。

Nuxt で作ったサイトの更新や、Nuxt 3 からの移行、会員ページの守り方の見直しは、グリームハブの開発・AI・自動化のご相談で承っています。サイトの構成によって進め方が変わるため、お問い合わせからご相談ください。

参考資料

この記事を共有XFacebook
鈴木 翔

技術の可能性に魅了され、学生時代からプログラミングとデジタルアートの分野に深い関心を持つ

この記事のテーマを、自社の次の一歩へ

Webサイトで、実現したいことから。

使う人の目的、必要な機能、更新の体制を整理し、制作・改善で最初に取り組むことを考えます。

  • サイトの目的
  • 機能と使いやすさ
  • 公開後の運用
Web制作・改善を相談する

構想段階からご相談いただけます。この記事の情報を相談フォームに引き継ぎます。

最新記事をメールで受け取る・Web制作ガイドを読む
無料ダウンロード

Web制作 費用・発注・集客 完全ガイド【2026年版】

費用相場・制作会社の選び方・集客戦略をPDFにまとめました。

The PDF and newsletter emails are currently in Japanese.

メルマガにも登録されます。いつでも解除可能です。