对于安全代码,请不要以这种方式生成您的令牌:$token = md5(uniqid(rand(), TRUE));
试试这个:
生成 CSRF 令牌
PHP 7
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
旁注:my employer's open source projects 之一是将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
}
}
使用 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 的人都可以通过将此过滤器添加到他们的 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 library。它仅适用于一次性使用的 per-form 令牌。当会话数据中存储了足够的令牌时(默认配置:65535),它将首先循环出最旧的未兑换令牌。