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

記事を検索

Vue SSRの属性名XSS 3.5.42で直る条件とNuxtの更新

目次 · 7項目

Nuxt のサイトで npm audit を実行すると、vue と @vue/server-renderer に「high」が出る。指摘は GHSA-g2v6-rqmx-r4w6 で、vuejs/core のリポジトリでのアドバイザリの公開は8月31日、GitHub Advisory Database への登録は10月5日(いずれも UTC。後者は日本時間では6日朝)です。CMS で編集者が決めた項目名を data-* 属性としてカードに出していたり、APIの応答をそのまま v-bind で要素に渡していたりするサーバーサイドレンダリング(SSR)のサイトでは、ページを開いただけで JavaScript が動く XSS になり得ます(想定の場面)。

一方で、修正版の 3.5.42 は8月27日に npm へ公開済みで、その後に依存を更新したサイトはすでに直っています。本記事では、当たるコードの条件、版の確かめ方、Nuxt で更新したつもりでも古い版が残る例を整理します。後半の結果は、編集部の検証環境で Vue と Nuxt を実際に動かして確かめたものです。

当たるのは「属性の名前」を外から受け取るSSR

Vue の SSR は、<div v-bind="obj"> のようにオブジェクトを属性へ展開するとき、@vue/server-renderer の ssrRenderAttrs() で HTML の文字列を組み立てます。属性の値は毎回エスケープされます。ところが属性の名前は、使えない文字の一覧(> / = " '、タブ、改行(LF)、改ページ、空白)に当たらないかを見るだけで、そのまま出力されていました。

この一覧に、復帰の文字(CR、\r)が入っていませんでした。アドバイザリによれば、ブラウザーは HTML を読む最初の段階で単独の CR を LF に置き換えます。LF は属性の名前の区切りになるため、Vue が1つの名前として出した x\rautofocus\ronfocus が、ブラウザーでは x・autofocus・onfocus の3つの属性として解釈されます。値の側にスクリプトを置けば、読み込んだ時点で実行されます。

アドバイザリが挙げる条件は次のとおりです。

  • 外部から来たキー(値だけでなく名前)を持つオブジェクトを、v-bind="object" かそれに相当する形で SSR の要素に渡している
  • 例として、権限の弱い利用者が項目名を設定できる CMS やフォームビルダー、別の場所で検証した設定オブジェクトを要素に広げるコンポーネント、データベースのレコード・APIの応答・クエリ文字列を変換したオブジェクトを属性にする処理が挙げられている
  • ブラウザー側の描画(クライアントの Vue)は setAttribute() が名前を検査するため、報告者はこの問題に到達できなかったとしている。SSR に限った問題

値だけを外から受け取る通常の使い方(:title="post.title" など)は、もともとエスケープされるため対象外です。重大度は HIGH(CVSS 3.1 のベクトルは AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N)です。

テンプレートの v-bind だけではない

編集部の検証環境(Linux、Node.js v22.22.0)で、無害なキー data-a\rdata-injected を3通りの書き方で SSR に渡し、出力を HTML5 のパーサー parse5 で読みました。

@vue/server-renderer 3.5.41
  <div v-bind="attrs"> をSSR用にコンパイル → ssrRenderAttrs を呼ぶコードになる
  ssrRenderAttrs(attrs)        → " id=\"card\" data-a\rdata-injected=\"yes\""
  parse5 の解釈              → id="card" data-a="" data-injected="yes"
  renderToString(h('div', attrs)) → 同じく data-a と data-injected に分かれた
  <Card v-bind="attrs">(ルート要素への引き継ぎ) → class, data-a, data-injected に分かれた

@vue/server-renderer 3.5.42 / 3.5.44
  3つとも出力は id="card"(または class="card")だけ。危険なキーは出力されない

テンプレートの v-bind に加えて、描画関数の h() と、props として宣言していない属性が子のルート要素へ引き継がれる場合も、同じ経路を通りました。コードを探すときは、この3つの形を対象にします。

自社のコードで探すところ

ここからは、アドバイザリの条件をリポジトリのどこで確かめるかについての編集部の整理です。

  1. .vue ファイルで、引数なしの v-bind="..." を検索する。渡しているオブジェクトのキーが、CMS・API・データベース・URL のクエリから来ていないかを見る
  2. h( や render() で、第2引数に外部のデータをそのまま渡していないかを見る
  3. 子コンポーネントに v-bind="..." でまとめて渡していないかを見る。子が props を宣言していなければ、キーはルート要素の属性になる
  4. Object.fromEntries や ...spread で外部の項目名から属性のオブジェクトを作る補助関数がないかを見る

キーを外から受け取る設計が必要な場合は、更新に加えて、data- で始まり英数字とハイフンだけからなる名前のように、許可する形を決めて絞る方法が考えられます。これは編集部の提案で、アドバイザリが求めている対策は更新です。

修正版は3.5.42、公開は8月27日

系列対象の版修正版npm の公開日時(UTC)
3.5 系3.5.41 以前(範囲の下限は 0)3.5.422026年8月27日
3.6 の RC3.6.0-rc.0〜rc.53.6.0-rc.62026年8月28日

修正は vuejs/core の PR #15266(コミット a2b40db、8月11日)で、packages/shared/src/domAttrConfig.ts の一覧に \u000d を1文字加える変更です。3.5.42 の変更履歴にも「server-renderer: reject CR in attribute names」とあります。vuejs/core のリポジトリのアドバイザリは8月31日(UTC)に公開されましたが、GitHub Advisory Database に載ったのは10月5日(UTC)で、npm audit が指摘を始めたのはそれ以降です。

10月21日時点の npm の latest は 3.5.44(10月13日公開)、rc は 3.6.0-rc.12 です。3.5.42 以降の版に上げれば修正が入ります。

確認しておきたいのは、修正のある場所です。危険な名前を判定する関数 isSSRSafeAttrName() は @vue/shared にあり、@vue/server-renderer はそれを読み込んで使います。npm では両者の版が完全一致で指定されているため通常はそろいますが、検証で overrides を使って @vue/server-renderer 3.5.42 と @vue/shared 3.5.41 を組み合わせたところ、属性は分かれ、npm audit の指摘は0件でした。依存の固定や上書きをしているプロジェクトでは、@vue/shared の版も確かめます。

Nuxtの古いロックファイルでは更新が届かない

Nuxt 4.6.2(10月16日公開)は vue を ^3.5.44 で要求するため、4.6.2 に上げれば Vue も上がります。一方、Nuxt 3 系の最新 3.21.11(8月5日公開)の要求は ^3.5.40 で、3.5.41 のままでも条件を満たします。

Nuxt 3.21.11 と Vue 3.5.41 で作ったロックファイルを使い、手元で次の順に試しました(npm 10.9.4。Nuxt の依存の解決で npm がエラーを出したため --legacy-peer-deps を付けています)。

操作ロックファイルの vue と @vue/server-renderernpm audit
package.json の vue を ^3.5.41 にして npm install3.5.41 のままhigh(GHSA-g2v6)
npm update vue3.5.44指摘なし
新規に Nuxt 4.6.2 で作成3.5.44―

^ の範囲に新しい版が含まれていても、npm install はロックファイルの版を保ちます。Nuxt 3 系のサイトは npm update vue(pnpm や yarn ではそれぞれの update)でロックファイルを更新し、差分に @vue/server-renderer と @vue/shared の 3.5.42 以降が入っていることを確かめます。

ビルド結果の package.json だけを見ない

同じ Nuxt 3.21.11 の最小のアプリ(URL のクエリを属性に展開する、検証のためだけの危険な書き方)をビルドし、.output/server を起動して確かめました。

Nuxt 3.21.11の最小アプリで編集部が確かめた結果。更新前は.output/server/package.jsonの@vue/sharedが3.5.44と表示されていたが、@vue/server-rendererは3.5.41で、その下に3.5.41の@vue/sharedが別に置かれ、CRを含むクエリのキーがdata-aとdata-injectedの2つの属性に分かれた。npm update vueのあと再ビルドすると、すべて3.5.44になり、危険なキーは出力されず、サーバーのログにunsafe attribute nameの警告が出た

更新前のビルドでは、.output/server/package.json に "@vue/shared": "3.5.44" と書かれ、.output/server/node_modules/@vue/shared も 3.5.44 を指していました。ところが @vue/server-renderer は 3.5.41 で、その下に別の node_modules/@vue/shared があり、ビルド結果には 3.5.41 の @vue/shared も同梱されていました。/?data-a%0Ddata-injected=yes を開くと、応答の <div> は parse5 で id・data-a・data-injected の3つに分かれました。

npm update vue のあとに再ビルドすると、.output/server の Vue の関連パッケージはすべて 3.5.44 になり、同じ要求の応答は <div id="card"> だけになりました。サーバーのログには unsafe attribute name と Skipped rendering unsafe attribute name が出ました(NODE_ENV=production で単体の Vue を動かした場合も同じ表示)。

ビルド結果で確かめるときは、.output/server/package.json の @vue/shared の行ではなく、@vue/server-renderer の版と、その下に古い @vue/shared が残っていないかを見ます。

更新後に確かめること

  • 本番の成果物で @vue/server-renderer が 3.5.42 以上であること(npm ls @vue/server-renderer @vue/shared、Nuxt は .output/server/node_modules も)
  • ステージングで、属性に展開しているページへキーに %0D を含む値を送り、応答の属性が増えないこと。クエリ以外から属性名を受け取る場合は、CMS の検証用の項目などで試す
  • 更新後のサーバーのログに unsafe attribute name が出たら、属性名に不正な文字を含むデータがどこから来たかを調べる(編集部の提案)

依存の更新を誰がどの周期で行うかの決め方は、React2Shellの放置サイトと保守契約の見直しで整理しています。Nuxt 4.6 への更新で確かめる点はNuxt 4.6のnuxt/server 書き換え前に確かめる点、同じ時期の Next.js のアドバイザリはNext.js 10月の脆弱性9件にまとめています。

確認した範囲

2026年10月21日に、github/advisory-database の GHSA-g2v6-rqmx-r4w6 のJSON、vuejs/core のリポジトリ(修正コミット a2b40db、タグ v3.5.41〜v3.5.44 と v3.6.0-rc.5・rc.6、CHANGELOG.md)、npm registry の vue・@vue/server-renderer・@vue/shared・nuxt の公開日時と依存範囲を直接取得して確認しました(資料調査)。あわせて、Linux・Node.js v22.22.0・npm 10.9.4 で、@vue/server-renderer 3.5.41・3.5.42・3.5.44 と、3.5.42 に @vue/shared 3.5.41 を組み合わせた構成の出力を parse5 で解析し、Nuxt 3.21.11 のロックファイルの更新と npm audit、最小のアプリのビルドと起動を試しました(実機検証)。実際のブラウザーでのスクリプトの実行、Vue 2(vue-server-renderer)、pnpm・yarn での更新、Nuxt 3.21.11 以外の版のビルド結果、クライアント側の描画で到達しないことは確かめていません。NVD と vuejs.org は開いていません。公開前の10月21日には、GitHub の API で vuejs/core のアドバイザリ本体(8月31日公開)を確かめ、10月5日より後に Vue の追加のアドバイザリや Nuxt 3 系の新しい版が出ていないことも確認しました。

Vue・Nuxt で作ったサイトの依存の更新や、SSR のコードの見直しは、開発・AI・自動化のご相談からお問い合わせください。

参考資料

この記事を共有XFacebook
鈴木 翔

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

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

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

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

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

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

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

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

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

The PDF and newsletter emails are currently in Japanese.

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