如何使用 PHP 正确添加跨站点请求伪造 (CSRF) 令牌

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6287903/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 23:54:41  来源:igfitidea点击:

How to properly add cross-site request forgery (CSRF) token using PHP

phpsecuritysessioncsrf

提问by Ken

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:

我正在尝试为我网站上的表单添加一些安全性。其中一个表单使用 AJAX,另一个是简单的“联系我们”表单。我正在尝试添加 CSRF 令牌。我遇到的问题是令牌仅在某些时候出现在 HTML“值”中。其余时间,该值为空。这是我在 AJAX 表单上使用的代码:

PHP:

PHP:

if (!isset($_SESSION)) {
    session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token']))
{$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;

}

HTML

HTML

 <form>
//...
<input type="hidden" name="token" value="<?php echo $token; ?>" />
//...
</form>

Any suggestions?

有什么建议?

回答by Scott Arciszewski

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

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

Try this out:

试试这个:

Generating a CSRF Token

生成 CSRF 令牌

PHP 7

PHP 7

session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];

Sidenote: One of my employer's open source projectsis 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.

旁注:一个我的雇主的开源项目是一个主动向移植random_bytes()random_int()到PHP 5个项目。它已获得 MIT 许可,可在 Github 和 Composer 上以paragonie/random_compat 的形式使用

PHP 5.3+ (or with ext-mcrypt)

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'];

Verifying the CSRF Token

验证CSRF令牌

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

不要只使用==甚至===, 使用hash_equals()(仅限 PHP 5.6+,但可用于具有hash-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
    }
}


Going Further with Per-Form Tokens

使用 Per-Form 令牌走得更远

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.

您可以使用 进一步限制令牌仅可用于特定表单hash_hmac()。HMAC 是一种特殊的密钥散列函数,即使使用较弱的散列函数(例如 MD5),也可以安全使用。但是,我建议改用 SHA-2 系列哈希函数。

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

首先,生成用作 HMAC 密钥的第二个令牌,然后使用这样的逻辑来呈现它:

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

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.

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

Bonus: Hybrid Approach + Twig Integration

奖励:混合方法 + Twig 集成

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

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

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

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

使用此 Twig 函数,您可以像这样使用通用令牌:

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

Or the locked down variant:

或锁定变体:

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

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.

Twig 只关心模板渲染;您仍然必须正确验证令牌。在我看来,Twig 策略提供了更大的灵活性和简单性,同时保持了最大安全性的可能性。



Single-Use CSRF Tokens

一次性 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.

如果您有一个安全要求,即允许每个 CSRF 令牌只能使用一次,那么最简单的策略是在每次成功验证后重新生成它。但是,这样做会使以前与同时浏览多个选项卡的人不能很好地混合的每个令牌无效。

Paragon Initiative Enterprises maintains an Anti-CSRF libraryfor 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.

Paragon Initiative Enterprises为这些极端情况维护了一个Anti-CSRF 库。它专门与一次性的 per-form 令牌一起使用。当会话数据中存储了足够的令牌(默认配置:65535)时,它会首先循环出最旧的未赎回令牌。

回答by datasage

Security Warning: md5(uniqid(rand(), TRUE))is not a secure way to generate random numbers. See this answerfor more information and a solution that leverages a cryptographically secure random number generator.

安全警告md5(uniqid(rand(), TRUE))不是生成随机数的安全方式。有关更多信息和利用加密安全随机数生成器的解决方案,请参阅此答案

Looks like you need an else with your if.

看起来你的 if 需要一个 else 。

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
    $_SESSION['token_time'] = time();
}
else
{
    $token = $_SESSION['token'];
}

回答by Dani

The variable $tokenis not being retrieved from the session when it's in there

当变量$token在会话中时,不会从会话中检索它