Подтвердить что ты не робот

PHP - CSRF - Как заставить его работать во всех вкладках?

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

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

Нужно ли мне хранить все токены в сеансе или есть лучшее решение для этого?

4b9b3361

Ответ 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;
}

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

$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 не просачивают токен, который будет работать для этого пользователя навсегда, и значение, которое изменяется на сбрасывает пароль, так что смена пароля делает недействительными существующие токены.

Ответ 2

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

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