PHP - CSRF - Как сделать так, чтобы он работал во всех вкладках? - PullRequest
16 голосов
/ 23 апреля 2010

Я читал о том, как предотвратить CSRF-атаки в последние дни. Я собираюсь обновлять токен при каждой загрузке страницы, сохранять токен в сеансе и проверять при отправке формы.

Но что, если у пользователя есть, скажем, 3 вкладки, открытые на моем сайте, и я просто сохраняю последний токен в сеансе? Это заменит токен другим токеном, и некоторое последующее действие завершится неудачей.

Нужно ли хранить все токены в сеансе или есть лучшее решение, чтобы это работало?

Ответы [ 2 ]

30 голосов
/ 23 апреля 2010

Да, при использовании подхода с сохраненными токенами вам нужно будет сохранить все сгенерированные токены на случай, если они вернутся в любой момент. Один сохраненный токен не работает не только для нескольких вкладок / окон браузера, но и для навигации назад / вперед. Как правило, вы хотите управлять потенциальным взрывом хранилища путем истечения срока действия старых токенов (по возрасту и / или количеству выданных токенов с тех пор).

Другим подходом, который полностью избегает хранения токенов, является выдача подписанного токена, сгенерированного с использованием секрета на стороне сервера. Затем, когда вы получите токен обратно, вы можете проверить подпись и, если она совпадает, вы знаете, что подписали ее. Например:

// 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;
}

При этом, если вы получаете токен с соответствующей подписью, вы знаете, что сгенерировали его. Само по себе это не сильно помогает, но тогда вы можете поместить в токен дополнительные вещи, кроме случайности, например, идентификатор пользователя:

$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.

Еще одна вещь, которую стоит добавить в токен, это время истечения, так что кратковременный компромисс клиента или атака MitM не пропускают токен, который будет работать для этого пользователя вечно, и изменяют значение пароль сбрасывается, поэтому изменение пароля делает недействительными существующие токены.

1 голос
/ 30 января 2011

Вы можете просто использовать токен, который является постоянным для текущего сеанса или даже для пользователя (например, хэш хеш-кода пароля пользователя) и не может быть определен третьей стороной (использование хеш-функции IP-адреса пользователя плохо например).

Тогда вам не нужно хранить, возможно, тонны сгенерированных токенов, и, если время сеанса не истечет (что, вероятно, в любом случае потребует от пользователя повторного входа в систему), пользователь может использовать столько вкладок, сколько ему нужно.

...