Для кода безопасности, пожалуйста, не генерируйте свои токены следующим образом: $token = md5(uniqid(rand(), TRUE));
Попробуйте это:
Генерация токена CSRF
PHP 7
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
Sidenote: Один из проектов моего работодателя с открытым исходным кодом - это инициатива по обратному переносу random_bytes()
и random_int()
в проекты PHP 5. Он имеет лицензию MIT и доступен на Github и Composer как paragonie / 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 использовался отдельный токен, а не тот, который вы просто перетаскивали на страницу.
Бонус: гибридный подход + интеграция веток
Любой, кто использует шаблонизатор Twig *, может воспользоваться упрощенной двойной стратегией, добавив этот фильтр в свою среду Twig:
$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 поддерживает библиотеку Anti-CSRF для этих угловых случаев. Он работает исключительно с токенами одноразового использования. Когда в данных сеанса хранится достаточное количество токенов (конфигурация по умолчанию: 65535), вначале циклически удаляются самые старые неиспользованные токены.