CSRF対策として「トークンを実装しているから安全」と考えていませんか。
確かに、CSRFトークンは基本的な対策として有効です。
しかし、トークンの実装方法や周辺の設計によっては、対策していても簡単に突破されるケースがあります。
この記事では、Web Security Academyのラボ「CSRF where token is tied to non-session cookie」をもとに、
- CSRFトークンがセッションと紐づいていない場合に何が起きるのか
- Cookie操作によってトークン検証が突破される仕組み
- WordPressで起こり得る実装ミス
を解説します。
CSRF where token is tied to non-session cookieラボの概要
このラボでは、メールアドレス変更機能にCSRF脆弱性があります。
一見するとCSRFトークンによる対策が実装されていますが、そのトークンはログインセッションとは別のCookieに紐づいています。
さらに、検索機能にHTTPレスポンス分割の脆弱性があり、攻撃者は被害者のブラウザに任意のCookieをセットすることが可能です。
この2つを組み合わせることで、CSRF攻撃が成立します。
トークンがユーザーと紐づいていない
通常、CSRFトークンは、そのユーザーのセッションと紐づいている必要があります。
しかしこのラボでは、
- CSRFトークン → 別Cookieに紐づいている
- セッション → 別管理
となっており、トークンが誰のものかを正しく判定できない状態になっています。
Cookieを書き換えられると崩れる
さらに問題なのは、別の脆弱性によって被害者のブラウザに任意のCookieをセットできる点です。
具体的には、検索機能において入力値がそのままレスポンスヘッダに反映されており、改行を使うことで Set-Cookie ヘッダを注入できます。
これにより、攻撃者は被害者のブラウザに対して自分のCSRFトークン用Cookieを強制的にセットできます。
CSRF攻撃の流れ
攻撃の流れは以下の通りです。
- 攻撃者が自分のアカウントで有効なCSRFトークンとCookieを取得
- レスポンス分割を利用して、被害者のブラウザにそのCookieをセット
- 被害者にCSRFリクエストを送信させる
- サーバーはトークンとCookieの整合性のみを確認するため、攻撃が成立する
重要なのは、ログイン状態(セッション)は被害者のままである点です。
なぜ対策しているのに破られるのか
このケースでは、CSRFトークン自体は存在しています。
しかし、
- トークンがセッションと紐づいていない
- Cookieを書き換えられる
という2つの問題が重なることで、トークンの意味がなくなっています。
WordPressで考えると
このラボの問題は、CSRFトークンがセッションと紐づいていないことにあります。
これをWordPressの実装に当てはめると、実際の現場でも起こり得るミスとして理解できます。
Case 1:CSRFトークンをCookieで管理してしまう
例えば、独自実装で以下のようにトークンを扱っているケースです。
setcookie('csrfKey', $generated_token);if ($_POST['csrf'] === $_COOKIE['csrfKey']) {
// 処理を許可
}一見するとトークンチェックをしているため安全に見えますが、この実装には問題があります。
Cookieはサーバー側が発行しますが、別の脆弱性がある場合、攻撃者によって任意の値をセットされる可能性があります。
例えば今回のラボのように、
- レスポンスヘッダに任意のSet-Cookieを混入できる
- あるいはXSSなどでCookie操作が可能になる
といった状況では、
攻撃者が自分のCSRFトークンを被害者のブラウザに設定することができます。
Case 2:トークンがユーザーと紐づいていない
本来、CSRFトークンはユーザーごとに異なる値である必要があります。
しかし、以下のような実装になっていると問題が発生します。
$token = get_option('csrf_token'); // サイト全体で共通if ($_POST['csrf'] === $token) {
// 処理を許可
}この場合、
- 誰でも同じトークンを使ってリクエストできる
- 攻撃者が取得したトークンをそのまま使い回せる
という状態になります。
結果として、被害者のセッションであっても、攻撃者のトークンでリクエストが通ってしまいます。
Case 3:別機能からCookieを書き換えられる
WordPress本体では起こりにくいものの、プラグインや独自実装でよくあるのがこのパターンです。
例として、検索機能で入力値をそのままレスポンスヘッダに反映してしまうケースがあります。
header("Set-Cookie: lastSearch=" . $_GET['q']);このとき、改行コードを含む入力がそのまま処理されると、
test%0d%0aSet-Cookie: csrfKey=attacker_tokenのような値を使って、任意のCookieを追加できます。
これにより、
- 被害者のブラウザに攻撃者のcsrfKeyが設定される
- その状態でCSRFリクエストを送る
という流れが成立します。
攻撃の流れ
- 攻撃者が自分のCSRFトークンを取得
- 別の脆弱性(レスポンス分割など)で被害者のCookieを書き換える
- 攻撃用のリクエストを被害者に送らせる
- トークンが一致するため処理が通る
このように、CSRF対策が実装されていても、周辺の処理に問題があると簡単に突破されます。
WordPressのnonceが安全な理由
WordPressのnonceは、以下の情報を元に生成されています。
- ユーザーID
- セッション情報
- 時間
そのため、
- 他ユーザーのnonceは使えない
- Cookieを書き換えても成立しない
という設計になっています。
ポイント
CSRF対策は、トークンがあるかどうかではなく、
- そのトークンが誰のものか
- どのセッションと結びついているか
まで含めて成立するものです。
攻撃者視点でどう見えるか
このラボの構成は、防御側の視点だけでは見えにくく、攻撃者の視点で追うと理解しやすくなります。
攻撃者はまず、機能を順番に観察しながら「どこが崩せるか」を探します。
SETP 1:CSRFトークンの性質を確認する
最初に行うのは、CSRFトークンの挙動の確認です。
- トークンはリクエストごとに変わるか
- 別アカウントでも使えるか
- 検証はどこで行われているか
このラボでは、別アカウントのトークンでもリクエストが通ることが確認できます。
ここで攻撃者は、トークンが防御として弱いと判断します。
SETP 2:トークンの保存場所に注目する
次に、トークンがどこに保存されているかを確認します。
このラボでは、
- POSTパラメータ(csrf)
- Cookie(csrfKey)
の2つで管理されていました。
ここで重要なのは、Cookieはクライアント側に存在するため、何らかの方法で書き換えられる可能性があるという点です。
SETP 3:Cookieを書き換える経路を探す
攻撃者は「csrfKeyを任意の値にできないか」を考え、アプリ内の他の機能を調べます。
その結果、検索機能において、
- 入力値がそのままレスポンスヘッダに反映される
- Set-Cookieとして出力される
という挙動が見つかります。
この時点で、レスポンス分割によって任意のCookieを追加できると判断します。
SETP 4:攻撃者のトークンを被害者にセットする
攻撃者は自分のアカウントで取得したトークンを使い、以下のようなCookieを被害者に設定します。
Set-Cookie: csrfKey=attacker_tokenこれにより、被害者のブラウザには攻撃者のトークンが保存されます。
SETP 5:CSRFを成立させる
その状態で、被害者に対してCSRFリクエストを送らせます。
リクエストには攻撃者のトークンを含めます。
- CookieのcsrfKey → 攻撃者の値
- POSTのcsrf → 攻撃者の値
この2つが一致するため、サーバー側の検証を通過します。
攻撃者の最終的な判断
この構成を見た攻撃者は、次のように判断します。
- CSRFトークンは存在するが、防御として成立していない
- トークンがユーザーやセッションに紐づいていない
- 別機能を利用してCookieを書き換えられる
つまり、防御機構が部分的にしか機能しておらず、組み合わせによって簡単に突破できる状態です。
ポイント
攻撃者は単一の脆弱性だけを見るのではなく、
- トークンの設計
- データの保存場所
- 他機能との関係
を組み合わせて攻撃が成立するかを判断します。
そのため、個々の処理が正しく見えても、全体として整合性が取れていなければ防御は成立しません。
CSRF対策のポイント
今回のケースから分かる対策はシンプルです。
トークンは必ずセッションと紐づける
- ユーザーごとに一意であること
- セッションと関連付けて検証すること
ヘッダにユーザー入力をそのまま使わない
- 改行コード(CRLF)を除去する
- CookieやLocationヘッダに直接反映しない
WordPressではnonceを使う
独自実装ではなく、WordPress標準の仕組みを使うことで多くのミスを防ぐことができます。
セキュリティ診断でのチェックポイント
このラボのような問題は、トークンの有無ではなく、扱い方のミスで発生します。
※ CSRFの仕組み自体が曖昧な場合は、まずこちらの記事で基本から整理しておくと理解しやすくなります。
1. 他ユーザーのトークンで通らないか
- 別アカウントのトークンに差し替える
- セッションを変えて再送する
これで通る場合、トークンはユーザーに紐づいていません。
2. すべての経路で検証されているか
同じ処理でも入口が複数ある場合があります。
- フォーム送信
- Ajax
- API
一部だけ検証が抜けていないか確認します。
処理ごとにチェック内容がバラバラ(ビジネスロジックの不整合)になっていると、セキュリティが崩れる原因になります。
3. トークンの保存場所は適切か
- Cookieに依存していないか
- 全ユーザー共通の値になっていないか
クライアント側で完結している場合は危険です。
4. Cookieを書き換えられる経路がないか
- 入力値がそのままヘッダに入っていないか
- Set-Cookieを注入できないか
CSRFとは別機能でも、攻撃の入口になります。
ポイント
CSRF対策は、そのトークンが正しく検証されているかで判断します。
そのため、実際にリクエストを書き換えて試すことが重要です。
CSRFとは直接関係ない機能でも、結果的に攻撃の入口になることがあります。
実際に、設定ミスや実装不備から想定外の挙動が発生しているケースも少なくありません。
▶ 【Webサイトの実態シリーズ】WordPressのエラーがそのまま表示されている企業サイトの実態
まとめ
CSRFトークンは有効な対策ですが、それだけで安全が保証されるわけではありません。
今回のラボのように、トークンの設計不備やCookie操作が可能な脆弱性が組み合わさると、対策していても攻撃が成立します。
重要なのは、トークンが誰のものかを正しく検証できているか。
そして、周辺の処理が安全に実装されているかという点です。
関連するロジックの問題については、以下の記事でもまとめています。
