Как правильно добавить токен CSRF с помощью PHP - PullRequest
72 голосов
/ 09 июня 2011

Я пытаюсь добавить безопасность в формы на моем веб-сайте.Одна из форм использует 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 ]

235 голосов
/ 28 июля 2015

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

23 голосов
/ 09 июня 2011

Предупреждение безопасности : 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'];
}
1 голос
/ 09 июня 2011

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

...