【実務・中級編】DOMベースXSSの発生メカニズムとクライアントサイドの脆弱性 – アプリケーションセキュリティ & 安全な開発防御ガイド

DOMベースXSS:サーバーログに残らない「見えない侵入者」をどう狩るか

やあ。現場でコードと格闘しているエンジニアのみんな、お疲れ様。
今日は、サーバーサイドのWAFをどれだけガチガチに固めても、バックエンドのログが「正常」に見えても、ユーザーのブラウザ上で平然と実行される恐ろしい脆弱性――DOMベースXSSについて話そう。

多くのエンジニアが「サーバー側でサニタイズしてるから大丈夫」と安心しきっているが、DOMベースXSSはサーバーを一切経由しない。攻撃者はURLのフラグメント(#以降)を操るだけで、君たちのアプリケーションを乗っ取ることができるんだ。

—

1. なぜDOMベースXSSが「盲点」なのか

一般的なXSSは、サーバーが生成したHTMLに悪意あるスクリプトが混入する。しかし、DOMベースXSSは、ブラウザ上のJavaScriptが「URLのパラメータ」や「フラグメント」を読み取り、それをそのままDOM操作系関数(シンク)に渡すことで発生する。

攻撃のメカニズム(PoC)

例えば、ある検索ページで「検索ワードを画面に表示する」こんなコードがあったとしよう。

// 脆弱な実装例
const fragment = decodeURIComponent(window.location.hash.substring(1));
const resultDiv = document.getElementById(‘search-result’);
// ここが運命の分かれ道:危険なシンクに直接渡している
resultDiv.innerHTML = “検索結果: ” + fragment;

攻撃者は、以下のURLを被害者にクリックさせるだけでいい。
https://example.com/search.html#

サーバーには # 以降の情報は届かない。だから、サーバー側のWAFは攻撃を検知できない。これが「サーバーログに残らない」と言われる所以だ。

—

2. 脆弱性を根絶する:セキュアな実装への書き換え

解決策はシンプルだ。「innerHTML」を使わない。これに尽きる。
HTMLとして解釈させず、純粋なテキストとしてDOMに挿入する習慣を徹底しよう。

推奨される実装(textContentを使う)

// セキュアな実装
const fragment = window.location.hash.substring(1); // デコードは後で自動的に行われる
const resultDiv = document.getElementById(‘search-result’);

// innerHTMLの代わりにtextContentを使う
// これにより、HTMLタグとして解釈されることなく、ただの文字列として描画される
resultDiv.textContent = “検索結果: ” + decodeURIComponent(fragment);

もし、どうしてもHTMLを描画する必要がある場合(リッチテキストエディタ等)、DOMPurifyのような信頼できるライブラリを通すのが鉄則だ。自前で正規表現を使ってタグを除去しようなどと考えてはいけない。現場の泥臭い戦いにおいて、自作サニタイザは「穴だらけ」の代名詞だ。

—

3. 多層防御:CSP(Content Security Policy)でトドメを刺す

コードの修正はもちろん必須だが、万が一の書き漏らしに備えて、インフラ側でも防御を固めておくのがプロの仕事だ。

NginxでのCSP設定例

サーバーのレスポンスヘッダーに以下の設定を追加することで、ブラウザに対して「信頼できないスクリプトの実行を禁止する」よう強制できる。

Nginx設定ファイルに追加
‘unsafe-inline’ を削除し、信頼できるソースのみに制限する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’;”;

  • default-src 'self': 自身のドメイン以外からの読み込みを原則禁止。
  • script-src 'self': インラインスクリプト(onclickや
securityintronationalをフォローする

コメント

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