こんにちは!Web開発やITインフラの管理、本当にお疲れ様です。
新しいシステムを作ったり、セキュリティのことを考えたりするのって、覚えることがたくさんあって大変ですよね。「なんだか専門用語ばかりで難しそう…」と感じていらっしゃる方も多いのではないでしょうか。
でも、安心してください!セキュリティ対策は、私たちの日常生活にある「防犯」の仕組みと同じ考え方がベースになっています。今回は、Webアプリの代表的な脆弱性の一つである「CSRF(クロスサイトリクエストフォージェリ)におけるトークン検証不備」について、身近な例えを交えながら、一歩ずつ優しく紐解いていきたいと思います。
一緒に、安全なWebサイトを作るためのコツを学んでいきましょう!
—
1. 身近な防犯で例える「CSRF(クロスサイトリクエストフォージェリ)」とは?
まずは、攻撃者がどんな手口を使うのか、私たちの日常生活に置き換えて考えてみましょう。
皆さんのご自宅には、玄関の鍵がありますよね。お出かけするときには、ちゃんと鍵をかけて施錠します。
ある日、あなたが信頼している近所のカフェに入り、カウンターに「合鍵」をうっかり置き忘れてしまったと想像してください。泥棒がその合鍵をこっそり持って、あなたの家に入り込み勝手に模様替えをしたり、荷物を持ち出したりしたら……想像するだけでも恐ろしいですよね。
この例え話をWebの世界に置き換えてみましょう。
- あなた(ユーザー):Webサイトにログインして、パスワードの変更画面などを開いている状態。
- 近所のカフェ(悪意ある外部サイト):あなたが何気なく踏んでしまった、怪しい広告や悪質なWebページ。
- 合鍵(Cookieなどのセッション情報):ブラウザが自動的に持っている「ログイン中ですよ」という証明書。
CSRF(クロスサイトリクエストフォージェリ)とは、まさにこの「あなたがログインしている状態(合鍵を持っている状態)を悪用して、悪意ある外部のWebサイトが、あなたの代わりに勝手にサーバーへ命令を送らせる攻撃」のことなんです。
—
2. なぜ起こる?「トークン検証不備」のメカニズム
では、なぜこの攻撃が成功してしまうのでしょうか?
最大の原因は、サーバー側が「そのリクエストは、本当に本人の意思でボタンが押されたものか?」を確認していない(=検証の仕組みがない)ことにあります。
先ほどの家の例えで言うと、泥棒があなたの合鍵を使って勝手に家に入ってきたとき、家の中に「本人確認用のパスワード(合言葉)」や「使い捨ての入場パス」が用意されていればどうでしょう?
泥棒は合鍵(Cookie)は持っていますが、使い捨てのパス(CSRFトークン)までは持っていないため、家の中に入ることができませんよね。
Webの世界でも同じです。サーバーが「あなただけの使い捨ての合言葉(CSRFトークン)」を発行し、フォームを送信するときにその合言葉が合っているかをチェックしていれば、外部のサイトから勝手に命令を送られても「合言葉がないからダメです!」と跳ね返すことができます。
しかし、このトークンの検証がごっそり抜け落ちているエンドポイント(窓口)があると、攻撃者は簡単にあなたの権限を乗っ取って、パスワード変更や退会手続きなどの危険な操作を実行できてしまうのです。
—
3. 攻撃者はどうやって仕掛けるのか?(イメージとコード例)
ここでは、新人開発者の皆さんが「こういう仕組みで攻撃が成立してしまうんだ」と直感的に理解できるよう、攻撃者が使う手口の仕組みを覗いてみましょう。
例えば、ある掲示板サイトに、攻撃者が次のような悪意あるHTMLを仕込んだとします。
<!-- 悪意ある外部サイト(または掲示板の書き込みなど)に隠された罠 -->
<!-- ユーザーがこのページを開くと、目に見えないところで裏側の処理が走ります -->
<form id="maliciousForm" action="https://example.com/api/update-password" method="POST">
<!-- 被害者の知らぬ間に、パスワードを勝手に書き換えようとするパラメータ -->
<input type="hidden" name="new_password" value="attacker_password123">
</form>
<script type="text/javascript">
// ページが読み込まれた瞬間に、自動的にフォームを送信(サブミット)する
document.getElementById('maliciousForm').submit();
</script>
被害者であるユーザーが、もしすでに https://example.com にログインしていた場合、ブラウザは「この人はログイン中だから」と気を利かせて、自動的にそのサイト用のCookie(合鍵)をリクエストに添えて送信してしまいます。
もし、サーバー側が https://example.com/api/update-password で「CSRFトークン」のチェックを行っていなければ、サーバーは「おっ、本人からのパスワード変更依頼だな」と勘違いして、パスワードを勝手に書き換えてしまうのです。これが恐ろしい「トークン検証不備」の実態です。
—
4. 一歩ずつ学ぶ!確実な対策と実装のポイント
「じゃあ、どうやって防げばいいの?」という疑問が湧いてきますよね。安心してください。対策はとてもシンプルで、すべてのフォームや重要なリクエストに「推測不可能な使い捨てのトークン(CSRFトークン)」を仕込み、サーバー側で厳重にチェックするだけです。
実際のPHPを例に、安全な実装方法を見てみましょう。
① トークンの生成とフォームへの埋め込み(サーバー側・画面側)
まずは、ユーザーがフォーム画面を開いたタイミングで、セッションごとに予測不可能なランダムな文字列(トークン)を生成し、画面の隠しフィールド(input type="hidden")に埋め込みます。
<?php
// セッションを開始
session_start();
// まだセッションにCSRFトークンがなければ、安全なランダム文字列を生成して保存する
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // 32バイトの安全なランダム値
}
?>
<!-- フォームを表示するHTML部分 -->
<form action="update-password.php" method="POST">
<!-- サーバー側でチェックするためのCSRFトークンを隠し項目として持たせる -->
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8'); ?>">
<label>新しいパスワード:</label>
<input type="password" name="new_password">
<button type="submit">パスワードを変更する</button>
</form>
② 送信されたトークンの検証(受け取り側)
次に、ユーザーがボタンを押してデータが送信されてきたとき、サーバー側で「届いたトークン」と「セッションに保存しておいたトークン」が完全に一致するかをチェックします。
<?php
session_start();
// リクエストがPOSTメソッドの場合のみ処理を実行
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. 送信されてきたトークンと、セッションのトークンが存在するか確認
// 2. hash_equals関数を使って、タイミング攻撃(処理時間の差を突く攻撃)を防ぎながら安全に比較する
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
// トークンが一致しない、または存在しない場合は処理を中断!
header('HTTP/1.1 403 Forbidden');
die('不正なリクエストが検出されました(CSRFエラー)。');
}
// --- ここから下に、安全なパスワード変更の処理を記述する ---
// $new_password = $_POST['new_password'];
// ... データベースの更新処理など ...
echo 'パスワードが正常に変更されました!';
}
?>
このように、「画面を表示するときに合言葉を発行し、受け取るときにその合言葉が正しいかを厳しくチェックする」という基本ルールを徹底するだけで、悪意ある外部サイトからの不正なリクエストを綺麗にシャットアウトすることができます。
—
5. まとめ
今回は、CSRFにおけるトークン検証不備の仕組みと、その対策について身近な例えを交えて解説しました。
- 攻撃の本質:ログイン中のユーザーの「合鍵(Cookie)」を悪用し、外部サイトから勝手に命令を送らせる手口。
- 原因:サーバー側で「本当に本人の意思による操作か(使い捨ての合言葉があるか)」を検証していないこと。
- 対策:予測不可能なCSRFトークンを生成・付与し、受信時に必ず
hash_equals等で厳重に比較・検証すること。
セキュリティの対策は、最初は難しく感じるかもしれませんが、一つひとつの仕組みはとてもロジカルで理にかなっています。ぜひ今回のコードや考え方を参考に、ご自身の開発現場やプロジェクトでも「トークンのチェックが漏れているエンドポイントがないか」を確認してみてくださいね。
一歩ずつ、安全で堅牢なWebアプリケーションを作っていきましょう!応援しています!
コメント