0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#0204(2025/08/11)XSSの流れと基本的な対策

Last updated at Posted at 2025-08-11

XSSの流れと基本的な対策

要点:XSSとは「信頼されない入力がHTML/JS/CSSの文脈に混入し、ブラウザで任意のJavaScriptが実行される」脆弱性である。


1. なぜXSSが起きるのか(概要)

  • ブラウザは <タグ>"属性"javascript: など文脈依存でデータを解釈する。未エスケープの < > " ' や危険なURIを埋め込むと文脈が切り替わり、スクリプト実行につながる。
  • 入力バリデーションは補助。決定打は「出力時の文脈エスケープ」と安全な組み立てAPIの使用。
  • 文字コード誤判定(例: UTF-8以外に誤認)やDOM操作(innerHTML)も誘因。

2. 攻撃者の主目的

  • セッション・認可の悪用:セッションID窃取、ユーザー権限での操作(送金・設定変更・データ閲覧)。
  • 情報収集:DOM上の個人情報、ローカル/セッションストレージ、非HTTPOnly Cookie、IndexedDB等。
  • フィッシング/画面改ざん:偽フォーム・モーダル、キー入力の盗み見。

3. XSSの基本的な流れ(ダイアグラム)

[攻撃者入力]
     │  (URL/フォーム/コメント)
     ▼
[サーバ側処理]
  ├─(反射) レスポンスへ直埋め
  └─(保存) DB等に保存→後配信
     ▼
[ブラウザ描画]
  ├─ HTML本文/属性/URL/JS文字列/CSS
  └─ 不適切な組み立て・未エスケープ
     ▼
[JS実行]
  ├─ Cookie/Storage/DOM取得
  ├─ 同一オリジンAPI呼び出し
  └─ 画面改ざん・操作乗っ取り

4. 反射型XSSと持続型(ストア型)XSS

4.1 定義と特徴(比較表)

項目 反射型XSS 持続型(ストア型)XSS
攻撃コードの保存 保存されない(要求→応答で反射) サーバ/DBに保存され続ける
被害条件 被害者が細工URL等を踏む 対象ページを開くだけで発動
拡散性 低め(誘導が要る) 高い(閲覧者全員影響)
典型箇所 検索結果、エラーメッセージ コメント欄、プロフィール、掲示板
主な対策 文脈エスケープ、URL検証、CSP 文脈エスケープ、サニタイズ、CSP

4.2 簡易例

  • 反射型: /search?q=<script>alert(1)</script> をそのまま結果画面に出力。
  • 持続型: コメントとして <img src=x onerror=alert(1)> を保存し、閲覧者のブラウザで実行。

補足:DOM型XSSはサーバ往復なしで、フロントJSがinnerHTML等で不正なDOMを生成して発生。対策は「危険なDOM APIを避ける」こと。


5. 基本的な対策3つ(必須の土台)

5.1 HTML要素内容のエスケープ

  • 原則:出力時に文脈エスケープ。PHPなら:
// 要素内容(テキストノード)
echo htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
  • ポイント:ENT_QUOTES"'を両方エスケープ。第3引数でUTF-8を明示

5.2 HTML属性値のエスケープ+ダブルクォートで囲む

// 属性値
printf('<img alt="%s">', htmlspecialchars($alt, ENT_QUOTES, 'UTF-8'));
  • URL属性(href, src, formaction等)はスキーム許可リストhttps:// のみ等)で検証。javascript:data:を禁止。

5.3 HTTPレスポンスの文字エンコーディングを明示

Content-Type: text/html; charset=UTF-8
  • ブラウザの自動判定を抑止。フルバイト攻撃や文字化け由来のバイパスを防ぐ。

6. 追加的な対策3つ(実務で必須に近い)

6.1 JavaScript内に値を埋め込む場合

  • HTMLエスケープは効かない。JS文字列/JSONとして安全に埋め込む:
// 安全なJSONとして埋め込み(タグ/クォートをHEX化)
echo '<script>const DATA = ' .
     json_encode($value, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT) .
     ';</script>';
  • DOM組み立ては textContent / createTextNode / setAttribute を基本に。innerHTMLは避ける。

6.2 HTTPOnly Cookie

  • セッションIDはJSから読めないようHttpOnlyで配布:
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=...;
  • Secure(HTTPS限定)・SameSite(CSRF軽減)も併用。

6.3 CSP(Content Security Policy)

  • スクリプトの読み込み元・実行方式を制御:
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-<ランダム値>' https://trust.cdn.example;
  object-src 'none'; base-uri 'self'; frame-ancestors 'self';
  • インラインJSはnonce方式へ移行(<script nonce="...">)。unsafe-inlineは極力避ける。

7. 文脈別サニタイズ早見表(比較)

文脈 安全な出力方法/API 追加検証 NG例(避ける)
HTML本文(テキスト) htmlspecialchars(…, ENT_QUOTES, 'UTF-8') / テンプレートの自動エスケープ 文字数・型 未エスケープ出力、innerHTML直書き
HTML属性値 同上+必ずクォートで囲む URLはhttps://等のスキーム白リスト 非クォート属性、javascript:許容
URL(href/src 文字列エスケープ+allowlist 相対/絶対の許可範囲 data:vbscript:javascript:
JS文字列/JSON json_encode(..., JSON_HEX_*) / エスケープ 期待型(配列/数値) 文字列連結での直埋め
DOM操作 textContent/createTextNode/setAttribute - innerHTML/outerHTML/insertAdjacentHTML
CSS 可能なら埋め込まない クラス/IDはサーバ側で割当 style直書き、expression()互換

8. 具体例で学ぶ「危険→安全」書き換え

危険(検索語の表示)

echo "検索: " . $_GET['q'];

安全

echo '検索: ' . htmlspecialchars($_GET['q'] ?? '', ENT_QUOTES, 'UTF-8');

危険(画像URLを属性に直埋め)

printf('<img src=%s>', $_GET['img']);

安全

$img = $_GET['img'] ?? '';
if (!preg_match('#^https://#', $img)) { $img = '/images/placeholder.png'; }
printf('<img src="%s" alt="">', htmlspecialchars($img, ENT_QUOTES, 'UTF-8'));

危険(JS内にユーザー名を連結)

<script>const name = '<?= $_GET['name'] ?>';</script>

安全

echo '<script>const name = ' .
     json_encode($_GET['name'] ?? '', JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT) . ';</script>';

9. よくある落とし穴(レビュー時の着眼点)

  • 自動エスケープの過信:テンプレートの例外(「生HTML」出力、safe/raw/v-html/dangerouslySetInnerHTML)が紛れていないか。
  • 文字コード未指定Content-Typecharset=UTF-8を返しているか。HTML5の<meta charset>補助
  • 属性の非クォート<a href=${x}>のような書き方。必ず"…"で囲む。
  • URL検証欠如javascript:data:の混入。
  • DOM APIの誤用:便利だからとinnerHTML。まずtextContentを選ぶ。
  • サニタイザ任せ:HTMLサニタイザ(例: コメント/タグ除去)は最後の手段。まず文脈エスケープを徹底。

10. ミニ演習:手元で赤信号を見つける

  • テキストノード: "><img src=x onerror=alert(1)> をそのまま出すとどうなる?
  • 属性値: " onerror="alert(1) が混入したら?
  • URL: javascript:alert(1) を弾けている?
  • JS埋め込み: '</script><script>alert(1)</script>json_encodeで安全にできる?

これらに破綻しない実装になっていれば、XSS耐性は概ね合格ライン。


11. まとめ(チェックリスト)

  • 要素内容は htmlspecialchars(…, ENT_QUOTES, 'UTF-8')出力時に適用
  • 属性値も同様にエスケープし、必ずクォートで囲む
  • Content-Typecharset=UTF-8 を明示
  • JS埋め込みは json_encode(JSON_HEX_*) 等でJSコンテキスト用にエスケープ
  • セッションCookieは HttpOnly; Secure; SameSite を付与
  • CSPでscript-srcを制御、インラインはnonce運用
  • 危険なDOM API(innerHTML等)を避け、textContent系を使う
  • URL属性は許可スキームのホワイトリストで検証

補足図:安全なレンダリングの考え方

[入力]
  ↓ (サーバで型/長さ/スキーム検証)
[出力時の文脈判定]
  ├─ HTML本文 → HTMLエスケープ
  ├─ HTML属性 → HTMLエスケープ+クォート
  ├─ URL属性 → エスケープ+スキーム検証
  ├─ JS埋め込み → JSON/JSエスケープ
  └─ DOM操作 → textContent等の安全API
      ↓
[CSP/HttpOnly/SameSite で最終防御]

このノートをプロジェクトのレビュー基準実装テンプレに落とし込めば、XSSは大きく減らせます。実務は「文脈エスケープ+安全API+ヘッダで締める」の三段構えでいきましょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?