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 (npmパッケージ)
- 影響範囲:
- Nuxt 3.4.3 〜 3.21.5 (修正: 3.21.6)
- Nuxt 4.0.0-alpha.1 〜 4.4.5 (修正: 4.4.6)
- 修正バージョン:
nuxt@3.21.6/nuxt@4.4.6 - 修正PR: nuxt/nuxt#35052
- 修正コミット:
6d4051f(fix/redirect-encoding → main) - CPE: 【情報不足】(NVDに未登録のためCPE定義なし)
技術詳細
脆弱性の概要
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() 関数が追加された。
ワークアラウンド
即時アップグレードが不可能な場合:
navigateTo(url, { external: true })に渡す前にユーザー制御URLを検証- 最低限
new URL(input).toString()で正規化し、<または>を含む入力を拒否 - 正常なURLにこれらの文字が含まれることはなく、malformed とみなして安全に拒否可能
関連脆弱性
- CVE-2024-34343 (GHSA-vf6r-87q4-2vjf):
javascript:プロトコルバイパス — 異なる根本原因だが同一機能の別脆弱性 - CVE-2026-46342 (GHSA-g8wj-3cr3-6w7v): 同一セキュリティアップデートで公開された
__nuxt_islandエンドポイントのキャッシュポイズニング脆弱性 - CVE-2026-47200: ルートミドルウェアバイパス脆弱性
- CVE-2026-45670: 開発サーバーのソースコード露出
影響度
成功したエクスプロイトにより以下のリスクが発生:
- セッションハイジャッキング: 実行されたスクリプトがセッションCookie (HttpOnlyでないもの) にアクセス可能
- 不正なユーザー操作: ユーザーに代わって任意のアクションを実行
- 機密データ漏洩: DOM内の機密情報や非同HttpOnly Cookieの取得
- さらなるクライアントサイド攻撃の足がかり: XSSを起点とした追加攻撃
検知方法
- 依存関係スキャンで
nuxtパッケージのバージョンが 3.21.6 / 4.4.6 未満か確認 - コードベースで
navigateTo(url, { external: true })の使用箇所を特定 - 該当箇所にユーザー制御入力 (クエリパラメータ等) が渡されているか確認
- 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 |
推奨対応
- 直ちに
nuxt@3.21.6またはnuxt@4.4.6以降へアップグレード navigateTo()に渡すURLの入力検証を実装- CDN/リバースプロキシの設定でキャッシュキーにクエリ文字列を含めることを確認
navigateTo(url, { external: true })の使用箇所を監査し、ユーザー制御入力が渡される経路を特定・修正