ブラウザの中の「見えない侵入者」:DOMベースXSSの正体と防ぎ方
こんにちは。現場で泥臭いインシデント対応を繰り返していると、「セキュリティはまるで家の防犯と同じだな」とつくづく感じます。
皆さんは、玄関の鍵をしっかり閉めていても、「家のポストから手を入れて、内側の鍵を開けられた」という空き巣の手口を聞いたことはありませんか?
Webの世界における「DOMベースXSS(クロスサイトスクリプティング)」は、まさにこれと同じです。サーバー側でしっかりガードを固めていても、ブラウザの中にある「ちょっとした油断」を突かれると、一瞬で攻撃が成立してしまいます。
今日は、この「クライアントサイドの死角」について、新人エンジニアの皆さんにもわかるように紐解いていきましょう。
—
1. DOMベースXSSとは?:犯人は「ブラウザの中」にいる
一般的なXSSは、サーバーから送られてくるHTMLそのものに攻撃コードが紛れ込んでいるケースが多いです。しかし、DOMベースXSSは一味違います。
サーバーは全く悪くない。悪いのはブラウザで動いているJavaScriptの書き方です。
攻撃のメカニズムを「家の防犯」で例えると
1. URLフラグメント(ソース): 攻撃者がURLの末尾に「#name=悪意あるスクリプト」という付箋(ふせん)を付けてブラウザに送り込みます。
2. JavaScript(実行フロー): あなたが書いたプログラムが、「おっ、URLに名前が書いてあるぞ!」と、深く考えずにその付箋を拾い上げます。
3. sink(危険な関数): そのまま、ページ内の「表示」を担当する場所(innerHTMLなど)に、付箋の内容を流し込んでしまいます。
すると、ブラウザは「ああ、これは表示していい文字なんだね」と勘違いして、攻撃者が仕込んだスクリプトをペタッと実行してしまうのです。これがDOMベースXSSの正体です。
—
2. なぜ危ない?「危険な関数(sink)」を見極めよう
開発者が一番注意すべきなのは、「外部からの入力を、そのままHTMLとして解釈させてしまう関数」です。これをセキュリティの専門用語で「sink(シンク:流し込み先)」と呼びます。
やってはいけない「危ない書き方」の例
// URLの # 以下のパラメータを取得
const params = new URLSearchParams(window.location.hash.substring(1));
const name = params.get(“name”);
// 【超危険!】そのままHTMLとしてブラウザに流し込む
// 攻撃者が を送ってきたら…即実行されます
document.getElementById(“welcome-msg”).innerHTML = “こんにちは、” + name + “さん!”;
この innerHTML は、HTMLタグを解釈してしまうため、悪意のあるスクリプトが混ざっているとそのまま動いてしまいます。これが「玄関の内側の鍵を開けられる」瞬間です。
—
3. どうやって防ぐ?「二重の防犯」を身につけよう
対策は難しくありません。基本は「信用しないこと」と「正しく処理すること」の2点です。
対策その1:テキストとして扱う(textContent)
HTMLとして解釈させたくないなら、innerHTMLではなくtextContentを使いましょう。これなら、どんなタグを送り込まれても、ただの「文字」として表示されるだけです。
// 【安全】これならスクリプトは動かない!
// 攻撃コードも単なる文字列として表示されるだけになります
document.getElementById(“welcome-msg”).textContent = “こんにちは、” + name + “さん!”;
対策その2:CSP(コンテンツ・セキュリティ・ポリシー)で蓋をする
もし仮にコードのどこかにバグがあったとしても、最終的な砦として「CSP」を設定しておきましょう。これは、「ブラウザに対して、許可された場所からのスクリプトしか動かさないぞ!」とルールを決める防犯センサーのようなものです。
HTTPレスポンスヘッダーに以下のように設定します。
信頼できる場所(自ドメインなど)のスクリプトしか実行を許可しない設定
Content-Security-Policy: default-src ‘self’; script-src ‘self’;
これを設定しておけば、万が一外部から怪しいスクリプトが混入しても、ブラウザが「許可されていない怪しいやつは動かさない!」と門前払いしてくれます。
—
最後に:一歩ずつ、「疑うこと」から始めよう
セキュリティというと、「何かすごいツールを使わなきゃいけないの?」と思われがちですが、実は「自分が書いたコードが、どういう入力を受け取って、どこに吐き出しているか」を意識するだけで、防げる攻撃は山ほどあります。
- URLや外部からのデータは、すべて「泥棒が書いたメモ」かもしれないと疑う。
innerHTMLなどの強力な関数を使うときは、「本当にこれが必要か?」と一呼吸置く。
まずはこの心構えを持つだけで、あなたのコードは格段に強固になります。一緒に、安全で頼られるエンジニアを目指していきましょう!
何か分からないことがあれば、いつでも現場の視点でお答えします。今日から一歩ずつ、セキュアな開発を楽しんでくださいね。
コメント