# XSS（クロスサイトスクリプティング）とは？仕組みと対策コード

XSS（クロスサイトスクリプティング）の仕組みと3つの種類、エスケープ・サニタイズ・Trusted Types・CSPによる対策を、IPAとMDNの資料をもとに安全なコード例つきでWeb開発者向けに解説します。

XSS（クロスサイトスクリプティング）は、攻撃者が用意したスクリプトを、正規のWebサイトの一部としてユーザーのブラウザで実行させる脆弱性です。原因は、外部から入力された値をエスケープやサニタイズをせずにページへ埋め込むことにあります。対策の基本は「出力する場所に合わせてエスケープする」ことで、そのうえでサニタイズ、Trusted Types、CSPを重ねます。

<Points>

- 原因は、入力値がHTMLやJavaScriptとして解釈される形でページに出力されること
- 根本対策は出力時のエスケープ。HTMLを表示したいときは信頼できるライブラリでサニタイズする
- CSPやHttpOnly属性は被害を抑える保険。根本対策の代わりにはならない

</Points>

## XSSとは

MDNは、XSSを「攻撃者が対象のWebサイトのコンテキストで悪意のあるコードを実行できるようにする攻撃」と説明しています。スクリプトが正規サイトのオリジンで動くため、同一オリジンポリシーによる保護が効きません。その結果、ページの書き換え、Cookieやセッション情報の窃取、ユーザーになりすました操作などが可能になります。

OWASP Top 10:2025では、XSS（CWE-79）はA05「Injection」に含まれます。OWASPはXSSを「頻度は高いが影響は比較的小さい」問題として位置づけていますが、ログイン中のユーザーの権限で何でもできてしまうため、軽視はできません。最新のTop 10全体は[OWASP Top 10の解説](/owasp-top-10/)にまとめています。

<Figure title="XSSの3つの種類">

| 種類 | 仕組み | 例 |
| --- | --- | --- |
| 反射型 | URLなどに含まれた値を、サーバーがそのままレスポンスに埋め込む | 検索語をそのまま「◯◯の検索結果」と表示する |
| 格納型（持続型） | データベースに保存された悪意ある入力が、閲覧した全員に配信される | コメント欄や掲示板への書き込み |
| DOMベース | サーバーを経由せず、ブラウザ側のJavaScriptが入力をHTMLとして挿入する | `location.search` の値を `innerHTML` に代入する |

</Figure>

格納型は、ページを開いた人すべてが被害に遭うため特に危険です。DOMベースはサーバーのログに痕跡が残りにくく、フロントエンドのコードレビューで見つける必要があります。

## 攻撃が成立する仕組み

たとえば、検索語を表示するページで次のような処理をしていたとします。

```js
// 危険な例：URLの値をそのままHTMLとして挿入している
const params = new URLSearchParams(location.search);
const q = params.get("q");
document.querySelector("#result").innerHTML = `${q} の検索結果`;
```

攻撃者が `q` に `<img src=x onerror=...>` のような値を入れたURLを用意し、ユーザーにクリックさせると、`onerror` に書かれたスクリプトがそのサイトのオリジンで実行されます。

<Flow
  title="反射型XSSの流れ"
  steps={[
    { label: "罠のURLを用意", note: "パラメータにスクリプトを含める" },
    { label: "ユーザーがクリック", note: "メールやSNS、別サイトのリンクから" },
    { label: "正規サイトが値を埋め込む", note: "エスケープせずにHTMLとして出力" },
    { label: "ブラウザがスクリプトを実行", note: "正規サイトのオリジンで動く" },
    { label: "被害", note: "情報の窃取、なりすまし操作、画面の改ざん" },
  ]}
/>

入力値が出力される場所（コンテキスト）によって、必要なエスケープが変わる点にも注意が必要です。MDNは、HTML要素の中、HTML属性の中、JavaScriptやCSSの中を区別しています。属性値を引用符で囲まないと、引用符をエスケープしていても `onmouseover=...` のような値で属性を追加されるおそれがあります。`<script>` や `<style>` の中に入力値を直接入れるのは、ほぼ常に危険です。

## 根本的な対策

IPAの「安全なウェブサイトの作り方」は、HTMLの入力を許可しない場合の根本的解決として、次の項目を挙げています。

<Figure title="IPAが示すXSSの根本的解決" source="IPA「安全なウェブサイトの作り方」">

| 対策 | 内容 |
| --- | --- |
| エスケープ処理 | 出力するすべての要素で特殊記号をHTMLエンティティに置き換える。属性値はダブルクォートで囲む |
| URLの制限 | 出力するURLは `http://` か `https://` で始まるものだけを許可する |
| scriptの動的生成の禁止 | `<script>` 要素の内容を外部入力から生成しない |
| 外部スタイルシートの取り込み禁止 | 任意のサイトのスタイルシートを読み込める仕様にしない |
| 文字コードの明示 | `Content-Type` ヘッダーに `charset` を必ず指定する |

</Figure>

実際のコードでは、自動エスケープのあるテンプレートエンジンやフレームワークを使うのが近道です。ReactのJSXは、`{}` で埋め込んだ値を自動でエスケープします。

```jsx
// 安全な例：JSXは埋め込んだ値を自動でエスケープする
export function SearchResult({ query }) {
  return <p>{query} の検索結果</p>;
}
```

素のJavaScriptでDOMを操作する場合は、HTMLとして解釈されない `textContent` を使います。

```js
// 安全な例：文字列をテキストとして挿入する
const q = new URLSearchParams(location.search).get("q") ?? "";
document.querySelector("#result").textContent = `${q} の検索結果`;
```

リンク先URLに入力値を使う場合は、スキームを確認してから設定します。

```js
// 安全な例：http / https 以外のURLは使わない
function safeUrl(input) {
  try {
    const url = new URL(input, location.origin);
    return url.protocol === "http:" || url.protocol === "https:" ? url.href : "#";
  } catch {
    return "#";
  }
}

link.href = safeUrl(userInput);
```

## HTMLを表示したい場合のサニタイズ

ブログのコメントやリッチテキストのように、利用者が書いたHTMLを表示したい場合もあります。このときは、許可した要素だけを残すサニタイズを行います。MDNとOWASPは、信頼できるライブラリとしてDOMPurifyを挙げています。

```js
import DOMPurify from "dompurify";

// 安全な例：危険な要素や属性を取り除いてから挿入する
container.innerHTML = DOMPurify.sanitize(userHtml);
```

Reactの `dangerouslySetInnerHTML` やDjangoの `safe` フィルターは、自動エスケープを無効にします。使う場合は、必ず事前にサニタイズしてください。

<Compare>
<Column title="HTMLとして解釈されるAPI（要注意）">

- `innerHTML`、`outerHTML`
- `insertAdjacentHTML()`
- `document.write()`
- `eval()`
- 文字列を渡す `setTimeout()`、`setInterval()`

</Column>
<Column title="代わりに検討するもの" tone="accent">

- `textContent` でテキストとして挿入
- フレームワークの自動エスケープ
- HTMLが必要ならDOMPurifyでサニタイズ
- 関数を渡す `setTimeout()`
- Trusted Typesで危険なAPIへの代入を強制チェック

</Column>
</Compare>

IPAは、`<script>` などの文字列を削除するのではなく無害な文字列に置き換えることや、ブラックリスト方式だけに頼らないことも注意点として挙げています。削除すると、残った文字が組み合わさって新たな危険な文字列ができることがあるためです。

## Trusted TypesとCSPで被害を抑える

エスケープ漏れは、どれだけ注意していても起こり得ます。ブラウザの機能を使って、万一のときの被害を抑えておきます。

<Steps>
<Step title="Trusted Typesを有効にする">

CSPで `require-trusted-types-for 'script'` を指定すると、`innerHTML` などに通常の文字列を代入したときに `TypeError` が発生します。サニタイズ用のポリシーを通した値しか受け付けなくなるため、エスケープ漏れを実行時に検出できます。Trusted Types自体はサニタイズをしないので、ポリシーの中でDOMPurifyなどを呼びます。

</Step>
<Step title="厳格なCSPを設定する">

nonceやハッシュを使ったCSPを設定すると、想定したスクリプト以外は実行されなくなります。注入された `<script>` 要素、インラインのイベントハンドラー、`javascript:` URL、`eval()` などがブロックされます。

</Step>
<Step title="CookieにHttpOnly属性を付ける">

IPAは保険的対策として、Cookieに `HttpOnly` 属性を付けてスクリプトからの読み取りを防ぐことを挙げています。ただし、Cookieの窃取以外の被害は防げません。

</Step>
</Steps>

Trusted TypesのポリシーはMDNで次のように紹介されています。

```js
// Trusted Types のポリシーでサニタイズを強制する
const policy = trustedTypes.createPolicy("my-policy", {
  createHTML: (input) => DOMPurify.sanitize(input),
});

element.innerHTML = policy.createHTML(userInput);
```

CSPはレスポンスヘッダーで指定します。nonceはリクエストごとに推測できない値を生成し、許可するスクリプトタグにだけ同じ値を付けます。

```http
Content-Security-Policy: script-src 'nonce-ランダムな値'; require-trusted-types-for 'script'
```

```html
<script nonce="ランダムな値" src="/app.js"></script>
```

<Note title="要追記：実体験">（ここに自社サービスでCSPやTrusted Typesを導入したときの手順、既存コードで発生した違反の件数や対応方法を書く）</Note>

## よくある質問

### Q. 入力時にチェックすればXSSは防げますか？

入力値のチェックだけでは不十分です。IPAは入力値の内容チェックを保険的対策と位置づけ、幅広い文字を許可する仕様では効果が限られるとしています。根本対策は、出力する場所に合わせたエスケープです。

### Q. Reactを使っていればXSSの心配はありませんか？

JSXで値を埋め込むと自動でエスケープされますが、`dangerouslySetInnerHTML` を使う場合や、`href` に利用者が入力したURLを入れる場合は別途対策が必要です。URLは `http:` か `https:` で始まるものだけを許可してください。

### Q. CSPを設定すればエスケープは不要ですか？

不要にはなりません。MDNもCSPを「予備の防御」と位置づけています。CSPに対応していない環境や設定の抜けもあるため、エスケープとサニタイズを基本にし、CSPは被害を抑える層として重ねます。

### Q. XSSとCSRFはどう違いますか？

XSSは正規サイト上で攻撃者のスクリプトを実行させる攻撃、CSRFは別サイトからユーザーに意図しないリクエストを送らせる攻撃です。XSSがあるとCSRFトークンも読み取られてしまうため、CSRF対策の前提としてもXSS対策が重要です。詳しくは[CSRFの解説](/csrf/)をご覧ください。

出典: [MDN「クロスサイトスクリプティング (XSS)」](https://developer.mozilla.org/ja/docs/Web/Security/Attacks/XSS)、[IPA「安全なウェブサイトの作り方 1.5 クロスサイト・スクリプティング」](https://www.ipa.go.jp/security/vuln/websecurity/cross-site-scripting.html)、[OWASP Top 10:2025 Introduction](https://top10.owasp.org/2025/0x00_2025-Introduction/)、[OWASP A05:2025 Injection](https://top10.owasp.org/2025/A05_2025-Injection/)、[OWASP Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
