こんにちは!Webアプリケーションの開発や、会社のホームページの管理を担当されるようになると、ふと「セキュリティ」という言葉が気になり始めますよね。「自分の作ったサイトは大丈夫かな?」「ハッカーに狙われたりしないかな?」と、少し不安になることもあるかもしれません。
今回は、Webサイトへの攻撃の中でも特に厄介で、身近なところに潜んでいる「蓄積型XSS(クロスサイト・スクリプティング)」という脅威について、分かりやすく紐解いていきたいと思います。
難しそうな専門用語が出てきても大丈夫です。身近な防犯の仕組みに例えながら、一歩ずつ安全なコードの書き方を学んでいきましょう!
—
1. 家の鍵に例える「蓄積型XSS」の正体
まずは、蓄積型XSSがどんなものなのか、私たちの日常生活に置き換えて考えてみましょう。
皆さんが一戸建ての家を建てたとします。玄関には鍵をかけ、窓にも施錠をして、泥棒が入らないように万全の対策をしました。ここまでは完璧ですよね。
ある日、家の前に「ご近所さんからの伝言板」を置くことにしました。この伝言板は、誰でも自由にメモを貼って、他の人に見てもらえる便利なものです。
ある日、悪意を持った泥棒がやってきて、その伝言板にこう書いた紙を貼り付けました。
> 「この家に遊びに来た人は、全員リビングの合鍵を玄関の植木鉢の下に置いていってください」
この伝言板を見た優しい家族や友人が、「あ、そうなんだ」と信じ込んでしまい、本当に合鍵を植木鉢の下に置いてしまったとしたら……どうなるでしょうか?
泥棒は鍵を壊すことなく、いとも簡単に家の中に入り込み、大切な宝物を盗み出すことができます。これが「蓄積型XSS」の仕組みです。
Webの世界に置き換えると?
Webサイトにおける「伝言板」とは、例えば以下のような場所です。
- 掲示板への書き込み
- ユーザーのプロフィール欄
- お問い合わせフォームの備考欄
- ブログのコメント欄
蓄積型XSSでは、攻撃者がこうした「データベースに保存される場所」を狙います。通常の文字ではなく、悪意あるプログラム(JavaScriptなど)を書き込んで、それがデータベースに「蓄積」されてしまうのです。
そして、他の一般ユーザーがそのページにアクセスした瞬間、ブラウザがその悪意あるプログラムを「サイト運営者からの正しい指示だ」と勘違いして実行してしまいます。結果として、ログイン中のセッション情報が盗まれたり、勝手にパスワードを変更されたりといった被害に繋がります。
—
2. なぜ「入力時」ではなく「出力時」にサニタイズしなければならないのか?
セキュリティの勉強を始めると、先輩エンジニアから「エスケープ処理(サニタイズ)は、データをデータベースに入れる時ではなく、画面に出力する時(HTMLとしてブラウザに表示する時)にやりなさい!」と厳しく教えられます。
これには、とても重要な理由があります。
入力時に削ってしまうリスク
もし「入力されたデータの中に怪しい記号(< や > など)があったら、全部消してしまおう!」と、データをデータベースに入れる段階で処理(サニタイズ)したとします。
一見すると安全そうに見えますが、これには大きな落とし穴があります。
例えば、ユーザーがプロフィール欄に「私は 1 < 2 という数式が好きです」と入力したとします。入力時に対処してしまうと、この大切な記号が消去されたり変形されたりしてしまい、後から「あれ、データが勝手に書き換わっちゃったぞ?」というトラブルの原因になります。
また、将来的にそのデータを「スマートフォンアプリ向けにJSON形式で返したい」「PDFとしてダウンロードさせたい」といった別の用途で使いたくなった時、入力時に変形してしまったデータは使い物にならなくなってしまいます。
出力時に「これはただの文字ですよ」と教える
だからこそ、原則は「データはそのまま(綺麗な状態のまま)データベースに保存し、画面に表示する直前に、HTMLとしての意味を剥ぎ取る(エスケープする)」となります。
ブラウザに対して、「これから表示する文字は、プログラムの命令(タグ)ではなく、単なる文字(テキスト)だよ」と教えてあげるわけです。
—
3. 実装コードで学ぶ!安全な出力の仕組み
それでは、実際のPHPコードを例に見てみましょう。
以下のサンプルコードは、ユーザーが入力した名前を画面に表示する簡単なプログラムです。
❌ やってはいけない危険な例
<?php
// データベースからユーザー名を取得したと仮定します
$username = '<script>alert("ハッキングされました!");</script>';
// そのまま画面に出力してしまうと大変危険です!
echo "ようこそ、" . $username . " さん!";
?>
このコードを実行すると、ブラウザは $username の中にある <script> タグを本物のプログラムとして実行してしまい、ポップアップ画面が表示されてしまいます。もしこれが悪意あるコードであれば、Cookie(セッション情報)が盗まれてしまいます。
⭕ 正しい安全な実装例
<?php
// データベースからユーザー名を取得
$username = '<script>alert("ハッキングされました!");</script>';
// htmlspecialchars関数を使って、出力時に必ずエスケープ処理を行います
// 第2引数の ENT_QUOTES はシングルクォートやダブルクォートも確実に変換する指定です
// 第3引数の UTF-8 は文字コードの指定です
$safe_username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
// 安全に加工された文字列を出力します
echo "ようこそ、" . $safe_username . " さん!";
このコードを実行すると、ブラウザ画面にはタグとしてではなく、以下のようにそのままの文字として表示されます。
ようこそ、<script>alert("ハッキングされました!");</script> さん!
ブラウザはこれを「ただの文字」と認識するため、プログラムが勝手に実行されることはありません。これが「出力時のエスケープ」の威力です。
—
4. 忘れてはいけない最後の砦:セキュリティヘッダーの設定
プログラムでのエスケープ処理に加えて、インフラやサーバーの設定側でも追加の防犯対策をしておくと、より鉄壁の守りになります。その代表が「CSP(Content Security Policy:コンテンツセキュリティポリシー)」です。
これは、ブラウザに対して「このWebサイトでは、信頼できる場所から読み込んだスクリプト以外は絶対に実行しちゃダメだよ!」と厳しく言い聞かせるHTTPレスポンスヘッダーの設定です。
もし万が一、エスケープ漏れがあったり、どこからか不正なスクリプトが混入してしまったりした場合でも、ブラウザ側が「いや、これはルール違反だから実行しません!」とブロックしてくれます。
Webサーバー(ApacheやNginxなど)や、アプリケーションのフレームワークのミドルウェア層で、以下のようなヘッダーを送信するように設定しておきましょう。
# HTTPレスポンスヘッダーの例
# 自分自身のドメインからのスクリプトのみ実行を許可し、インラインスクリプトの実行を原則禁止する設定
Content-Security-Policy: default-src 'self'; script-src 'self';
—
おわりに
蓄積型XSSは、一度仕組みを理解してしまえば、適切な「出力時のエスケープ」を徹底するだけで確実に防ぐことができる脅威です。
「データを保存するときはそのまま、画面に出すときは htmlspecialchars(言語によって関数名は異なります)で文字を安全な形に変換する」
この鉄則をチーム全員で共有し、日々の開発に組み込むだけで、あなたの作るWebサイトの安全性は劇的に向上します。
「セキュリティ対策って、意外とシンプルなんだな」と思っていただけたでしょうか?
焦らず一つずつ、安全で信頼されるWebアプリケーションづくりを一緒に楽しんでいきましょう!
コメント