Как правильно добавить токен 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> 

Какие-либо предложения?

Предупреждение безопасности . 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']; 

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

Параметр $token не извлекается из сеанса, когда он там