Как правильно добавить токен CSRF с помощью PHP
Я пытаюсь добавить некоторую безопасность в формы на моем веб-сайте. Одна из форм использует AJAX, а другая представляет собой простую форму "свяжитесь с нами". Я пытаюсь добавить токен CSRF. Проблема, с которой я сталкиваюсь, заключается в том, что токен иногда отображается только в HTML-"значении". В остальное время значение пусто. Вот код, который я использую в форме AJAX:
PHP:
if (!isset($_SESSION)) {
session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token']))
{$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
}
HTML
<form>
//...
<input type="hidden" name="token" value="<?php echo $token; ?>" />
//...
</form>
Есть какие-нибудь предложения?
3 answers
Предупреждение о безопасности:
md5(uniqid(rand(), TRUE))
это небезопасный способ генерации случайных чисел. Смотрите этот ответ для получения дополнительной информации и решения, которое использует криптографически безопасный генератор случайных чисел.
Похоже, вам нужно что-то еще с вашим if.
if (!isset($_SESSION['token'])) {
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();
}
else
{
$token = $_SESSION['token'];
}
Для кода безопасности, пожалуйста, не создавайте свои токены таким образом: $token = md5(uniqid(rand(), TRUE));
rand()
предсказуемоuniqid()
только добавляет до 29 бит энтропии-
md5()
не добавляет энтропии, он просто детерминированно смешивает ее
Попробуйте это:
Генерация токена CSRF
PHP 7
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
Примечание: Один из проектов с открытым исходным кодом моего работодателя - это инициатива по переносу random_bytes()
и random_int()
в проекты PHP 5. Он лицензирован MIT и доступен на Github и Composer как образец/random_compat.
PHP 5.3+ (или с помощью ext-mcrypt)
session_start();
if (empty($_SESSION['token'])) {
if (function_exists('mcrypt_create_iv')) {
$_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
} else {
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
}
}
$token = $_SESSION['token'];
Проверка токена CSRF
Не просто используйте ==
или даже ===
, используйте hash_equals()
( Только PHP 5.6+, но доступен для более ранних версий с библиотекой hash-compat).
if (!empty($_POST['token'])) {
if (hash_equals($_SESSION['token'], $_POST['token'])) {
// Proceed to process the form data
} else {
// Log this as a warning and keep an eye on these attempts
}
}
Идем дальше с помощью токенов для каждой формы
Вы можете дополнительно ограничить доступность токенов для определенной формы с помощью hash_hmac()
. HMAC - это особая хэш-функция с ключом, которая безопасна в использовании даже с более слабыми хэш-функциями (например, MD5). Однако я рекомендую вместо этого использовать семейство хэш-функций SHA-2.
Сначала сгенерируйте второй токен для использования в качестве ключа HMAC, затем используйте логику, подобную этой, для его визуализации:
<input type="hidden" name="token" value="<?php
echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />
, А затем с помощью соответствующей операции при проверке токена:
$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
// Continue...
}
Токены, сгенерированные для одной формы, не могут быть повторно использованы в другой контекст, не зная $_SESSION['second_token']
. Важно, чтобы вы использовали отдельный токен в качестве ключа HMAC, а не тот, который вы просто опускаете на страницу.
Бонус: Гибридный подход + Интеграция веток
Любой, кто использует Механизм шаблонов веток, может воспользоваться упрощенной двойной стратегией, добавив этот фильтр в свою среду веток:
$twigEnv->addFunction(
new \Twig_SimpleFunction(
'form_token',
function($lock_to = null) {
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
if (empty($_SESSION['token2'])) {
$_SESSION['token2'] = random_bytes(32);
}
if (empty($lock_to)) {
return $_SESSION['token'];
}
return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
}
)
);
С помощью этой функции Twig вы можете использовать оба токена общего назначения, например:
<input type="hidden" name="token" value="{{ form_token() }}" />
Или заблокированный вариант:
<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />
Twig занимается только отрисовкой шаблонов; вы все равно должны правильно проверить маркеры. На мой взгляд, стратегия Twig обеспечивает большую гибкость и простоту, сохраняя при этом возможность обеспечения максимальной безопасности.
Одноразовые токены CSRF
Если у вас есть требование безопасности, чтобы каждый токен CSRF можно было использовать ровно один раз, самая простая стратегия восстанавливает его после каждой успешной проверки. Однако, это приведет к аннулированию всех предыдущих токенов, которые плохо сочетаются с людьми, просматривающими несколько вкладок одновременно.
Paragon Initiative Enterprises поддерживает библиотеку анти-CSRF для этих угловых случаев. Он работает исключительно с одноразовыми токенами для каждой формы. Когда в данных сеанса будет сохранено достаточное количество токенов (конфигурация по умолчанию: 65535), сначала будут выведены самые старые неиспользованные токены.
Переменная $token
не извлекается из сеанса, когда она находится там