悪意ある「落書き」からウェブサイトを守る!DOMPurifyでHTML入力を安全にクリーンアップしよう
皆さん、こんにちは!セキュリティ担当の〇〇です。
日々の開発、お疲れ様です。ウェブサイトやアプリケーションを開発していると、ユーザーからの入力を受け付ける機会はたくさんありますよね。例えば、コメント欄やプロフィール編集、問い合わせフォームなど。これらの「ユーザーからの入力」が、実はサイバー攻撃の入口になってしまうことがあるんです。
今回は、そんな悪意ある入力からウェブサイトを守るための強力な味方、「DOMPurify」というライブラリについて、その基本的な使い方から、ちょっと踏み込んだ設定まで、初心者の方にも分かりやすく解説していきたいと思います。「インジェクション攻撃?」「サニタイズって何?」という方も、ご安心ください。身近な例え話を交えながら、一歩ずつ対策を学んでいきましょう!
そもそも「インジェクション攻撃」って、どんな怖いものなの?
まず、DOMPurifyがなぜ必要になるのか、その背景にある「インジェクション攻撃」について、簡単に触れておきましょう。
インジェクション攻撃は、様々な種類がありますが、中でもHTMLやJavaScriptを悪用するものは、ウェブサイトの見た目を書き換えたり、ユーザーの情報を盗み出したりするのに使われやすいんです。
例えるなら、あなたの家の壁に、勝手に落書きをされたり、防犯カメラの映像を書き換えられたりするようなイメージです。
- SQLインジェクション: これは、データベースに命令を送る際に、悪意のある命令を紛れ込ませる攻撃です。家の鍵に、ピッキングツールとは別に、合鍵を作るための特殊な道具を差し込まれるようなイメージでしょうか。データベースにある大切な情報(顧客リストなど)が盗まれたり、改ざんされたりする可能性があります。
- OSコマンドインジェクション: これは、サーバー上で動いているOS(WindowsやLinuxなど)に、普段は使わないような「裏口」から命令を送る攻撃です。家のドアに鍵がかかっていても、勝手に窓をこじ開けられたり、電話回線を使って勝手に指示を出されたりするようなイメージです。サーバーを踏み台にされて、他のシステムに攻撃を仕掛けられたり、データを破壊されたりする危険性があります。
そして、今回焦点を当てるのは、HTMLインジェクションと呼ばれるものです。これは、ウェブページに表示されるHTMLコードの中に、悪意のあるコードを紛れ込ませる攻撃です。
例えば、コメント欄に以下のようなコードが投稿されたとします。
この製品は素晴らしい!
もし、このコメントがそのままウェブサイトに表示されてしまったらどうなるでしょう?「あなたのクッキーを盗みました!」というメッセージが、ユーザーのブラウザに表示されてしまいます。これは、ユーザーのログイン情報などを盗むための、第一歩となる可能性があるんです。まるで、家のポストに「この郵便物、中身は秘密だよ」と書かれた怪しい封筒が勝手に投函されているようなものです。
DOMPurify:ウェブサイトの「検閲官」として悪意あるコードを無力化!
そこで登場するのが、DOMPurifyです。DOMPurifyは、JavaScriptで書かれたライブラリで、ウェブサイトが受け取ったHTML入力を「検閲」し、悪意のあるコードを無害なものに変換してくれる役割を担います。
例えるなら、あなたが書いた手紙を、ポストに投函する前に「検閲官」がチェックしてくれるようなものです。もし、手紙の中に「危険な道具の作り方」のような怪しい情報が書かれていたら、「これはダメですよ!」と消したり、無害な文章に書き換えたりしてくれます。
DOMPurifyは、次のような方法で悪意あるコードを無害化します。
- 不審なタグや属性の削除:
タグやonerrorのようなイベント属性など、悪用されやすいものを削除します。 - URLの無害化:
javascript:のような、実行すると危険な処理が走る可能性のあるURLを、安全なもの(例えばabout:blank)に書き換えます。
DOMPurifyの基本的な使い方:まずはシンプルに導入してみよう!
DOMPurifyを導入するのは、実はとても簡単です。まずは、最も基本的な使い方を見てみましょう。
1. DOMPurifyの読み込み
HTMLファイルに、以下のようにDOMPurifyのCDN(Content Delivery Network)を読み込みます。
ユーザーからのコメント
このコードでは、
1. でDOMPurifyライブラリを読み込んでいます。
2. userInput という変数に、悪意のある可能性のあるHTMLコードを格納しています。
3. DOMPurify.sanitize(userInput) で、userInput の内容をDOMPurifyに渡して、安全なHTMLに変換しています。
4. 変換された cleanHtml を、comment-output というIDのdiv要素の中に表示しています。
このコードを実行すると、alert()関数やonerror属性は削除され、安全なHTMLとして表示されるはずです。まるで、落書きされた壁紙が、きれいに塗り直されたようなイメージですね。
設定をカスタマイズしよう:より強固なセキュリティのために
DOMPurifyは、デフォルトでも十分安全なのですが、ウェブサイトの要件に合わせて、さらに細かく設定をカスタマイズすることも可能です。これにより、攻撃者がさらに巧妙な手口で侵入しようとしても、しっかりと防御できるようになります。
許可するタグや属性を限定する (ALLOWED_TAGS, ALLOWED_ATTR)
「うちのサイトでは、タグは使うけど、タグは絶対に必要ない!」という場合、DOMPurifyに「このタグだけは許可するよ」と明示的に指示することができます。
// 許可するタグを明示的に指定する
const allowedTags = ['p', 'a', 'strong', 'em', 'br', 'img'];
// 許可する属性を明示的に指定する(imgタグにはalt属性だけ許可)
const allowedAttrs = {
'a': ['href', 'target', 'title'],
'img': ['src', 'alt']
};
const options = {
ALLOWED_TAGS: allowedTags,
ALLOWED_ATTR: allowedAttrs
};
// カスタム設定でサニタイズする
const cleanHtmlCustom = DOMPurify.sanitize(userInput, options);
document.getElementById('comment-output').innerHTML = cleanHtmlCustom;
この設定では:
ALLOWED_TAGS:,,,,,のタグのみを許可しています。ALLOWED_ATTR:タグにはhref,target,title属性、タグにはsrc,alt属性のみを許可しています。
このように、必要最低限のタグと属性だけを許可することで、未知の脆弱性を突かれるリスクをさらに低減できます。これは、家の鍵を「この鍵穴に合う鍵以外は絶対に回らない」と設定するような、より厳密なセキュリティ対策と言えますね。
FORBID_TAGS と FORBID_ATTR で、特定のタグや属性を禁止する
逆に、「このタグや属性は、どんな理由があっても使われたくない!」という場合に便利なのが、FORBID_TAGS と FORBID_ATTR です。
// 禁止したいタグを指定する
const forbiddenTags = ['script', 'iframe'];
// 禁止したい属性を指定する
const forbiddenAttrs = ['onerror', 'onload', 'onclick'];
const optionsForbidden = {
FORBID_TAGS: forbiddenTags,
FORBID_ATTR: forbiddenAttrs
};
// 禁止設定でサニタイズする
const cleanHtmlForbidden = DOMPurify.sanitize(userInput, optionsForbidden);
document.getElementById('comment-output').innerHTML = cleanHtmlForbidden;
この例では、やタグ、そしてonerrorなどのイベント属性を明確に禁止しています。これは、泥棒が侵入しそうな「怪しい場所」を、あらかじめ「立ち入り禁止」の看板で明示するようなイメージです。
URLの処理をカスタマイズする (URL_POLICY)
URLについても、より詳細な制御が可能です。例えば、「うちのサイトのドメイン(例: example.com)からのリンクは許可するけど、それ以外の外部ドメインへのリンクは禁止したい」といった設定ができます。
const optionsUrlPolicy = {
// URLポリシーを設定
URL_POLICY: {
// "default"は、その他のプロトコルやドメインに対するデフォルトの処理
"default": (url) => {
// 許可するプロトコル(例: http, https, mailto)
const allowedProtocols = ['http', 'https', 'mailto'];
if (allowedProtocols.includes(url.protocol)) {
// 許可するドメイン(例: example.com)
const allowedDomains = ['example.com'];
if (allowedDomains.includes(url.host)) {
return url.build(); // 安全なURLとしてそのまま返す
}
}
return 'about:blank'; // それ以外は無害なURLに置き換える
},
// 特定のプロトコル(例: 'mailto')に対する処理を個別に定義することも可能
// "mailto": (url) => { / ... / }
}
};
const userInputWithLink = '
公式サイトはこちら: Example.com
怪しいサイト: Malicious
';
// URLポリシーを設定してサニタイズする
const cleanHtmlUrl = DOMPurify.sanitize(userInputWithLink, optionsUrlPolicy);
document.getElementById('comment-output').innerHTML = cleanHtmlUrl;
この設定では:
URL_POLICYというオブジェクトで、URLの処理ルールを定義します。"default"の部分で、デフォルトの処理を記述します。allowedProtocolsで許可するプロトコル(http,https,mailtoなど)を指定します。allowedDomainsで、許可するドメイン(ここではexample.com)を指定します。- もしURLがこれらの条件に合わない場合は、
'about:blank'という無害なURLに置き換えられます。
これは、家の玄関に「訪問者リスト」を置いて、リストに載っている人だけしか家に入れないようにするような、非常に厳格な管理体制と言えます。
運用上の注意点:DOMPurifyだけでは完璧ではない?
DOMPurifyは非常に強力なツールですが、万能ではありません。いくつか運用上の注意点があります。
- サーバーサイドでのバリデーションも忘れずに: DOMPurifyは主にブラウザ側(クライアントサイド)で動作します。しかし、悪意のある攻撃者はJavaScriptを無効にしたり、ブラウザのDOM操作を迂回したりする可能性があります。そのため、必ずサーバーサイドでも入力値のバリデーション(検証)を行ってください。これは、家の玄関に鍵をかけるだけでなく、窓にも補助錠をつけたり、警備会社と契約したりするような、多層防御の考え方です。
- DOMPurifyのバージョンを最新に保つ: セキュリティの世界は常に進化しています。DOMPurifyも、新しい攻撃手法に対応するために、日々改善されています。定期的にDOMPurifyのバージョンを最新の状態にアップデートすることをお勧めします。これは、家の鍵を定期的に新しいものに交換したり、防犯システムを最新の状態に保ったりするのと同じです。
- 設定は「最小権限の原則」で: DOMPurifyの設定は、必要最低限のものだけを許可するようにしましょう。あれもこれもと許可しすぎると、かえってセキュリティホールを作ってしまう可能性があります。「この機能は本当に必要か?」と常に自問自答しながら、設定を調整していくことが大切です。
まとめ:安全なウェブサイト開発のために、DOMPurifyを味方につけよう!
今回は、インジェクション攻撃のメカニズムと、それを防ぐためのDOMPurifyの使い方について解説しました。
- ユーザーからの入力には、悪意のあるコードが紛れ込んでいる可能性があることを理解する。
- DOMPurifyは、そんな悪意あるコードを無害化してくれる強力なライブラリである。
- 基本的な使い方だけでなく、
ALLOWED_TAGSやURL_POLICYなどの設定をカスタマイズすることで、より強固なセキュリティを実現できる。 - DOMPurifyはクライアントサイドだけでなく、サーバーサイドでのバリデーションも併用し、常に最新の状態に保つことが重要である。
ウェブサイトやアプリケーションを安全に開発することは、ユーザーからの信頼を得る上で非常に重要です。DOMPurifyのようなツールを上手に活用し、悪意ある「落書き」から大切なウェブサイトを守っていきましょう。
「難しそう…」と感じた方も、まずは簡単な導入から試してみてください。一つずつ、できることから対策を学んでいくことが、セキュリティ強化への確実な一歩となります。
もし、さらに深い部分や、具体的な実装で困ったことがあれば、いつでも気軽に質問してくださいね!一緒に、より安全なウェブの世界を築いていきましょう!
コメント