CSRF対策として「トークンを導入しているから安全」と考えていませんか。
しかし、トークンの実装方法によっては、対策しているつもりでも簡単に突破されるケースがあります。
この記事では、Web Security Academyのラボ「CSRF where token is not tied to user session」をもとに、
- CSRFトークンがセッションと紐づいていない場合に何が起きるのか
- WordPressではどのような実装ミスが起こり得るのか
を、実務に近い形で解説します。
CSRFの基本的な仕組みについては、以下の記事で整理しています。
このラボの脆弱性の概要
今回の問題はシンプルです。
- CSRFトークンは存在する
- しかし、そのトークンがユーザーセッションと紐づいていない
つまり、有効なトークンであれば、誰のものでも通る状態になっています。
CSRFは実装のわずかな違いで成立条件が変わります。他のパターンについては以下の記事でまとめています。
▶ WordPressで考えるCSRF|メール変更機能を悪用する攻撃
▶ WordPressで考えるCSRF対策の落とし穴|トークン未送信で突破されるケース
攻撃の流れ|他ユーザーのトークンが使えてしまう仕組み
攻撃は次のように進みます。
- 攻撃者が自分のアカウントでログイン
- 正常なCSRFトークンを取得
- そのトークンを使って攻撃用リクエストを作成
- 被害者にそのリクエストを送らせる
本来であれば、被害者のリクエストには被害者専用のトークンが必要ですが、このケースでは攻撃者のトークンでもそのまま通るため、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対策はトークンを入れることだけでは成立しません。
重要なのは、
- そのトークンが誰のものか
- そのリクエストと一致しているか
を正しく検証することです。
トークンが存在していても、セッションと紐づいていなければ、対策としては不十分です。
