現場で差がつく!JavaScriptによるXSS対策:文字列エスケープの「最適解」
フロントエンド開発の現場で、もっともやってはいけないミスの一つが「ユーザー入力をそのままHTMLに流し込むこと」だ。XSS(クロスサイトスクリプティング)は、もはや古典的な脆弱性と言われながらも、実務の現場ではいまだに「ちょっとした油断」から重大なインシデントを引き起こす。
今日は、フレームワークに頼り切るのではなく、ブラウザとJavaScriptの挙動を深く理解した上で、「いかに堅牢に、かつパフォーマンスを損なわずに文字列をエスケープするか」という、中級エンジニアなら押さえておくべき知見を共有しよう。
—
なぜ「手動エスケープ」の理解が不可欠なのか?
ReactやVueのようなモダンフレームワークは、デフォルトで文字列を自動エスケープしてくれる。しかし、`dangerouslySetInnerHTML`を使う場面や、レガシーなjQueryベースのプロジェクト、あるいは純粋なVanilla JSでDOMを構築する際、この「防御壁」は自分たちで構築しなければならない。
ブラウザはHTMLをパースする際、`<` や `&` を見つけると、「ここからタグが始まるのか?」「これは実体参照か?」と解釈を切り替える。攻撃者はこの挙動を悪用して、`‘;
const safeHTML = escapeHTML(userInput);
console.log(safeHTML);
// 出力: <script>alert("XSS")</script>
このコードの「こだわり」ポイント
1. 正規表現のグローバル検索 (`/g`): これにより、文字列全体を一度のスキャンで処理できる。計算量は文字列の長さに依存するのみで、非常に効率的だ。
2. `escapeMap`の分離: ロジックとデータを分けることで、将来的にエスケープ対象を増やしたい(例えばバッククォートなど)場合にも即座に対応できる。
3. シングルクォートの扱い: `’` を `'` にしているのは、JavaScriptの文字列リテラルやHTML属性値の中で安全に扱うためのベストプラクティスだ。
—
テンプレートリテラルとの「危険な関係」
最近はテンプレートリテラル(バッククォート)を使ってHTMLを組み立てることが多いはずだ。しかし、テンプレートリテラルは「エスケープしてくれない」。
// 危険な書き方
const name = ‘‘;
const html = `
`; // これをそのままDOMに挿入すると発火する
これを防ぐためには、「変数展開する際に必ずエスケープ関数を通す」というルールをチームで徹底すること。もし可能なら、タグ付きテンプレートリテラル(Tagged Template Literals)を自作して、自動でエスケープがかかる仕組みを作ると、ヒューマンエラーを劇的に減らせる。
—
ブラウザの裏側を覗く:`textContent`という最強の武器
ここまでエスケープ処理を解説してきたが、実は「HTMLとして挿入する必要がない」なら、もっと強力な手段がある。それが `textContent` だ。
const div = document.createElement(‘div’);
// 危険な文字列をそのまま代入しても、ブラウザが自動的にテキストとして処理してくれる
div.textContent = userInput;
document.body.appendChild(div);
`innerHTML` は文字列をHTMLとしてパースするが、`textContent` は「中身をテキストノードとして扱う」。ブラウザ側で解釈されることが物理的に不可能なため、XSSの心配がゼロになる。
現場の格言:「innerHTMLは最終手段。textContentで解決できるなら、それに越したことはない。」
—
最後に:油断は最大の脆弱性
XSS対策は、一度実装すれば終わりではない。新しいUIコンポーネントを作るたびに、「この文字列はどこから来たのか?」「エスケープは漏れていないか?」と自問自答する癖をつけること。
コードは嘘をつかないが、書いた人間は簡単に嘘をつく。フレームワークに甘えず、こうした低レイヤーの挙動を肌感覚で理解しているエンジニアこそが、真に頼れるフロントエンドのスペシャリストだ。
今日紹介した `escapeHTML` 関数を、自分のプロジェクトの `utils.js` に忍ばせておいてくれ。きっと、いざという時に君の背中を守ってくれるはずだ。

コメント