【入門編】 CSRFトークンの生成・検証とSameSite属性の適切な活用 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

こんにちは!インフラやセキュリティの現場を渡り歩いてきたホワイトハッカーの私ですが、今回は新人のIT担当者や、これからWebセキュリティを本格的に学びたいという開発者の方に向けて、とっても大切なテーマを解説します。

今日のテーマは、Webアプリを狙う代表的な攻撃の一つ「CSRF(クロスサイトリクエストフォージェリ)」と、その鉄壁の守り方である「CSRFトークン」と「SameSite属性」についてです。

「なんだか名前が難しそう……」と思ったそこのあなた、大丈夫ですよ!一歩ずつ、私たちの身の回りの防犯に置き換えながら、優しく紐解いていきましょう。

—

1. CSRF(クロスサイトリクエストフォージェリ)ってどんな攻撃?

まずは、敵を知ることから始めましょう。CSRFがどんな悪さをするのか、私たちの「お家」に例えて考えてみますね。

身近な例え:勝手に合鍵を使われる「なりすまし泥棒」

想像してみてください。あなたは信頼できるカフェの会員で、いつも自分のIDとパスワードでログインして、マイページからお買い物をしています。ブラウザ(Internet ExplorerやChromeなど)は、あなたが「ログイン中」であることを記憶するために、お店の会員証のようなもの(Cookie)をポケットに入れています。

さて、あなたがそのカフェのログイン状態のまま、ちょっと怪しい悪意あるWebサイト(罠サイト)にうっかりアクセスしてしまったとします。

この罠サイト、実は裏側でこっそりあなたのブラウザに対して、こう命令するプログラムを隠し持っています。

  • 「おい、あのカフェのサイトに飛んで、この人の貯金から勝手に買い物をすませてこい!」

あなたのブラウザは、あなたが罠サイトにいることなどお構いなしに、ポケットに入っている「本物の会員証(Cookie)」を自動的に添えて、カフェのサイトにリクエストを送ってしまいます。
カフェの店員(サーバー)から見ると、「おっ、いつも来てくれる常連さん(あなた)からの正しい注文だな!」と見えてしまうため、本人の意思とは関係なく、勝手にお金が引き落とされてしまう……。これがCSRFの恐ろしいメカニズムです。

要するに、「ユーザーが意図しないリクエストを、信頼されたサイトに対して強制的に実行させる」のがCSRFという攻撃なんですね。

—

2. 泥棒を防ぐ多層防御の鍵:CSRFトークンとSameSite属性

この厄介な泥棒を防ぐために、現代のWebセキュリティでは「多層防御(何重もの鍵をかけること)」が基本になります。私たちが使うのは、次の2つの強力な武器です。

1. CSRFトークン(ステートフルな合言葉の検証)
2. CookieのSameSite属性(ブラウザの賢いおせっかい機能)

それぞれの仕組みを、詳しく見ていきましょう。

—

3. その1:CSRFトークンで「本人の確かな意思」を確認する

CSRFトークンというのは、簡単に言うと「その場限りで使い捨ての特別な合言葉(チケット)」のことです。

仕組みのイメージ

先ほどのカフェの例に戻りましょう。
お店側が、あなたが買い物のページを開くたびに、毎回ランダムで絶対に他人は予測できない「今日の合言葉(CSRFトークン)」をこっそり発行して、入力フォームのなかに見えないように(hiddenフィールドとして)忍ばせます。

もし、悪意ある罠サイトが勝手に注文の命令を送ろうとしても、この「今日の合言葉」を知り得ないため、サーバー側で「おや、合言葉が入っていないぞ、あるいは間違っているな!」と気づき、ピシャリとリクエストを拒否できるのです。

実装のコード例(PHPによるステートフルな検証)

実際に、開発の現場でどのように実装するのか、シンプルなPHPのコードを見てみましょう。設定やコメントを丁寧に書いているので、そのまま参考にしてみてくださいね。

<?php
// セッションを開始します(サーバー側にユーザーの状態を保存するステートフルな仕組み)
session_start();

// 1. フォームを表示する画面の場合
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    // まだセッションにCSRFトークンがなければ、予測困難なランダム文字列を生成して保存します
    if (empty($_SESSION['csrf_token'])) {
        // random_bytesは暗号学的に安全なランダムなバイト列を生成します
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    $token = $_SESSION['csrf_token'];
}

// 2. ユーザーがフォームを送信(POST)してきた場合
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 送信されてきたトークンと、セッションに保存しておいたトークンを比較します
    $submitted_token = $_POST['csrf_token'] ?? '';
    
    // hash_equalsを使うことで、タイミング攻撃というセキュリティホールを防ぎます
    if (!hash_equals($_SESSION['csrf_token'], $submitted_token)) {
        // トークンが一致しない場合は、不正なリクエスト(CSRFの疑い)として処理を中断します
        http_response_code(403);
        exit('不正なアクセスが検出されました(CSRFトークンエラー)。');
    }

    // --- ここから下が本来の安全な処理 ---
    // 例: データベースの更新やパスワードの変更など
    echo '正常に処理が完了しました!';
    
    // セキュリティを高めるため、一度使ったトークンは破棄する(または再生成する)のが一般的です
    unset($_SESSION['csrf_token']);
}
?>

<!-- HTMLフォーム側の実装例 -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>安全なフォームのサンプル</title>
</head>
<body>
    <form action="index.php" method="POST">
        <!-- ユーザーには見えない隠しフィールド(hidden)に、合言葉(CSRFトークン)を仕込んでおきます -->
        <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($token, ENT_QUOTES, 'UTF-8'); ?>">
        
        <label>お名前: <input type="text" name="username"></label>
        <button type="submit">送信する</button>
    </form>
</body>
</html>

このように、サーバー側で記憶している合言葉と、送信されてきた合言葉が一致するかどうかを厳密にチェックするのが、CSRFトークンの基本的なアプローチです。

—

4. その2:CookieのSameSite属性で「よそ見」を防ぐ

さて、CSRFトークンだけでもかなり強力ですが、近年のブラウザの進化によって、さらに強力な「おまわりさん」が味方につくようになりました。それがCookieのSameSite属性です。

SameSite属性ってなに?

先ほど、ブラウザが自動的に会員証(Cookie)をポケットから出してしまうのが原因だと言いましたよね。
このSameSite属性は、ブラウザに対して「この会員証は、今いるサイトと同じ場所(Same Site)にいる時だけ出しなさい。別の場所(よそ)から頼まれても、絶対にポケットから出しちゃダメだよ!」と指示する設定です。

SameSite属性には、主に次の3つの設定値があります。

1. SameSite=Strict

  • 一番厳しい設定です。どのようなリンクや別サイトからの遷移であっても、自分がいるドメイン以外の場所から来たリクエストには、一切Cookieを添付しません。セキュリティは最高ですが、外部サイトからリンクを踏んでやってきたユーザーが「毎回ログアウト状態に見えてしまう」といった利便性の問題が起きることがあります。

2. SameSite=Lax

  • 程よいバランスの設定です。通常のリンクをクリックしてサイトを訪れた場合はCookieを渡しますが、別のサイトから画像を読み込ませたり、こっそり裏側でリクエストを送らせるような「悪意ある通信(CSRFの温床になるもの)」の時にはCookieを添付しません。現代のWebアプリケーションでは、デフォルトの標準としてよく使われます。

3. SameSite=None

  • 制限をしない設定です。外部のサイトと連携する仕組み(APIなど)でどうしてもCookieを送る必要がある場合に使います。ただし、この設定を使う場合は、通信の盗聴を防ぐために必ずSecure属性(HTTPSでの通信限定)を一緒に設定することが必須になります。

設定のコード例(HTTPレスポンスヘッダー)

サーバーからブラウザにCookieを渡す際、次のようにSameSite属性とSecure属性をセットで付与します。

# サーバーがブラウザに対してCookieを設定する際のレスポンスヘッダーの例
Set-Cookie: session_id=abc123xyz789; Secure; HttpOnly; SameSite=Lax
  • Secure: 暗号化された通信(HTTPS)でのみCookieを送信します。
  • HttpOnly: JavaScriptからこのCookieを盗み見られないように保護します(XSS対策)。
  • SameSite=Lax: 別サイトからの不正なリクエスト時にCookieの送信をブロックします。

—

5. まとめ:二重の鍵で堅牢なWebアプリケーションを作ろう

いかがでしたでしょうか?
CSRFという攻撃の正体は、ユーザーが知らぬ間にブラウザから送らされてしまう「なりすましリクエスト」でした。

これに対する防御は、決して難しい魔法ではありません。

  • 玄関の二重ロックのように、サーバー側で使い捨ての合言葉を確認する「CSRFトークン」をしっかりと実装する。
  • 番犬のように、ブラウザの自動送信をスマートに制限してくれるCookieの「SameSite属性(LaxやStrict)」を正しく設定する。

この2つを組み合わせることで、あなたの作るWebアプリケーションはぐっと堅牢になり、ユーザーの大切なデータを守ることができます。

セキュリティの対策に「これで完璧」というゴールはありませんが、一歩ずつ正しい知識を身につけ、泥臭く確実な実装を積み重ねていくことが、最高のエンドポイント・Webセキュリティへの近道です。
これからも一緒に、安全で快適な開発ライフを楽しんでいきましょう!

コメント

タイトルとURLをコピーしました