ここ数日で CSRF 攻撃を防ぐ方法について読みました。ページロードごとにトークンを更新し、トークンをセッションに保存し、フォームを送信するときにチェックを行います。
しかし、ユーザーが自分の Web サイトで 3 つのタブを開いて、セッションの最後のトークンを保存したとしたらどうなるでしょうか? これにより、トークンが別のトークンで上書きされ、一部の事後アクションが失敗します。
セッションにすべてのトークンを保存する必要がありますか、それともこれを機能させるためのより良い解決策はありますか?
はい、格納されたトークンのアプローチでは、生成されたすべてのトークンを、いつでも戻ってきた場合に備えて保持する必要があります。複数のブラウザー タブ/ウィンドウだけでなく、前後のナビゲーションでも、1 つの格納されたトークンが失敗します。一般に、古いトークンを期限切れにすることで、潜在的なストレージの爆発を管理する必要があります (それ以降に発行されたトークンの年齢および/または数によって)。
トークンの保存を完全に回避する別のアプローチは、サーバー側のシークレットを使用して生成された署名付きトークンを発行することです。その後、トークンが戻ってきたら、署名を確認できます。一致する場合は、署名したことがわかります。例えば:
// Only the server knows this string. Make it up randomly and keep it in deployment-specific
// settings, in an include file safely outside the webroot
//
$secret= 'qw9pDr$wEyq%^ynrUi2cNi3';
...
// Issue a signed token
//
$token= dechex(mt_rand());
$hash= hash_hmac('sha1', $token, $secret);
$signed= $token.'-'.$hash;
<input type="hidden" name="formkey" value="<?php echo htmlspecialchars($signed); ?>">
...
// Check a token was signed by us, on the way back in
//
$isok= FALSE;
$parts= explode('-', $_POST['formkey']);
if (count($parts)===2) {
list($token, $hash)= $parts;
if ($hash===hash_hmac('sha1', $token, $secret))
$isok= TRUE;
}
これにより、署名が一致するトークンを取得した場合、それを生成したことがわかります。それ自体はあまり役に立ちませんが、ユーザーIDなど、ランダム性以外の追加のものをトークンに入れることができます。
$token= dechex($user->id).'.'.dechex(mt_rand())
...
if ($hash===hash_hmac('sha1', $token, $secret)) {
$userid= hexdec(explode('.', $token)[0]);
if ($userid===$user->id)
$isok= TRUE
現在、各フォームの送信は、フォームを取得した同じユーザーによって承認される必要があり、CSRF をほぼ無効にします。
トークンに入れることをお勧めするもう 1 つのことは、有効期限です。これにより、一時的なクライアントの侵害や MitM 攻撃によって、そのユーザーに対して永久に機能するトークンと、パスワードのリセット時に変更される値がリークされなくなります。パスワードを変更すると既存のトークンが無効になるようにします。
現在のセッションまたはユーザーに対しても永続的であり (ユーザーのパスワードのハッシュのハッシュなど)、第三者によって決定できないトークンを単純に使用できます (たとえば、ユーザーの IP のハッシュを使用するのは良くありません)。 .
そうすれば、おそらく大量の生成されたトークンを保存する必要がなくなり、セッションの有効期限が切れない限り (おそらくユーザーは再度ログインする必要があります)、ユーザーは好きなだけタブを使用できます。