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


96

我正在尝试为我的网站上的表单添加一些安全性。一种形式是使用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

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

有什么建议?


只是好奇,它有什么token_time用?
zerkms 2011年

@zerkms我当前未使用token_time。我打算限制令牌有效的时间,但是还没有完全实现代码。为了清楚起见,我将其从上面的问题中删除。

1
@Ken:这样,用户可以在打开表单,发布表单并获取无效令牌时得到案件?(因为它已失效)
zerkms 2011年

@zerkms:谢谢,但是我有点困惑。您有机会为我提供示例吗?

2
@肯:当然。假设令牌在10:00 am到期。现在是上午09:59。用户打开一个表单并获得一个令牌(仍然有效)。然后,用户填写表格2分钟,然后将其发送。只要现在是上午10:01,令牌就会被视为无效,因此用户会收到表单错误。
zerkms 2011年

Answers:


286

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

试试看:

生成CSRF令牌

PHP 7

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的形式获得

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 为这些极端情况维护了一个反CSRF库。它仅与一次性使用的形式令牌一起使用。当会话数据中存储了足够的令牌(默认配置:65535)时,它将首先循环出最旧的未兑换令牌。


不错,但是在用户提交表单后如何更改$ token?在您的情况下,一个令牌用于用户会话。
Akam

1
仔细查看github.com/paragonie/anti-csrf的实现方式。令牌是一次性的,但可以存储多个令牌。
Scott Arciszewski'3

@ScottArciszewski您如何看待从具有会话密钥的会话ID中生成消息摘要,然后将接收到的CSRF令牌摘要与再次将会话ID与我之前的密钥进行哈希运算进行比较?我希望你明白我的意思。
MNR

1
我对验证CSRF令牌有疑问。如果$ _POST ['token']为空,则不应继续,因为此发布请求是在没有令牌的情况下发送的,对吗?
弘树

1
因为它将被回显到HTML表单中,并且您希望它是不可预测的,所以攻击者不能仅仅伪造它。您实际上是在这里实现质询响应身份验证,而不仅仅是“是的,这种形式是合法的”,因为攻击者可以欺骗它。
Scott Arciszewski

24

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

看来您是否需要if的其他条件。

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

11
注意:我不会信任md5(uniqid(rand(), TRUE));安全上下文。
Scott Arciszewski

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.