XSSの「教科書的な理解」を捨てろ:現場が知るべきDOMベース攻撃の真実
「XSSなんて今さら対策済みだよ」——そう言って油断している開発者が、一番最初に標的になる。
こんにちは。セキュリティチーフとして数々のインシデント現場を見てきたが、悲しいことに、いまだに htmlspecialchars() を適当にかけて「対策完了」と思い込んでいる現場があまりにも多い。特にフロントエンドが複雑化した現代において、反射型や蓄積型はWAFが弾いてくれても、クライアントサイドだけで完結する「DOMベースXSS」は、インフラエンジニアやWAFの防壁を鮮やかにすり抜ける。
今回は、教科書には載っていない「攻撃者がどこを突くのか」、そして「泥臭くどう守るか」を解説する。
—
1. 攻撃者の視点:なぜDOMベースXSSは防げないのか?
反射型や蓄積型が「サーバーからのレスポンス」に依存するのに対し、DOMベースXSSは「クライアント側のJavaScript」の不適切な実装を狙う。
例えば、URLのフラグメント(#以降)からパラメータを取得し、それをそのまま innerHTML に放り込むようなコードだ。
// 危険な実装例:URLのパラメータをそのままDOMに挿入している
const params = new URLSearchParams(window.location.hash.substring(1));
const name = params.get("name");
document.getElementById("welcome-msg").innerHTML = "ようこそ、" + name + "さん";
この時、ブラウザはサーバーにリクエストを飛ばさない。#<img src=x onerror=alert(1)> といったURLを踏ませるだけで、攻撃者のスクリプトはブラウザ内部で「正当な動作」として実行される。WAFのログには何も残らない。これが恐ろしい点だ。
—
2. 対策の核心:エスケープだけでは足りない
よくある間違いが、「入力値をエスケープすれば安心」という考えだ。しかし、現代の開発では textContent を使うだけでリスクの9割は消える。
実務で使えるセキュアな実装パターン
HTMLへの動的な反映が必要な場合は、以下のルールを徹底してほしい。
JavaScriptでの修正例:
// セキュアな実装:innerHTMLは絶対禁止
const name = params.get("name");
const el = document.getElementById("welcome-msg");
// textContentを使えば、HTMLタグは単なる文字列として扱われる
el.textContent = "ようこそ、" + name + "さん";
// もし安全なHTMLを挿入したい場合は、DOMPurifyのようなライブラリを通す
// el.innerHTML = DOMPurify.sanitize(name);
—
3. インフラレベルでの防御:CSP(Content Security Policy)を「強制」する
アプリケーションコードが完璧であるという前提は捨てろ。どんなに気をつけても、ライブラリの脆弱性でXSSが埋め込まれることはある。そのための最後の砦がCSPだ。
Nginxの設定で、信頼できないインラインスクリプトを一切禁止するポリシーを叩き込む。
Nginx設定例 (/etc/nginx/conf.d/security.conf):
# インラインスクリプトを禁止し、信頼されたドメインからのスクリプトのみ許可する
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none'; base-uri 'self';";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
script-src 'self': 自分のドメイン以外からのスクリプト実行を許可しない。object-src 'none': プラグイン(Flashなど)の実行を無効化する。nosniff: MIMEタイプを偽装してスクリプトを無理やり実行させる攻撃を防ぐ。
—
4. 最後に:エンジニアが持つべき「疑いの精神」
脆弱性を見つけるのは難しいことではない。コードを見たときに「もしこの変数が攻撃者の意図通りに書き換えられたら、ブラウザはどう動く?」と問い続けることだ。
今回のポイントを整理しよう。
1. DOM操作の原則: innerHTML は攻撃の入り口と見なせ。代わりに textContent を使うのが正解だ。
2. ライブラリへの依存: HTMLを動的に生成する際は、自前で正規表現を書いて防ごうとするな。DOMPurify を使う。
3. 多層防御: CSPは「壊れたアプリ」を「攻撃から守る」ための最終防衛ラインだ。運用環境には必ず導入しろ。
セキュリティ対策は、「コードを書くこと」ではなく「リスクを制御すること」だ。明日から自分の書くコードに innerHTML が残っていないか、一度 grep をかけてみることをお勧めする。それが、あなたのシステムを堅牢にする第一歩だ。
コメント