如何使用PHP正确添加CSRF令牌

时间:2011-06-09 03:52:31

标签: php security session csrf

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

有什么建议吗?

4 个答案:

答案 0 :(得分:226)

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

试试这个:

生成CSRF令牌

PHP 7

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

旁注:my employer's open source projects之一是将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 templating engine的任何人都可以通过将此过滤器添加到其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 library。它仅适用于一次性使用的单一形式令牌。当足够的令牌存储在会话数据中时(默认配置:65535),它将首先循环出最旧的未兑换令牌。

答案 1 :(得分:23)

  

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

看起来你需要另外一个if。

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

答案 2 :(得分:1)

当会话

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

答案 3 :(得分:-1)

CSRF 保护

CSRF 使用的类型

形式

<form>
   @csrf
</form>

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

元标记

<meta name="csrf-token" content="{{ csrf_token() }}">

AJAX

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

会议

use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    $token = $request->session()->token();

    $token = csrf_token();

    // ...
});

中间件

 App\Providers\RouteServiceProvider

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'stripe/*',
        'http://example.com/foo/bar',
        'http://example.com/foo/*',
    ];
}