为了安全起见,请不要使用以下方式生成您的令牌:$token = md5(uniqid(rand(), TRUE));
请尝试以下代码:
生成 CSRF 令牌
PHP 7
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];
附注:我的雇主的开源项目之一是将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'])) {
} else {
}
}
深入了解Per-Form Tokens
您可以使用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_time
用于什么? - zerkmstoken_time
。我原本想限制令牌有效的时间,但是代码还没有完全实现。为了清晰起见,我已经从上面的问题中将其删除。 - Ken