CSRF 防护

跨站请求伪造 (CSRF) 是一种攻击类型,攻击者可以在不知情或未经授权的情况下,利用经过身份验证的用户的身份执行命令。

CakePHP 提供了两种形式的 CSRF 防护

  • SessionCsrfProtectionMiddleware 将 CSRF 令牌存储在会话中。这需要您的应用程序在每次请求时打开会话,这可能会产生副作用。基于会话的 CSRF 令牌的优点是,它们的作用域限定为特定用户,并且只在会话处于活动状态时有效。

  • CsrfProtectionMiddleware 将 CSRF 令牌存储在 Cookie 中。使用 Cookie 可以使 CSRF 检查在服务器上没有任何状态的情况下完成。Cookie 值使用 HMAC 检查进行验证。但是,由于其无状态的性质,CSRF 令牌在不同用户和会话之间是可重复使用的。

注意

您不能同时使用以下两种方法,必须选择其中一种。如果您同时使用两种方法,则每次 PUTPOST 请求都会出现 CSRF 令牌不匹配错误。

跨站请求伪造 (CSRF) 中间件

CSRF 防护可以应用于您的整个应用程序,也可以应用于特定路由作用域。通过将 CSRF 中间件应用于您的应用程序中间件堆栈,您可以保护应用程序中的所有操作。

// in src/Application.php
// For Cookie based CSRF tokens.
use Cake\Http\Middleware\CsrfProtectionMiddleware;

// For Session based CSRF tokens.
use Cake\Http\Middleware\SessionCsrfProtectionMiddleware;

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $options = [
        // ...
    ];
    $csrf = new CsrfProtectionMiddleware($options);
    // or
    $csrf = new SessionCsrfProtectionMiddleware($options);

    $middlewareQueue->add($csrf);

    return $middlewareQueue;
}

通过将 CSRF 防护应用于路由作用域,您可以有条件地将 CSRF 应用于特定路由组。

// in src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Routing\RouteBuilder;

public function routes(RouteBuilder $routes) : void
{
    $options = [
        // ...
    ];
    $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
    parent::routes($routes);
}

// in config/routes.php
$routes->scope('/', function (RouteBuilder $routes) {
    $routes->applyMiddleware('csrf');
});

基于会话的 CSRF 中间件选项

可用的配置选项有

  • key 要使用的会话键。默认为 csrfToken

  • field 要检查的表单字段。更改此项还需要配置 FormHelper。

启用后,您可以从请求对象访问当前 CSRF 令牌

$token = $this->request->getAttribute('csrfToken');

如果您需要旋转或替换会话 CSRF 令牌,您可以使用以下方法

$this->request = SessionCsrfProtectionMiddleware::replaceToken($this->request);

Added in version 4.3.0: 添加了 replaceToken 方法。

跳过特定操作的 CSRF 检查

两种 CSRF 中间件实现都允许您使用跳过检查回调功能,以便更细粒度地控制哪些 URL 应该执行 CSRF 令牌检查。

// in src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $csrf = new CsrfProtectionMiddleware();

    // Token check will be skipped when callback returns `true`.
    $csrf->skipCheckCallback(function ($request) {
        // Skip token check for API URLs.
        if ($request->getParam('prefix') === 'Api') {
            return true;
        }
    });

    // Ensure routing middleware is added to the queue before CSRF protection middleware.
    $middlewareQueue->add($csrf);

    return $middlewareQueue;
}

注意

您应该只对使用 Cookie/会话处理有状态请求的路由应用 CSRF 防护中间件。例如,在开发 API 时,不使用 Cookie 进行身份验证的无状态请求不受 CSRF 的影响,因此不需要为这些路由应用中间件。

与 FormHelper 集成

CsrfProtectionMiddlewareFormHelper 无缝集成。每次您使用 FormHelper 创建表单时,它都会插入一个包含 CSRF 令牌的隐藏字段。

注意

使用 CSRF 防护时,您应该始终使用 FormHelper 启动您的表单。如果不这样做,您需要在每个表单中手动创建隐藏输入。

CSRF 防护和 AJAX 请求

除了请求数据参数之外,CSRF 令牌还可以通过特殊的 X-CSRF-Token 标头提交。使用标头通常可以更容易地将 CSRF 令牌与 JavaScript 密集型应用程序或基于 XML/JSON 的 API 端点集成。

CSRF 令牌可以通过 JavaScript 的 Cookie csrfToken 或 PHP 的请求对象属性 csrfToken 获取。当您的 JavaScript 代码驻留在与 CakePHP 视图模板分离的文件中时,以及当您已经具有通过 JavaScript 解析 Cookie 的功能时,使用 Cookie 可能更容易。

如果您有单独的 JavaScript 文件,但不想处理 Cookie,您可以例如在布局中将令牌设置为全局 JavaScript 变量,方法是定义一个类似这样的脚本块

echo $this->Html->scriptBlock(sprintf(
    'var csrfToken = %s;',
    json_encode($this->request->getAttribute('csrfToken'))
));

然后,您可以在此脚本块之后加载的任何脚本文件中以 csrfTokenwindow.csrfToken 的形式访问令牌。

另一个选择是将令牌放在一个自定义的元标记中,例如

echo $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken'));

然后,可以通过查找名为 csrfTokenmeta 元素,在脚本中访问它,使用 jQuery 可以像这样简单地实现

var csrfToken = $('meta[name="csrfToken"]').attr('content');