如何正确添加使用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不会从会话中被检索到