なぜ、その「innerHTML」があなたのシステムを沈没させるのか?
現場でコードレビューをしていると、未だに「とりあえずこれで動くから」と innerHTML を多用しているエンジニアによく出会う。正直に言おう。そのコードは、あなたのアプリケーションに対する「裏口」を常に開けっ放しにしているのと同じだ。
今回は、Webフロントエンドにおいて最も基本的でありながら、最も軽視されがちな「DOM操作のセキュリティ」について、現場の泥臭い知見を交えて解説する。
—
1. innerHTMLの「悪魔の誘惑」とその正体
innerHTML は、文字列をHTMLとしてパースし、DOMツリーに展開する。開発者にとっては、動的にUIを構築できる便利なツールだ。しかし、この「HTMLとしてパースする」という性質こそが、攻撃者にとっての格好の入り口になる。
攻撃者が見ている「盲点」:PoCで理解する
例えば、ユーザーが入力した「名前」を画面に表示するだけの機能で、innerHTML を使ってしまったとしよう。
// 脆弱な実装
const userInput = ““;
document.getElementById(‘display’).innerHTML = “こんにちは、” + userInput;
攻撃者が userInput に上記のような細工をした文字列を送り込むと、ブラウザは「imgタグの読み込み失敗」をトリガーにして、onerror 属性内の悪意あるJavaScriptを実行する。
これがXSS(クロスサイトスクリプティング)の基本形だ。もしこれが alert ではなく、「セッションIDを外部の攻撃者サーバーに送信するスクリプト」だったらどうなるか? 想像するだけで背筋が凍るはずだ。WAFをすり抜ける難読化手法などは後を絶たない。根本的な防御は「ブラウザにHTMLとして解釈させないこと」に尽きる。
—
2. textContentという最強の盾
一方で textContent は、その名の通り「テキスト」としてのみ扱う。たとえ入力値の中に タグやイベントハンドラが混入していても、これらはすべて「ただの文字列」として画面に表示されるだけで、プログラムとして実行されることは絶対にない。
「HTMLタグを含めて表示させたい」という要件がある場合はどうするか? その時は innerHTML を使うのではなく、「DOMPurify」のようなライブラリを使ってサニタイズ(無害化)した上でDOMに挿入するのが、今のモダンWeb開発の鉄則だ。
---
3. 実践:セキュアな実装コード
ここからは、実務でそのまま使える安全な実装パターンを紹介する。
ケースA:単純なテキスト表示(最も安全)
ユーザー入力をそのまま表示する場合、迷わず textContent を使うこと。
// セキュアな実装:textContentを使用
const userInput = "";
const displayElement = document.getElementById('display');
// これならタグは実行されず、文字列としてそのまま表示される
displayElement.textContent = userInput;
ケースB:HTMLタグを一部許可したい場合
どうしてもHTMLをレンダリングする必要があるなら、必ずライブラリを通せ。車輪の再発明はするな。
// npm install dompurify
import DOMPurify from 'dompurify';
const unsafeHTML = "こんにちは";
// DOMPurifyが危険な属性やタグを除去し、安全なHTMLのみを返す
const cleanHTML = DOMPurify.sanitize(unsafeHTML);
document.getElementById('display').innerHTML = cleanHTML;
---
4. インフラ・バックエンド側の二段構え
フロントエンドでの対策に加え、サーバー側でも「防御の深層(Defense in Depth)」を意識した設定を忘れてはならない。
CSP(Content Security Policy)でトドメを刺す
万が一、コードに脆弱性が残っていたとしても、CSPを設定していれば被害を最小限に抑えられる。Nginxの設定例を挙げておく。
Nginx設定例:信頼できないソースからのスクリプト実行を禁止する
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';";
default-src 'self': コンテンツは自分のドメインからのみ読み込む。script-src 'self': インラインスクリプト(innerHTML経由の攻撃含む)の実行を原則禁止する。
---
最後に:プロフェッショナルとしての心得
「動けばいい」というコードは、数ヶ月後の自分やチームメンバーに技術的負債という名の爆弾を手渡す行為だ。
今回紹介した textContent への切り替えは、単なる修正ではない。「ブラウザの解釈に委ねるな、明示的に制御せよ」というセキュリティの基本原則を実践するということだ。
もし今日、あなたのプロジェクトのソースコードを検索して innerHTML が見つかったら、それは「改善のチャンス」だ。今すぐリファクタリングのタスクをチケットに起票してほしい。それができるチームこそが、真に信頼されるエンジニア集団であると私は信じている。
コメント