如何正确添加使用PHP的CSRF令牌
我正在尝试为我的网站上的表单添加一些安全性。 其中一种forms使用AJAX,另一种forms是直接“联系我们”forms。 我正在尝试添加一个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>
有什么build议么?
安全警告 :
md5(uniqid(rand(), TRUE))
不是一个安全的方式来产生随机数字。 看到这个答案更多的信息和利用密码安全的随机数发生器的解决scheme。
看起来像你需要一个与你的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));
-
rand()
是可预测的 -
uniqid()
只能加起来29位的熵 -
md5()
不添加熵,它只是确定性地混合它
试试这个:
生成一个CSRF令牌
PHP 7
session_start(); if (empty($_SESSION['token'])) { $_SESSION['token'] = bin2hex(random_bytes(32)); } $token = $_SESSION['token'];
旁注: 我的一个雇主的开源项目是将random_bytes()
和random_int()
移植到PHP 5项目中的一项举措。 它是麻省理工学院许可的,可以在Github和Composer上以paragonie / random_compat的forms获得 。
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'];
validationCSRF令牌
不要只使用==
甚至===
,使用hash_equals()
(仅PHP 5.6+,但可用于早期版本与哈希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)也可以安全使用。 不过,我build议使用SHA-2系列散列函数。
首先,生成第二个令牌用作HMAC密钥,然后使用这样的逻辑来渲染它:
<input type="hidden" name="token" value="<?php echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']); ?>" />
然后在validation令牌时使用一致操作:
$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']); if (hash_equals($calc, $_POST['token'])) { // Continue... }
为一个表单生成的令牌不能在不知道$_SESSION['second_token']
情况下在另一个上下文中重用。 使用一个单独的标记作为一个HMAC密钥,而不是仅仅放在页面上。
奖金:混合方式+树枝整合
任何使用Twig模板引擎的人都可以通过在Twig环境中添加此filter来获得简化的双重策略:
$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() }}" />
或者locking的变体:
<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />
小枝只关心模板渲染; 您仍然必须正确validation令牌。 在我看来,Twig策略提供了更大的灵活性和简单性,同时保持了最大安全性的可能性。
一次性使用CSRF令牌
如果您有一个安全要求,即允许每个CSRF令牌只能使用一次,则最简单的策略将在每次成功validation后重新生成。 但是,这样做会使每个以前的令牌失效,这种令牌不能与同时浏览多个选项卡的人混合。
Paragon Initiative Enterprises为这些案例保留了一个Anti-CSRF库 。 它只与一次性使用的每个表单令牌一起工作。 当足够的令牌存储在会话数据中时(默认configuration:65535),它会先循环掉最旧的未兑换令牌。
当它在那里时,variables$token
不会从会话中被检索到