WordPressで考えるCSRFトークンの欠陥│セッションと紐づいてないトークンはなぜ危険なのか

CSRF対策として「トークンを導入しているから安全」と考えていませんか。

しかし、トークンの実装方法によっては、対策しているつもりでも簡単に突破されるケースがあります。

この記事では、Web Security Academyラボ「CSRF where token is not tied to user session」をもとに、

  • CSRFトークンがセッションと紐づいていない場合に何が起きるのか
  • WordPressではどのような実装ミスが起こり得るのか

を、実務に近い形で解説します。

CSRFの基本的な仕組みについては、以下の記事で整理しています。

CSRFの仕組みまとめ|成立条件と対策の基本

このラボの脆弱性の概要

今回の問題はシンプルです。

  • CSRFトークンは存在する
  • しかし、そのトークンがユーザーセッションと紐づいていない

つまり、有効なトークンであれば、誰のものでも通る状態になっています。

CSRFは実装のわずかな違いで成立条件が変わります。他のパターンについては以下の記事でまとめています。

WordPressで考えるCSRF|メール変更機能を悪用する攻撃
WordPressで考えるCSRF対策の落とし穴|トークン未送信で突破されるケース


攻撃の流れ|他ユーザーのトークンが使えてしまう仕組み

攻撃は次のように進みます。

  1. 攻撃者が自分のアカウントでログイン
  2. 正常なCSRFトークンを取得
  3. そのトークンを使って攻撃用リクエストを作成
  4. 被害者にそのリクエストを送らせる

本来であれば、被害者のリクエストには被害者専用のトークンが必要ですが、このケースでは攻撃者のトークンでもそのまま通るため、CSRFが成立します。


なぜCSRF対策が無効化されるのか

本来、CSRFトークンは次のように扱う必要があります。

  • トークンはユーザーごとに異なる
  • セッションと一致するかを検証する

しかし、このラボでは

  • トークンが「存在するか」しか見ていない
  • 誰のトークンかを確認していない

という状態でした。

その結果、

  • セッション:被害者
  • トークン:攻撃者

という不整合がそのまま通ってしまいます。


WordPressでよくあるCSRF対策の実装ミス

WordPressではCSRF対策として「nonce」が広く使われています。

しかし実際の案件では、nonceを使っているのに脆弱な状態になっているケースも少なくありません。

ここでは、ありがちな実装ミスを具体例で見ていきます。

例:プロフィール変更フォーム

ユーザーがメールアドレスを変更するフォームを想定します。

<form method="POST">
<input type="email" name="email">
<?php wp_nonce_field('update_email_action'); ?>
<button type="submit">更新</button>
</form>

見た目としては問題なさそうです。

サーバー側の処理

if ($_POST) {
wp_verify_nonce($_POST['_wpnonce'], 'update_email_action');
update_user_meta(get_current_user_id(), 'email', $_POST['email']);
}

一見するとnonceを検証しているように見えます。

実際の問題点

このコードには重大な問題があります。

wp_verify_nonce(...)

の戻り値をチェックしていません。

そのため、

  • 正しいトークンでも処理される
  • 不正なトークンでも処理される

という状態になります。

つまり、CSRF対策が実質機能していません。

改めて整理すると

この状態は、今回のラボと同じ構造です。

  • トークンは存在する
  • しかし検証が意味を持っていない

その結果、

  • 攻撃者が用意したリクエスト
  • 被害者のセッション

を組み合わせることで攻撃が成立します。


もう一つの実例:AJAX処理で発生しやすいCSRF脆弱性

WordPressではAJAXでもnonceが使われます。

add_action('wp_ajax_update_email', 'update_email');
add_action('wp_ajax_nopriv_update_email', 'update_email');
function update_email() {
wp_verify_nonce($_POST['_wpnonce'], 'update_email_action');
update_user_meta(get_current_user_id(), 'email', $_POST['email']);
}

この実装の問題点

この場合、

  • nopriv によって未ログインでも実行可能
  • ユーザー確認が行われていない

という問題があります。

結果として、

  • トークンさえあれば処理が通る
  • ユーザーとの紐づきが弱い

状態になります。

なぜ危険か

本来は、ログインユーザーとそのユーザーのトークンが一致している必要があります。

しかしこの実装では、

  • 誰のトークンか
  • 誰のリクエストか

が正しく結びついていません。

実務で起きやすい理由

このような問題は、次のような背景で発生します。

  • nonceを入れることが目的になっている
  • 戻り値を確認していない
  • コピペで実装している
  • AJAXと通常処理で検証方法が統一されていない

WordPressでも、nonceを使っているだけでは不十分で、正しく検証しているかが重要です。

実際のWordPressでは、CSRF以外にもログイン機能やAPIを狙った攻撃が多く確認されています。

WordPressの「/wp-login.php」スキャンとは│アクセスログ分析
WordPress公開直後に来たxmlrpc.php POSTリクエスト|アクセスログ分析
WordPressでユーザー名がバレる理由と対策|ユーザー列挙攻撃をログから解説


攻撃者視点で見ると

この脆弱性は攻撃者にとって非常に単純です。

通常のCSRFでは、

  • 被害者のトークンを取得する必要がある
  • あるいはトークンを回避する必要がある

しかしこのケースでは、自分でトークンを取得すればよいという状態になります。

つまり、

  • 正常にログインできる
  • フォームを一度送信できる

これだけで攻撃の準備が整います。


CSRFトークンが使い捨ての場合の注意点

このラボではCSRFトークンが使い捨てです。

  • 一度使ったトークンは無効
  • 取得してすぐ使う必要がある

そのため、

  • トークン取得時のリクエストは送信しない
  • 値だけ取得して別リクエストで使う

という手順が必要になります。


セキュリティ診断で確認すべきポイント

CSRF対策の確認では、以下をチェックします。

トークンがセッションと紐づいているか

  • 他ユーザーのトークンで通らないか
  • アカウントを切り替えて検証する

トークン検証が正しく行われているか

  • トークンの有無ではなく検証結果を見ているか
  • 条件分岐で検証がスキップされていないか

トークンの使い回しができないか

  • 同じトークンで複数回リクエストできないか
  • 時間経過で無効になるか

エンドポイントごとの実装差異

  • 通常処理とAJAXで挙動が異ならないか
  • 別ルートで検証が抜けていないか

このような実装の抜け漏れは、CSRFに限らず他の脆弱性でも発生します。

WordPressで起こるビジネスロジック脆弱性まとめ|認証バイパス・価格改ざん・クーポン不正の実例


まとめ|CSRFトークンはセッションと紐づいていることが重要

CSRF対策はトークンを入れることだけでは成立しません。

重要なのは、

  • そのトークンが誰のものか
  • そのリクエストと一致しているか

を正しく検証することです。

トークンが存在していても、セッションと紐づいていなければ、対策としては不十分です。