その「勝手に操作」を防ぐ!CSRF攻撃のからくりと、あなたのWebサイトを守る「魔法の合言葉」
Webサイトの開発や運用に携わる皆さん、こんにちは!セキュリティの世界は、なんだか難しそう…と感じている方もいらっしゃるかもしれませんね。でも、大丈夫。今日から一緒に、サイバー攻撃の「あるある」を理解し、それを防ぐための具体的な方法を、身近な例え話を交えながら、一歩ずつ学んでいきましょう。
今回は、Webアプリケーションのセキュリティでよく耳にする「CSRF(クロスサイトリクエストフォージェリ)」という攻撃について、その仕組みと、それを防ぐための「Anti-CSRFトークン」という仕組みを、まるで家の鍵や泥棒対策のように、分かりやすく解説していきます。
そもそもCSRFって、どんな攻撃なの?
突然ですが、皆さんの家には鍵がありますよね。泥棒が勝手に家に入ってこないように、しっかりと鍵をかけていると思います。
CSRF攻撃も、これと似たようなシチュエーションを想像してみてください。
あなたは、あるWebサイト(例えば、オンラインバンキングやSNS)で、「パスワードを変更する」とか「友達にメッセージを送る」といった、「あなた自身が意図して行う操作」をしようとしています。
ところが、悪意のある第三者(=泥棒)が、あなたが普段見ている別のWebサイト(例えば、怪しげな広告や、改ざんされたブログ記事)に、「あなたが、そのWebサイトにログインしている状態のまま、勝手に操作を実行させるための仕掛け」を隠しておいたとします。
あなたがその怪しげなWebサイトを見てしまった瞬間、あなたが持っている「そのWebサイトへのログイン情報(=家の鍵)」が、泥棒に悪用されてしまうのです。
具体的には、こんな流れで攻撃が進みます。
1. あなた、WebサイトA(例:オンラインバンキング)にログインする。
- これは、あなたが家に鍵をかけて入るようなものです。WebサイトAは、あなたが「本人である」ことを証明するために、特別な「チケット(セッションID)」をくれます。
2. あなたは、WebサイトB(例:怪しいブログ)を何気なく閲覧する。
- これは、あなたが家の外で、見知らぬ人に話しかけられるようなものです。
3. WebサイトBには、実は「WebサイトAへの勝手な操作」を指示する、巧妙な仕掛けが隠されている。
- 泥棒が、あなたの家の鍵穴を覗き見たり、開けっ放しの窓から侵入を試みるようなイメージです。
4. あなたのブラウザが、WebサイトBの指示に従い、WebサイトAに「勝手に操作を実行して!」というリクエストを送ってしまう。
- あなたのブラウザは、WebサイトAから受け取った「チケット(セッションID)」を持っているので、WebサイトAは「ああ、本人からのリクエストだな」と勘違いして、その指示を実行してしまうのです。
5. 意図しない操作が実行される。(例:不正送金、アカウント乗っ取りなど)
- 泥棒の思うがままに、あなたの情報が盗まれたり、勝手に操作されたりします。
このように、CSRF攻撃は、ユーザーが「本人である」と認識されている状況を利用して、意図しない操作を強制的に実行させる、非常に厄介な攻撃なんです。
どうやって、この「勝手に操作」を防ぐの? ~ Anti-CSRFトークンという「秘密の合言葉」
では、このCSRF攻撃から、私たちのWebサイトを守るにはどうすれば良いのでしょうか?ここで登場するのが、「Anti-CSRFトークン」という仕組みです。
これは、先ほどの家の鍵の例えで言うと、「合鍵」のようなものです。
通常の鍵(セッションID)だけでは、泥棒に悪用されてしまう可能性があります。そこで、さらに「この合鍵を持っている人だけが、特定の部屋(=重要な操作)に入れる」という仕組みを追加するのです。
具体的には、Webサイト側が、ユーザー一人ひとりに、「あなただけが知っている、使い捨ての秘密の合言葉(=CSRFトークン)」を渡します。
そして、ユーザーが「パスワード変更」や「商品購入」といった重要な操作を行う際には、この「秘密の合言葉」も一緒に送ってもらうようにします。
Webサイト側は、送られてきた「秘密の合言葉」が、事前に渡したものと一致するかどうかを確認します。
- 一致した場合: 「おお、ちゃんと本人からのリクエストだな!」と判断し、操作を実行します。
- 一致しなかった場合: 「あれ?この合言葉、知らないぞ?誰か悪さをしようとしているな!」と判断し、操作を拒否します。
なぜこれで防げるかというと、泥棒は、あなたがWebサイトAにログインしていることを知っていても、あなたが持っている「秘密の合言葉」までは知らないからです。秘密の合言葉は、あなたのブラウザがWebサイトAから受け取って、それをフォームに埋め込むなどして、一緒に送信されるものだからです。悪意のあるWebサイトBは、その「秘密の合言葉」を盗むことができないのです。
Anti-CSRFトークンの生成・検証フロー
実際の開発現場では、このAnti-CSRFトークンをどのように実装するのでしょうか?一般的な流れを見ていきましょう。
1. ユーザーがWebページを表示する時:
- Webサーバーは、ユーザーごとにユニークで、かつ有効期限のある「CSRFトークン」を生成します。
- 生成されたCSRFトークンは、HTMLフォームのhidden(隠し)フィールドとして、ページに埋め込まれます。
- 同時に、このトークンはサーバー側でセッション情報などと紐付けて保存しておきます。
2. ユーザーがフォームを送信する時:
- ユーザーが「パスワード変更」などのボタンをクリックすると、フォームデータと一緒に、hiddenフィールドに埋め込まれたCSRFトークンもサーバーに送信されます。
- JavaScriptを使って非同期(AJAX)で送信する場合も、トークンをリクエストヘッダーに含めて送信するのが一般的です。
3. サーバー側での検証:
- サーバーは、送信されてきたCSRFトークンを受け取ります。
- サーバーは、事前に保存しておいたトークンと、送信されてきたトークンを比較します。
- 一致すれば: リクエストは正当なものとみなし、処理を続行します。
- 一致しなければ: CSRF攻撃の可能性が高いと判断し、リクエストを拒否します。
コード例:PHPでの実装イメージ
ここでは、PHPを使った簡単なイメージをご紹介します。多くのWebフレームワーク(Laravel, Ruby on Rails, Djangoなど)では、このようなCSRF対策が標準で組み込まれているので、実際の開発では、フレームワークのドキュメントに従うのが一般的ですが、基本的な考え方を理解するために見てみましょう。
1. トークンの生成とフォームへの埋め込み (PHP)
パスワード変更フォーム
解説:
- `session_start()`: ユーザーごとにユニークな情報を保持するためのセッションを開始します。
- `$_SESSION[‘csrf_token’] = bin2hex(random_bytes(32));`: ランダムで強力なCSRFトークンを生成し、セッションに保存しています。`bin2hex(random_bytes(32))`は、セキュリティ上推奨される方法で、予測困難なトークンを生成します。
- ``: 生成したトークンを、フォームの目に見えない(hidden)フィールドとしてHTMLに埋め込んでいます。ユーザーはこのフィールドを意識しませんが、ブラウザがフォーム送信時に一緒にサーバーへ送ってくれます。
- `htmlspecialchars()`: クロスサイトスクリプティング(XSS)攻撃を防ぐため、HTMLエンティティに変換しています。
2. トークンの検証 (PHP)
解説:
- `$_SERVER[‘REQUEST_METHOD’] === ‘POST’`: この処理がPOSTメソッドで実行された場合のみ、トークン検証を行うようにしています。
- `$_POST[‘csrf_token’] ?? null`: フォームから送信されたCSRFトークンを受け取ります。PHP 7以降で使える`??`演算子(Null合体演算子)を使うと、キーが存在しない場合に`null`を返すので、エラーを防げます。
- `$submitted_token === $session_token`: 送信されてきたトークンと、サーバー側で保持しているセッションのトークンが完全に一致するかどうかを厳密に比較します。
- `http_response_code(403);`: トークンが一致しなかった場合、クライアントに「Forbidden」(禁止)というエラーコードを返します。これは、不正なリクエストであることを示す標準的な方法です。
- `$_SESSION[‘csrf_token’] = bin2hex(random_bytes(32));`: パスワード変更などの重要な処理が成功した場合、トークンを再生成することが推奨されます。これにより、一度使われたトークンが再度使われるのを防ぎ、セキュリティをさらに高めます。
SameSite属性:ブラウザが「お邪魔します」をブロック!
さて、Anti-CSRFトークンは強力な武器ですが、さらにセキュリティを強化するために、「SameSite属性」という、ブラウザの機能も活用できます。
これは、例えるなら「見知らぬ人からの突然の訪問を、家のドアベルが自動でブロックしてくれる」ようなイメージです。
SameSite属性は、Cookieに設定できる属性の一つで、ブラウザに対して「このCookieを、どのサイトからのリクエストと一緒に送信するか」を指示します。
SameSite属性には、主に以下の3つの値があります。
- `Strict`:
- 意味: 同じサイト(ドメイン)からのリクエストでなければ、Cookieを送信しません。
- 例え: あなたの家(Webサイト)のドアベルは、家族(同じドメインからのアクセス)からの呼び鈴にしか反応しません。知らない人(別のドメインからのアクセス)が来ても、完全に無視します。
- 効果: 最も強力なCSRF対策になりますが、リンクをクリックして別のサイトからあなたのサイトに遷移してきた場合など、一部で使い勝手が悪くなることがあります。
- `Lax`:
- 意味: 基本的には`Strict`と同様ですが、ナビゲーション(リンクのクリック、URLの直接入力など)によってトップレベルナビゲーション(ブラウザのアドレスバーが変わるような遷移)が発生する場合に限り、Cookieを送信します。GETメソッドのリクエストであれば、多くのケースで送信されます。
- 例え: 家のドアベルは、家族からの呼び鈴に反応しますが、玄関のチャイムが鳴って「宅配便です」というような、トップレベルの「来訪」であれば、一時的にドアを開けて確認します。しかし、勝手にドアが開けられるようなことはありません。
- 効果: `Strict`よりも柔軟性があり、多くのWebサイトでCSRF対策として推奨されています。現在、多くのブラウザでデフォルトの挙動になっています。
- `None`:
- 意味: どのサイトからのリクエストでもCookieを送信します。
- 例え: 家のドアベルは、誰からの呼び鈴でも反応します。
- 効果: CSRF対策としては機能しません。この属性を使う場合は、必ず`Secure`属性(HTTPS接続時のみCookieを送信する)も併せて設定する必要があります。
SameSite属性の設定方法
Webサーバーの設定や、アプリケーションのフレームワークで設定することができます。
例:Webサーバー(Nginx)での設定
ユーザーセッションのCookieにSameSite属性を設定する例
add_header Set-Cookie “session_id=$session_id; SameSite=Lax; Secure; HttpOnly”;
解説:
- `Set-Cookie “session_id=$session_id; …”`: Cookieを設定するヘッダーです。
- `SameSite=Lax`: Cookieを送信する条件を`Lax`に設定しています。
- `Secure`: このCookieはHTTPS接続時のみ送信されるようになります。
- `HttpOnly`: JavaScriptからこのCookieにアクセスできなくなります。これも、Cookieの不正利用を防ぐための重要な属性です。
まとめ:多層防御で、あなたとユーザーを守りましょう!
今日の話で、CSRF攻撃の怖さと、それを防ぐためのAnti-CSRFトークン、そしてSameSite属性の重要性が少しでも伝わったでしょうか?
- CSRF攻撃: ユーザーがログインしている状態を利用して、意図しない操作を強制する攻撃。
- Anti-CSRFトークン: ユーザーごとに発行される「秘密の合言葉」。これが一致しないと、重要な操作は実行されません。
- SameSite属性: ブラウザが、Cookieを送信する条件を制御し、CSRF攻撃を防ぐための仕組み。
これらの対策を適切に実装することで、あなたのWebサイトは、より安全になります。
セキュリティ対策は、一度やったら終わりではありません。常に最新の情報をキャッチアップし、開発プロセスに組み込んでいくことが大切です。
今日の解説が、皆さんの「セキュリティへの第一歩」となれば嬉しいです。これからも、一緒に一歩ずつ、安全なWebアプリケーション開発を目指していきましょう!

コメント