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


Предупреждение: 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));

попробуйте это:

создание токена CSRF

PHP 7

session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];

Sidenote: один из мой работодатель проекты с открытым исходным кодом является инициативой backport 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+ только, но доступен для более ранних версий с хэш-совместимость библиотека.)

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

идти дальше с токенами Per-Form

вы можете дополнительно ограничить токены, доступные только для определенной формы, используя 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 templating engine can воспользуйтесь упрощенной двойной стратегией, добавив этот фильтр в свою среду 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 поддерживает библиотека анти-CSRF для этих случаев углу. Он работает исключительно с одноразовыми токенами для каждой формы. Когда в данных сеанса хранится достаточное количество токенов (конфигурация по умолчанию: 65535), сначала будут задействованы самые старые неиспользованные токены.


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