【実務・中級編】DOM型XSS(DOM-based XSS)のクライアントサイド実行フロー – アプリケーションセキュリティ & 安全な開発防御ガイド

DOM型XSSの深淵:サーバーログに残らない「見えない攻撃」をどう防ぐか

やあ、現場の諸君。今日もフロントエンドの華やかな機能実装に追われているかな?

アプリケーションセキュリティの現場で、「WAFを導入しているからXSSは完璧」と豪語するエンジニアに出会うと、私は少しだけ眉をひそめる。なぜなら、DOM型XSSは、サーバーのログを一切汚さずにブラウザという「クライアントの庭」で完結する、極めて狡猾な攻撃手法だからだ。

今日は、サーバーサイドの防御網をすり抜け、ユーザーのセッションを奪い去る「DOM型XSS」のメカニズムと、明日から使える泥臭い防御策について、現場の知見を共有しよう。

—

1. DOM型XSSの本質:なぜサーバーを守っても防げないのか

反射型や格納型XSSは、攻撃ペイロードが一度サーバーを経由する。つまり、WAFやサーバー側のエスケープ処理で検知・無害化が可能だ。

しかし、DOM型XSSは違う。
攻撃の起点(ソース)はURLのフラグメント(#以降)やブラウザ内のデータであり、そこからJavaScriptがデータを読み取り、そのまま危険な関数(シンク)へ流し込むことで成立する。

攻撃のフローはこうだ:
1. 攻撃者が細工したURLを生成する(例: example.com/#)
2. ユーザーがそのリンクを踏む。
3. サーバー側では「#以降」は送信されないため、アクセスログには何も残らない。
4. クライアントサイドのJSがlocation.hashを読み取り、innerHTMLに流し込む。
5. ブラウザ上でJavaScriptが発火する。

サーバーサイドのエンジニアがどんなに堅牢なバリデーションを書いても、この「ブラウザ内部で完結するデータフロー」の前では無力なんだ。

—

2. 危険なソースとシンク(ここをマークせよ)

コードレビューを行う際は、以下の「ソース」から入力が入り、「シンク」に渡る経路がないかを血眼になって探してほしい。

  • ソース(データの入り口):
  • location.search (クエリパラメータ)
  • location.hash (フラグメント識別子)
  • document.referrer
  • window.name
  • シンク(実行される危険な関数):
  • innerHTML, outerHTML
  • document.write()
  • eval(), setTimeout(string), setInterval(string)

特に、モダンなフロントエンドフレームワークを使っていても、v-html (Vue) や dangerouslySetInnerHTML (React) を安易に使えば、DOM型XSSの扉は簡単に開く。

—

3. 実践:セキュアな実装コード

「バリデーションさえしていれば大丈夫」という考えは捨てよう。「そもそも危険な関数を使わない」、あるいは「データをDOMに挿入する際に必ず無害化する」ことが鉄則だ。

悪い例:脆弱な実装

// URLのハッシュ値をそのままinnerHTMLに代入している(最悪のケース)
const hash = window.location.hash.substring(1);
document.getElementById(‘display’).innerHTML = hash;

良い例:DOMPurifyを活用した安全な実装

外部ライブラリの DOMPurify を使うのが、今の業界標準だ。これを通せば、スクリプトタグや危険な属性は徹底的に除去される。

import DOMPurify from ‘dompurify’;

// 1. ソースを取得
const hash = window.location.hash.substring(1);

// 2. 信頼できる関数で無害化してからシンクへ渡す
const cleanHTML = DOMPurify.sanitize(hash);

// 3. 安全に描画
document.getElementById(‘display’).innerHTML = cleanHTML;

※ もしライブラリを使えない場合は、innerHTMLではなく、必ず textContent を使うこと。これならHTMLタグとして解釈されず、ただの文字列として扱われるため、XSSは物理的に発生しない。

—

4. 最後の砦:Content Security Policy (CSP) の設定

アプリケーションコードの修正は必須だが、運用側として「多層防御」を敷くのもプロの仕事だ。Nginxの設定で Content-Security-Policy ヘッダーを吐き出しておこう。

Nginx設定ファイル例
スクリプトの実行元を制限し、インラインスクリプトを禁止する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’; base-uri ‘self’;” always;

この設定の意味:

  • script-src 'self': 自分のドメイン以外からのスクリプト読み込みを許可しない。
  • 'unsafe-inline' を含めない: これにより、HTML内に直書きされた
securityintronationalをフォローする

コメント

タイトルとURLをコピーしました