【実務・中級編】CSRF攻撃の基本メカニズムとブラウザの自動送信挙動 – アプリケーションセキュリティ & 安全な開発防御ガイド

CSRFの悪夢:なぜ「ログインしているだけ」で攻撃は成立してしまうのか

現場でインシデント対応をしていると、いまだに「CSRF(クロスサイトリクエストフォージェリ)? うちは認証かけてるから大丈夫だよ」という甘い言葉を耳にすることがある。だが、断言しよう。認証済みであることこそが、CSRFの最大のトリガーだ。

CSRFは、ユーザーが認証済みであるという「信頼」を逆手に取り、攻撃者が用意した罠サイトから、あたかもユーザー本人が操作したかのようにリクエストを送りつける攻撃手法だ。今日は、なぜブラウザがそんな「裏切り」を働いてしまうのか、そして現場レベルでどう防ぐべきかを、泥臭い実務の視点から紐解いていく。

—

1. なぜブラウザは勝手にCookieを送信するのか?

CSRFのメカニズムを理解するための鍵は、ブラウザの「Cookie自動送信仕様」にある。

ブラウザは、ドメイン(ホスト名)単位でCookieを管理している。例えば、あなたが bank.com にログインし、セッションIDを含むCookieがブラウザに保存されたとする。その状態で、攻撃者が仕掛けた evil.com という別のサイトにアクセスしたとき、evil.com が bank.com/transfer?amount=1000000&to=attacker というリクエストを投げたとしよう。

このとき、ブラウザは「ああ、bank.com へのリクエストだな。じゃあ保存してある bank.com 用のCookieを付けて送らなきゃ」と、自動的に認証情報を付与してしまう。 サーバー側は、届いたCookieを見て「お、本人からのリクエストだな」と判断し、送金処理を実行してしまう。これがCSRFの基本ロジックだ。

—

2. 実践的防御策:アンチCSRFトークンの実装

「画面を開くたびにランダムなトークンを生成し、送信時に検証する」。これがCSRF対策の王道だ。攻撃者は evil.com からは bank.com の画面内容(隠しフィールドにあるトークン)を読み取ることができない(同一生成元ポリシー:SOPの壁)。だから、トークンを知る術がない攻撃者はリクエストを捏造できないという仕組みだ。

Python (Flask) でのセキュアな実装例

実務では、自作のトークン管理よりもフレームワークのミドルウェアを使うのが鉄則だ。以下はFlaskでの実装例だが、ロジックはどの言語でも同じだ。

from flask import Flask, session, request, abort
import secrets

app = Flask(__name__)
app.secret_key = ‘super-secret-key’ # 本番では環境変数から読み込むこと

フォーム表示時にトークンを生成し、セッションに保存
@app.route(‘/transfer’, methods=[‘GET’])
def show_form():
csrf_token = secrets.token_hex(32)
session[‘csrf_token’] = csrf_token
return f’

‘

送信時にトークンを検証
@app.route(‘/transfer’, methods=[‘POST’])
def process_transfer():
token_in_session = session.get(‘csrf_token’)
token_in_request = request.form.get(‘csrf_token’)

# トークンが一致しない、または存在しない場合は即座に遮断
if not token_in_session or token_in_session != token_in_request:
abort(403) # 403 Forbiddenを返す

return “送金成功”

—

3. 「モダンな武器」:SameSite属性の活用

近年では、ブラウザの挙動自体を制御する SameSite 属性が標準になった。これを使わない手はない。

Nginxでの設定(全レスポンスに適用)

サーバー側でCookieをセットする際、必ず SameSite=Lax または Strict を指定する。

Nginxの設定例: Set-Cookieヘッダーを強制的に書き換える場合
ただし、アプリケーション側で正しくSet-Cookieするように修正するのがベスト
proxy_cookie_path / “/; HTTPOnly; Secure; SameSite=Lax”;

  • Lax (推奨): 通常のリンク遷移ではCookieを送るが、外部サイトからのPOSTリクエストではCookieを送らない。これで大部分のCSRFは防げる。
  • Strict: 同一サイトからのリクエストのみCookieを送る。セキュリティは最強だが、外部サイトからのリンクで「ログイン状態」が維持されないため、UXとの兼ね合いが必要だ。

—

4. 現場の教訓:完璧な防御のために

最後に、現場でよくある失敗を共有しておく。

1. 「GETリクエストで状態変更」は厳禁: CSRF対策のトークンは主にPOST/PUT/DELETEを対象にする。しかし、GETで送金や削除ができる設計自体が脆弱性の温床だ。GET = 参照のみ という基本原則を徹底せよ。
2. JSフレームワーク(React/Vue)の落とし穴: AJAXで通信する場合、カスタムヘッダー(例: X-CSRF-Token)を付与して検証するのが現代のデファクトだ。フロントエンド側で自動的にヘッダーを付与する仕組みを共通ライブラリとして作り込もう。
3. WAFは最後の砦: AWS WAFやCloudflareなどのWAFは、CSRFを完全に防ぐものではない。あくまで「攻撃の試行回数を減らす」ためのフィルタリングである。アプリケーションのコード自体に脆弱性がある状態で、WAFだけで守り切ろうとするのは危険だ。

セキュリティは「どこか一つを塞げば終わり」ではない。ブラウザの仕様を理解し、サーバー側で厳密に検証し、インフラで多重に防御する。この積み重ねが、あなたのアプリケーションを、そしてユーザーの資産を守る唯一の道だ。

さあ、今すぐコードベースを確認してくれ。GET リクエストで重要な処理をしていないか、SameSite 属性が漏れていないか。その数分が、明日の「深刻なセキュリティインシデント」を未然に防ぐことになる。

コメント

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