CVE-2026-45669_vulnerability_report_20260522

CVE-2026-45669 Vulnerability Report (詳細版)

概要

項目 内容
CVE番号 CVE-2026-45669
公開日 2026-05-19
最終更新 2026-05-22
CVSS v3.1 6.1 (Medium) — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
CVSS v4.0 5.3 (Medium) — CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N
CVSS v2.0 4.3 (Medium) — CVSS2#AV:N/AC:M/Au:N/C:N/I:P/A:N
CWE CWE-83 (Improper Neutralization of Script in Attributes in a Web Page)
GHSA ID GHSA-fx6j-w5w5-h468
影響製品 Nuxt (npm)
EPSS 調査時点 (2026-05-22) で公開スコア未確認
CISA KEV なし (2026-05-22時点)
発見者 @Mr-In4inci3le
修正担当者 @danielroe (Nuxtコアメンテナ)

影響を受けるソフトウェアおよびバージョン

技術詳細

脆弱性の概要

Nuxtの navigateTo() 関数を external: true オプションで使用した際、サーバーサイドで生成されるHTMLリダイレクトボディ内の <meta http-equiv="refresh"> タグにおける反射型XSS (Reflected XSS) 脆弱性。

navigateTo(url, { external: true }) が呼び出されると、NuxtはSSRリダイレクト用HTMLレスポンスを生成する。この際、宛先URLのサニタイゼーションが不十分で、二重引用符 (") のみが %22 にエンコードされる。一方、<>&' といったHTML属性において重要な文字はエンコードされずに残る。

攻撃者が navigateTo() に渡されるURLを操作できる場合(例: ?next=?redirect= クエリパラメータ)、content="..." 属性を脱出して任意のHTML/JavaScriptをインジェクションでき、アプリケーションのオリジンでスクリプトが実行される。

根本原因 — コードレベル分析

脆弱なコード: packages/nuxt/src/app/composables/router.ts

// ❌ 脆弱な実装 (3.21.5 以前)
const URL_QUOTE_RE = /"/g
const encodedLoc = location.replace(URL_QUOTE_RE, '%22')

// 生成されるHTML:
// <meta http-equiv="refresh" content="0; url=${encodedLoc}">

問題点: URL_QUOTE_RE = /"/g は二重引用符のみをエンコード。<, >, &, ' がそのまま出力されるため、content="..." 属性から脱出可能。

Locationヘッダーとの不一致: Location ヘッダーは encodeURL() (URLコンストラクタ使用) で適切にエンコードされる一方、HTMLボディ内のサニタイザーはより限定的な処理しか行っておらず、この不一致が脆弱性の根源である。

修正コード: 同ファイル (修正後)

// ✅ 修正後の実装 (3.21.6 / 4.4.6)
const HTML_ATTR_UNSAFE_RE = /[&"'<>]/g
const HTML_ATTR_ENCODE_MAP: Record<string, string> = {
  '&': '%26',
  '"': '%22',
  "'": '%27',
  '<': '%3C',
  '>': '%3E',
}
function encodeForHtmlAttr (value: string): string {
  return value.replace(HTML_ATTR_UNSAFE_RE, c => HTML_ATTR_ENCODE_MAP[c]!)
}

// 適用
const encodedLoc = encodeForHtmlAttr(location)

修正コミット詳細 (6d4051f)

項目 内容
変更ファイル数 3ファイル (router.ts, test/basic.test.ts, test/fixtures/basic/app/pages/navigate-to-external-encode.vue)
追加関数 encodeForHtmlAttr() — HTML属性用の包括的パーセントエンコーダー
削除された正規表現 URL_QUOTE_RE = /"/g (二重引用符のみマッチ)
追加された正規表現 HTML_ATTR_UNSAFE_RE = /[&"'<>]/g (5文字すべてマッチ)
パフォーマンス影響 なし (CodSpeedベンチマークで20件unchanged、3件skipped)

PoC (Proof of Concept)

テストフィクスチャ (修正PRで追加されたテストケース)

<!-- test/fixtures/basic/app/pages/navigate-to-external-encode.vue -->
<script setup lang="ts">
await navigateTo('https://example.com/x><img src=x>&"\'', { external: true })
</script>

テストケース

// test/basic.test.ts
it('encodes HTML-significant characters in external redirect body', async () => {
  const res = await fetch('/navigate-to-external-encode', { redirect: 'manual' })
  const body = await res.text()
  expect(res.status).toEqual(302)
  expect(res.headers.get('location')).not.toContain('<')
  expect(res.headers.get('location')).not.toContain('>')
  const content = body.match(/content="0; url=([^"]*)"/)?.[1] ?? ''
  expect(content).not.toMatch(/[<>&"']/)
  expect(content).toContain('%3C')
  expect(content).toContain('%3E')
  expect(content).toContain('%26')
  expect(content).toContain('%27')
})

攻撃シナリオ

条件: ?next= パラメータを navigateTo() に渡すグローバルミドルウェアが存在するアプリケーション

攻撃リクエスト:

GET /?next=https://evil.example/x><img src=x onerror=alert(document.domain)>

脆弱な状態でのレスポンスボディ:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="refresh" content="0;url=https://evil.example/x><img src=x onerror=alert(document.domain)>">
</head>
<body>Redirecting...</body>
</html>

修正後のレスポンスボディ:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="refresh" content="0;url=https://evil.example/x%3E%3Cimg%20src=x%20onerror=alert(document.domain)%3E">
</head>
<body>Redirecting...</body>
</html>

修正内容

nuxt@4.4.6 および nuxt@3.21.6 (PR #35052) で修正。メタリフレッシュボディにURLを挿入する前に、HTML属性において重要な文字 (&, ", ', <, >) のすべてをパーセントエンコードする encodeForHtmlAttr() 関数が追加された。

ワークアラウンド

即時アップグレードが不可能な場合:

  1. navigateTo(url, { external: true }) に渡す前にユーザー制御URLを検証
  2. 最低限 new URL(input).toString() で正規化し、< または > を含む入力を拒否
  3. 正常なURLにこれらの文字が含まれることはなく、malformed とみなして安全に拒否可能

関連脆弱性

影響度

成功したエクスプロイトにより以下のリスクが発生:

検知方法

  1. 依存関係スキャンで nuxt パッケージのバージョンが 3.21.6 / 4.4.6 未満か確認
  2. コードベースで navigateTo(url, { external: true }) の使用箇所を特定
  3. 該当箇所にユーザー制御入力 (クエリパラメータ等) が渡されているか確認
  4. CDN/リバースプロキシのログで不審な >< を含むリダイレクトURLを監視

参考情報

ソース URL
GitHub Advisory https://github.com/advisories/GHSA-fx6j-w5w5-h468
GitLab Advisory https://advisories.gitlab.com/npm/nuxt/CVE-2026-45669
Tenable https://www.tenable.com/cve/CVE-2026-45669
Resolved Security https://www.resolvedsecurity.com/vulnerability-catalog/CVE-2026-45669
Netlify Changelog https://www.netlify.com/changelog/2026-05-19-nuxt-security-vulnerabilities
修正PR #35052 https://github.com/nuxt/nuxt/pull/35052
修正コミット 6d4051f https://github.com/nuxt/nuxt/commit/6d4051f
修正コミット 1ca9c44 https://github.com/nuxt/nuxt/commit/1ca9c44

推奨対応

  1. 直ちに nuxt@3.21.6 または nuxt@4.4.6 以降へアップグレード
  2. navigateTo() に渡すURLの入力検証を実装
  3. CDN/リバースプロキシの設定でキャッシュキーにクエリ文字列を含めることを確認
  4. navigateTo(url, { external: true }) の使用箇所を監査し、ユーザー制御入力が渡される経路を特定・修正