如何使用PHP正确添加跨站点请求伪造(CSRF)令牌 [英] How to properly add cross-site request forgery (CSRF) token using PHP

查看:308
本文介绍了如何使用PHP正确添加跨站点请求伪造(CSRF)令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为我的网站上的表单添加一些安全性.一种形式是使用AJAX,另一种形式是简单的与我们联系"形式.我正在尝试添加CSRF令牌.我遇到的问题是令牌有时仅在HTML值"中显示.其余时间,该值为空.这是我在AJAX表单上使用的代码:

I am trying to add some security to the forms on my website. One of the forms uses AJAX and the other is a straightforward "contact us" form. I'm trying to add a CSRF token. The problem I'm having is that the token is only showing up in the HTML "value" some of the time. The rest of the time, the value is empty. Here is the code I am using on the AJAX form:

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>

有什么建议吗?

推荐答案

对于安全代码,请不要以这种方式生成令牌:$token = md5(uniqid(rand(), TRUE));

For security code, please don't generate your tokens this way: $token = md5(uniqid(rand(), TRUE));

  • rand() is predictable
  • uniqid() only adds up to 29 bits of entropy
  • md5() doesn't add entropy, it just mixes it deterministically

尝试一下:

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 的形式提供.

Sidenote: One of my employer's open source projects is an initiative to backport random_bytes() and random_int() into PHP 5 projects. It's MIT licensed and available on Github and Composer as paragonie/random_compat.

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 库的早期版本.

Verifying the CSRF Token

Don't just use == or even ===, use hash_equals() (PHP 5.6+ only, but available to earlier versions with the hash-compat library).

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),也可以安全使用.但是,我建议改为使用SHA-2系列哈希函数.


Going Further with Per-Form Tokens

You can further restrict tokens to only be available for a particular form by using hash_hmac(). HMAC is a particular keyed hash function that is safe to use, even with weaker hash functions (e.g. MD5). However, I recommend using the SHA-2 family of hash functions instead.

首先,生成第二个令牌以用作HMAC密钥,然后使用如下所示的逻辑对其进行渲染:

First, generate a second token for use as an HMAC key, then use logic like this to render it:

<input type="hidden" name="token" value="<?php
    echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />

然后在验证令牌时使用全等操作:

And then using a congruent operation when verifying the token:

$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
    // Continue...
}

为一种形式生成的令牌在不知道$_SESSION['second_token']的情况下无法在另一种上下文中重用. 重要的是,您要使用一个单独的令牌作为HMAC密钥,而不是您刚刚在页面上放下的令牌.

The tokens generated for one form cannot be reused in another context without knowing $_SESSION['second_token']. It is important that you use a separate token as an HMAC key than the one you just drop on the page.

使用 Twig模板引擎的任何人都可以通过将简化过滤器添加到他们的Twig环境中而受益于简化的双重策略. :

Anyone who uses the Twig templating engine can benefit from a simplified dual strategy by adding this filter to their Twig environment:

$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功能,您可以像这样使用两个通用令牌:

With this Twig function, you can use both the general purpose tokens like so:

<input type="hidden" name="token" value="{{ form_token() }}" />

或锁定的变体形式:

<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />

Twig仅关注模板渲染;您仍然必须正确验证令牌.我认为,Twig战略提供了更大的灵活性和简便性,同时又保持了最大的安全性.

Twig is only concerned with template rendering; you still must validate the tokens properly. In my opinion, the Twig strategy offers greater flexibility and simplicity, while maintaining the possibility for maximum security.

如果您有一个安全要求,即每个CSRF令牌只能使用一次,则最简单的策略是在每次成功验证后重新生成它.但是,这样做会使以前的所有令牌失效,而这与一次浏览多个选项卡的人不能很好地融合在一起.

If you have a security requirement that each CSRF token is allowed to be usable exactly once, the simplest strategy regenerate it after each successful validation. However, doing so will invalidate every previous token which doesn't mix well with people who browse multiple tabs at once.

Paragon Initiative Enterprises为这些极端情况维护了 Anti-CSRF库.它仅与一次性使用的形式令牌一起使用.当会话数据中存储了足够的令牌(默认配置:65535)时,它将首先循环出最旧的未兑换令牌.

Paragon Initiative Enterprises maintains an Anti-CSRF library for these corner cases. It works with one-use per-form tokens, exclusively. When enough tokens are stored in the session data (default configuration: 65535), it will cycle out the oldest unredeemed tokens first.

这篇关于如何使用PHP正确添加跨站点请求伪造(CSRF)令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆