Для коду безпеки не генеруйте свої токени таким чином: $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), він спочатку вимкне найстаріші невикористані маркери.
token_time
використовується?