一个令牌与多个令牌以防止 CSRF 攻击

Posted

技术标签:

【中文标题】一个令牌与多个令牌以防止 CSRF 攻击【英文标题】:One token vs. multiple tokens to prevent CSRF attacks 【发布时间】:2014-01-02 11:25:12 【问题描述】:

我正在使用 Codeigniter,我想防止可能发生的 CSRF 攻击尝试。为了实现这一点,我为每个要保护的表单添加了一个带有随机标记的隐藏输入标签,同时我将这个标记保存在会话中,以便在开始处理此表单数据时进行比较。

  // set a token to prevent CSRF attacks
  $csrf_token = md5(uniqid(rand(), true));
  $this->session->set_userdata("csrf_token", $csrf_token);

表格将如下所示:

<form action="path/to/handler/page" method="post">
    <input type="text" name="title">
    <input type="text" name="date">
    <textarea name="content"></textarea>
    <input type="hidden" name="csrf_token" value="<?php echo $this->session->userdata("csrf_token") ?>"> 
    <input type="submit" name="submit" value="Save"> 
</form>

在我处理提交数据的页面中,我检查了类似这样的 CSRF 攻击:

  // make sure there is no CSRF attack attempt
  $csrf_token = $this->session->userdata("csrf_token");
  if (empty($csrf_token) || $csrf_token !== $this->input->post("csrf_token")) 
    die("Some message here!!");
  

而且效果很好。但是正如您所见,我为每个包含表单的页面生成一个随机令牌,在某些情况下,如果我在浏览器中打开另一个选项卡以执行其他操作,这会导致问题。考虑这种情况:

    我打开了add.php 页面来添加一个新项目。 我填写了所有必需的数据,但我没有提交表格。 现在我决定在浏览器的另一个选项卡中打开edit.php 页面来编辑现有项目。 然后我又回到了填写好的add.php页面,尝试提交数据。

此时我会收到一个错误,因为当我打开add.php 页面时已存储在会话中的令牌的值已更改并在我打开edit.php 页面时替换为另一个令牌。那么我该如何解决这个问题呢?当每个用户成功登录时,我是否应该只为他生成一个令牌,然后在他可能处理的所有页面中使用这个令牌?这种方法有什么风险或缺点吗?

【问题讨论】:

不要使用rand() - 出于安全目的,它不够随机。 @SilverlightFox 你建议改用什么? openssl_random_pseudo_bytes 【参考方案1】:

我浏览了您的帖子,没有看到为什么不使用基本的 codeigniter CSRF 保护的理由?您似乎在重新发明***,并制造了标准实现中不存在的问题。

更不用说你违反 DRY 原则,试图将你的令牌打印到每个表单上。有什么理由不保持简单?

Codeigniter 内置 CSRF 保护,可以在 /application/config/config.php 中启用

$config['csrf_protection'] = TRUE;
$config['csrf_token_name'] = 'csrf_token_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;

【讨论】:

有什么理由无缘无故地否决了这个答案?至少对原因发表评论。我认为没有理由重新发明***。简短的谷歌搜索显示最坏的情况是 IE8/9,codeigniter 2+ 修复了大部分问题。 @DavidDuncan 别担心,我赞成你的回答。【参考方案2】:

要解决这个问题,您可以创建一个带有唯一键的令牌字符串,并将键/令牌对存储在会话中(作为 CodeIgniter 中的用户数据)。

考虑到这种情况,您需要以下步骤:

    创建唯一的令牌密钥。 创建令牌字符串。 在会话中存储密钥/令牌对(用户数据)。 为 CSRF 密钥和令牌创建 2 个隐藏的 &lt;input&gt; 元素。 通过检查会话中是否存在密钥/令牌来验证发布的密钥/令牌。

开始工作:

$csrf_key   = "TOKEN_" . mt_rand(0, mt_getrandmax());
$csrf_token = hash("sha512", mt_rand(0, mt_getrandmax()));
// Store the key/token pair in session
$this->session->set_userdata($csrf_key, $csrf_token);

添加隐藏inputs:

<form action="path/to/handler/page" method="post">
    <!-- form input elements -->
    <input type="hidden" name="csrf_key" value="<?php echo $csrf_key; ?>">
    <input type="hidden" name="csrf_token" value="<?php echo $this->session->userdata($csrf_key); ?>">
</form>

验证发布的密钥/令牌:

if (count($_POST)) 
    if (! isset($_POST['csrf_key']) or ! isset($_POST['csrf_token'])) 
        die('No CSRF token found, invalid request.');     
    

    $key   = $this->input->post('csrf_key');
    $token = $this->input->post('csrf_token');

    if ($token !== $this->session->userdata($key))                 
        die('Invalid CSRF token, access denied.');
    

【讨论】:

以上是关于一个令牌与多个令牌以防止 CSRF 攻击的主要内容,如果未能解决你的问题,请参考以下文章

令牌如何防止csrf攻击?

在隐藏字段中添加反 CSRF 令牌真的可以防止 CSRF 攻击吗? [复制]

在基于 AJAX 的应用程序中使用令牌防止 CSRF

何时需要使用令牌保护表单(CSRF 攻击)?

过滤 CSRF 令牌以防止 CSRF

CSRF 和 iframe