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

查看:48
本文介绍了如何使用 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()code> 和 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 Token

不要只使用 == 甚至 ===,使用 hash_equals()(仅限 PHP 5.6+,但可用于带有 哈希兼容 库).

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
    }
}

<小时>

进一步使用 Per-Form 令牌

您可以使用 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 库.它专门与一次性的 per-form 令牌一起使用.当会话数据中存储了足够的令牌(默认配置: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天全站免登陆