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

2022-08-30 07:17:21

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

菲律宾比索 :

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

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

网页 :

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

有什么建议吗?


答案 1

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

试试这个:

生成 CSRF 令牌

7 菲律宾比索

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

旁注:我雇主的开源项目之一是向后移植和进入 PHP 5 项目的倡议。它是麻省理工学院授权的,可在Github和Composer上作为paragonie / random_compatrandom_bytes()random_int()

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+,但可用于具有哈希兼容库的早期版本)。=====

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

为一种形式生成的令牌不能在不了解 的情况下在另一个上下文中重用。使用单独的令牌作为 HMAC 密钥比刚刚放在页面上的令牌更重要。$_SESSION['second_token']

奖励:混合方法+树枝集成

任何使用 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为这些角落案例维护了一个反CSRF库。它只适用于一次性每表单令牌。当会话数据中存储了足够的令牌(默认配置:65535)时,它将首先循环出最旧的未赎回令牌。


答案 2

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

看起来你需要一个其他人与你的如果。

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

推荐