为什么 _token 和 XSRF-TOKEN 在 Laravel 中不同?

goo*_*ing 5 csrf laravel websecurity

我不明白为什么 AJAX 请求 (XSRF-TOKEN) 的令牌与_token正常表单使用的令牌不同。此外,它更长。为什么?为什么有 2 个令牌呢?为什么不使用一个对 ajax 和普通请求都相同的请求?

Abi*_*gos 11

1 方法,2 技术

Laravel 使用 2 种不同的技术来防止 CSRF 攻击。

方法是相同的:

向客户端发送令牌(CSRF 或 XSRF),并且客户端必须在以下请求中将其返回

有两个步骤:

  • 服务器发送令牌(获取表单)(CSRF 或 XSRF)
  • 客户端返回令牌作为X-token(发布表单)(X-CSRF 或 X-XSRF)

当您看到 X- 令牌时,它是客户端通过 Post 发送到服务器的客户端回复

我们拥有两种技术的原因不是它们使用不同的方法

这是因为 Web 应用程序客户端架构使用 2 种不同的架构:

  • 老式:服务器生成纯 html 并将其发送给客户端
  • 单页应用程序:客户端 SPA 框架(如 Vue、React、Angular)以 Json 或 Xml 形式发送和接收数据,并在 Dom 中创建适当的 Html

现在 CSRF-Protection Technics 适应了这两种客户端架构,如下所示:

+-------------+-----------------+-----------+------------+
| Client Arch | Protection Tech | Get Token | Post Token |
+-------------+-----------------+-----------+------------+
| old-fashion | sync-token      | CSRF      | X-CSRF     |
| SPA         | cookie-header   | XSRF      | X-XSRF     |
+-------------+-----------------+-----------+------------+
Run Code Online (Sandbox Code Playgroud)

机制说明

1.服务器生成Token

Laravel 制作一个 CSRF 令牌(40 个字符)并将其存储在会话中

/**
     * Regenerate the CSRF token value.
     *
     * @return void
     */
    public function regenerateToken()
    {
        $this->put('_token', Str::random(40));
    }
Run Code Online (Sandbox Code Playgroud)

在会话中生成并存储令牌后,令牌将作为 CSRF 和 XSRF 发送到客户端

客户端将决定使用它想要的任何内容。

2.服务器向客户端发送Token

对于老式(同步令牌技术)客户端可以通过调用csrf_token()blade中的帮助器方法以两种形式接收CSRF令牌:

  1. 在表单主体中:<input type='hidden' name='_token' value='{{csrf_token()}}' />
  2. 在元标记中,Ajax 请求可以在其标头中使用它

下面是这个辅助方法如何返回相应的值:

/**
     * Get the CSRF token value.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    function csrf_token()
    {
        $session = app('session');

        if (isset($session)) {
            return $session->token();
        }

        throw new RuntimeException('Application session store not set.');
    }
Run Code Online (Sandbox Code Playgroud)

对于 cookie-header(SPA 框架)客户端框架(如 Angular)可以在 Cookie 中接收 XSRF 令牌,因为

服务器中没有生成 Html 表单,服务器可以在其中播种其隐藏输入。它可以将其令牌发送给客户端的方式是使用 cookie 发送它。(此方法名为XSRF)

/**
     * Add the CSRF token to the response cookies.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Symfony\Component\HttpFoundation\Response  $response
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function addCookieToResponse($request, $response)
    {
        $config = config('session');

        $response->headers->setCookie(
            new Cookie(
                'XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']),
                $config['path'], $config['domain'], $config['secure'], false, false, $config['same_site'] ?? null
            )
        );

        return $response;
    }
Run Code Online (Sandbox Code Playgroud)

Laravel 将令牌放在两个地方,因为由客户端决定使用哪种方法,并期望客户端响应其中一种方法。

3.客户端发送X-Token到服务器

在客户端:

  1. 老式(X-CSRF):
  • 在发布数据中发布令牌或:
  • 像这样进行 ajax 调用:
`$.ajaxSetup({
           headers: {
          'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
           }
          });`
Run Code Online (Sandbox Code Playgroud)
  1. SPA 框架:这些框架将令牌作为 X-XSRF-TOKEN 放在帖子标题中

  2. 服务器检查 X- 令牌与会话中令牌


现在是 Laravel 检查令牌的时候了

在VerifyCSRFMiddleware中,Laravel检查请求是否应该检查它检查的CSRF保护令牌:

/**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     *
     * @throws \Illuminate\Session\TokenMismatchException
     */
    public function handle($request, Closure $next)
    {
        if (
            $this->isReading($request) ||
            $this->runningUnitTests() ||
            $this->inExceptArray($request) ||
            $this->tokensMatch($request) //compares request_token vs session_token
        ) {
            return tap($next($request), function ($response) use ($request) {
                if ($this->shouldAddXsrfTokenCookie()) {
                    $this->addCookieToResponse($request, $response); //add cookie to response
                }
            });
        }

        throw new TokenMismatchException('CSRF token mismatch.');
    }
Run Code Online (Sandbox Code Playgroud)

其中两行很有趣:

$this->tokensMatch($request)
Run Code Online (Sandbox Code Playgroud)

$this->addCookieToResponse($request, $response);
Run Code Online (Sandbox Code Playgroud)

因此服务器可以在每个请求中放置多个数据:

  1. html 表单输入 _token(40 个字符)(C SRF)
  2. html 元标头 csrf-token(40 个字符)(C SRF)
  3. cookie XSRF-TOKEN(224 个字符)(X SRF)

客户端可以将多个数据发送到服务器作为对令牌的响应

  1. 后置参数 _token(40 个字符)(X- C SRF)
  2. http 标头 X-CSRF-TOKEN(40 个字符)(X- C SRF)
  3. http 标头 X-XSRF-TOKEN(224 个字符)(X- X SRF)

为什么 CSRF 令牌是 40 个字符,而 XSRF 是 224 个字符?我们稍后会谈到这个

Http 请求必须将令牌与上述X 令牌之一相匹配

   /**
     * Determine if the session and input CSRF tokens match.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function tokensMatch($request)
    {
        $token = $this->getTokenFromRequest($request);// it get token from request

        return is_string($request->session()->token()) &&
               is_string($token) &&
               hash_equals($request->session()->token(), $token); //checks if it is equal to session token or not
    }



/**
     * Get the CSRF token from the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string
     */
    protected function getTokenFromRequest($request)
    {
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');//check sync-token

        if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
            $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
        }

        return $token;
    }
Run Code Online (Sandbox Code Playgroud)

要检查的第一个模式是同步令牌,来自客户端的令牌可以位于Http 标头中,如果从客户端中的 Ajax 方法调用请求,也<input name='_token' />可以位于Http 标头中。

线

$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
Run Code Online (Sandbox Code Playgroud)

将检查它,如果可以检索,它将返回并通过 session_token 检查

if (! $tokenNULL会检查 cookie 标头模式:

从标头获取$header = $request->header('X-XSRF-TOKEN')并解密它(如果需要解密)

$token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
Run Code Online (Sandbox Code Playgroud)

在添加到 cookie 之前是否已加密

Cookie 加密

这就是 XSRF 令牌可能为 224 个字符的原因: Cookie 加密 ,您可以禁用 cookie 加密并使 XSRF 令牌为 40 个字符,就像 CSRF 令牌一样

所以区别在于 Cookie 加密。

Cookie加密的必要性

为什么 Cookie 需要加密?为什么 XSRF Coo​​kie 需要加密?

一般来说,Laravel 会在 cookie 上存储一些数据,并且 cookie 可以由客户端修改。因为服务器不希望客户端进行修改,所以它对Cookie 进行加密。可以将其配置为不加密 CSRF Coo​​kie,因为它不会被用户更改,并且只会被cookie 劫持窃取,而加密不会阻止此事件。

其唯一的区别是必须为两种 CSRF 保护方法提供令牌(未加密和加密)。因此,如果攻击者可以访问cookie 存储的(X-XSRF) 令牌(因为劫持 > Cookie 更容易使用 XSS 劫持运行时 html 和 css),则不能滥用同步令牌机制。由于使用 http-form 参数进行 CSRF 攻击更容易,因为 html 可以在电子邮件等中,而 Runnig Js 不太常见。

结论

因此,如果客户使用老式客户端架构师。cookie 标头技术 >(XSRF 存储在 Cookie 中)不会让他在 cookie 中出现数据泄漏

有关这种预防模式的更多信息可以在这里找到:

https://en.wikipedia.org/wiki/Cross-site_request_forgery#Prevention