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

Why do _token and XSRF-TOKEN differ in Laravel?

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

如果您看一下 vendor/laravel/framework/src/Illuminate/Session/Store.php,有一个名为 regenerateToken 的方法,它将为应用程序生成令牌

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

然后是从 sessionJS(也来自会话)获得的令牌,它们都具有相同的 40 个字符长度,并且令牌只是一个简单的随机 40存储在您的会话中的字符。它没有加密或散列,因为只有你,用户可以访问会话,因为如果我想从任何外部来源进行 CSRF 攻击,源无法访问会话所以基本上它不需要散列或加密那 40 长度令牌。

旁注:令牌本身不加密,默认情况下所有会话都在laravel中加密。

以下是文档中关于 XSRF 的内容:

https://laravel.com/docs/8.x/csrf#csrf-x-xsrf-token

所以基本上它与加密并保存在 cookie 中的 _token 的值相同

1 方法,2 技术

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

方法相同:

to send a token (CSRF or XSRF) to The Client and Client Have to return it back in following request

并且有两个步骤:

  • 服务器发送令牌(获取表单)(CSRF 或 XSRF)
  • client return token as X-token (post 一个表单) (X-CSRF or X-XSRF)

when you see an X- token its an client-replied that client sends with Post to the server

我们有 2 个技术的原因 不是这些使用不同的方法,

这是因为 Web 应用程序 Client-Side 使用 2 种不同架构的架构:

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

现在 CSRF-Protection Technics 适应了这两个 Client-Side 架构,如下所示:

+-------------+-----------------+-----------+------------+
| Client Arch | Protection Tech | Get Token | Post Token |
+-------------+-----------------+-----------+------------+
| old-fashion | sync-token      | CSRF      | X-CSRF     |
| SPA         | cookie-header   | XSRF      | X-XSRF     |
+-------------+-----------------+-----------+------------+

机制说明

1.Server 生成令牌

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

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

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

客户端将决定使用它想要的任何东西。

2.Server 发送令牌给客户端

对于 old-fashioned(sync-token 技术)客户端可以通过调用 blade 中的 csrf_token() 辅助方法接收两种形式的 CSRF 令牌:

  1. 形式 body : <input type='hidden' name='_token' value='{{csrf_token()}}' />
  2. 在 Ajax 请求的元标记中可以在其 header
  3. 中使用它

这里是这个辅助方法return的对应值:

/**
     * 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.');
    }

for cookie-header (SPA Frameworks) client framework (like Angular) 可以在 Cookie 中接收 XSRF Token 因为:

there is no Html Form generating in the server which server can seed its hidden input in it. and The Way it can send its token to the client is sending it with cookie. (This method named 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;
    }

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

3.Client 发送 X- 令牌到服务器

在client-side中:

  1. old-fashion (X-CSRF):
  • post 令牌在 post 数据中或:
  • 像这样调用 ajax:
`$.ajaxSetup({
           headers: {
          'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
           }
          });`
  1. SPA 框架:这些框架将令牌作为 X-XSRF-TOKEN 放入 Post Headers

  2. 服务器检查 X 令牌与 in-session 令牌


现在是 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.');
    }

有两行感兴趣:

$this->tokensMatch($request)

$this->addCookieToResponse($request, $response);

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

  1. html 表单输入 _token (40 个字符) (CSRF )
  2. html 元 header csrf-token(40 个字符)(CSRF)
  3. cookie XSRF-TOKEN (224 个字符) (XSRF)

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

  1. post 参数 _token (40 个字符) (X-C SRF)
  2. http header X-CSRF-TOKEN (40 个字符) (X- CSRF)
  3. http header X-XSRF-TOKEN (224 个字符) (X- XSRF)

Why in CSRF token are 40 chars and in XSRF are 224 chars ? We Will get to this a little bit latter

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

   /**
     * 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;
    }

要检查的第一个模式是 sync-token,来自客户端的令牌可以在 <input name='_token' /> 中,也可以在 Http Header 中,如果从客户端中的 Ajax 方法调用请求。

$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

将检查它,如果可以检索到它,它将 return 并通过 session_token

检查

if (! $tokenNULL 它将检查 cookie-header 模式:

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

$token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));

如果在添加到cookie之前已经加密

Cookie 加密

This is The Reason That XSRF Token Could be 224chars : Cookie Encryption and you may disable cookie Encryption and make the XSRF Token 40 chars, Like The CSRF Token

so The Difference was for The Cookie Encryption.

Cookie加密的必要性

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

一般来说,Laravel在cookies上存储一些数据,cookie是可以被客户端修改的。因为服务器不想在客户端上进行修改,所以它 Encrypt Cookies 。 这可以配置为不 加密 CSRF Coo​​kie 因为它不会被用户更改并且它只会被 cookie 劫持 窃取加密不会阻止此事件。

The only Difference its make is to having to token (unencrypted and encrypted) for two CSRF Protection methods. So if attackers can access a cookie-stored (X-XSRF) Token (Since Hijacking > Cookie is much easier to hijacking runtime html and css with XSS ) it cannot be Abuse With sync-token mechanism. Since CSRF Attack with http-form parameter is easier since html can be in email or etc While Runnig Js is Less common.

结论

So if a client use old-fashion client architect . the cookie-header technic > ( XSRF stored in Cookie ) wont leave him with a data leak in cookie.

有关此预防模式的更多信息可在此处找到:

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

简短的回答是 XSRF-TOKEN 是加密的 CSRF-TOKEN,就是这样。加密后的字符串将比原始字符串长得多。

它存在的原因是(如文档中所述):

This cookie is primarily sent as a convenience since some JavaScript frameworks and libraries, like Angular and Axios, automatically place its value in the X-XSRF-TOKEN header on same-origin requests.

这意味着当使用自动在请求中设置 X-XSRF-TOKEN header 的框架或库时,如果存在 XSRF-TOKEN cookie,那么您就不必担心关于在每次请求时传递 CSRF 令牌,因为它是自动完成的。

就我个人而言,我不能 100% 确定手册中的陈述目前是否准确或是否真的有用。我个人最终完全删除了这个 cookie,因为防火墙配置不当,我们的一些客户使用它来剥离长 cookie,到目前为止我还没有错过它。