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

发布于 2024-11-14 08:15:29 字数 575 浏览 2 评论 0 原文

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

PHP :

if (!isset($_SESSION)) {
    session_start();
    $_SESSION['formStarted'] = true;
}

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

HTML :

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

有什么建议吗?

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 :

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

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

他不在意 2024-11-21 08:15:29

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

它尝试一下:

生成 CSRF 令牌

PHP 7

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

旁注:我雇主的开源项目之一items 是一项将 random_bytes()random_int() 反向移植到 PHP 5 项目中的计划。它已获得 MIT 许可,并可在 Github 和 Composer 上以 paragonie/random_compat 的形式获取。

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

验证 CSRF 令牌

不要只使用 == 甚至 ===,使用 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
    }
}

进一步使用每个表单令牌

您可以使用 hash_hmac( )。 HMAC 是一种特殊的带密钥的哈希函数,即使使用较弱的哈希函数(例如 MD5),也可以安全使用。不过,我建议改用 SHA-2 系列哈希函数。

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

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

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

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

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

奖励:混合方法 + Twig 集成

任何使用 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']);
        }
    )
);

通过此 Twig 功能,您可以像这样使用通用令牌:

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

或者锁定的变体:

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

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


一次性 CSRF 令牌

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

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

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

Try this out:

Generating a CSRF Token

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

PHP 5.3+ (or with 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

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

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.

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

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.

Bonus: Hybrid Approach + Twig Integration

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

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

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


Single-Use CSRF Tokens

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

∝单色的世界 2024-11-21 08:15:29

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

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

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

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

Looks like you need an else with your if.

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
    $_SESSION['token_time'] = time();
}
else
{
    $token = $_SESSION['token'];
}
孤檠 2024-11-21 08:15:29

当变量 $token 位于会话中时,不会从会话中检索该变量

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文