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

时间:2013-12-14 20:13:07

标签: php codeigniter security csrf

我正在使用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!!");
  }

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

  1. 我打开add.php页面添加新项目。
  2. 我填写了所有必需的数据,但我没有提交表格。
  3. 现在我决定在浏览器的另一个标签页中打开edit.php页面来修改现有项目。
  4. 然后我回到填写的add.php页面并尝试提交数据。
  5. 此时我会收到一个错误,因为当我打开add.php页面时,我打开edit.php页面时存储在会话中的令牌的值已被更改并替换为另一个令牌。那么我该如何解决这个问题呢?我是否应该在成功登录时为每个用户生成一个令牌,然后在他可能处理的所有页面中使用此令牌?这种方法有任何风险或缺点吗?

2 个答案:

答案 0 :(得分:3)

我扫描了您的帖子,但没有看到为什么不使用基本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;

答案 1 :(得分:1)

要解决此问题,您可以使用唯一键创建一个令牌字符串,并在会话中存储键/令牌对(作为CodeIgniter中的userdata)。

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

  1. 创建唯一令牌密钥。
  2. 创建令牌字符串。
  3. 在会话中存储密钥/令牌对(userdata)。
  4. 为CSRF密钥和令牌创建2个隐藏的<input>元素。
  5. 通过检查会话中是否存在密钥/令牌来验证发布的密钥/令牌。
  6. 开始工作:

    $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);
    

    添加隐藏的input s:

    <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.');
        }
    }