为啥 _token 和 XSRF-TOKEN 在 Laravel 中有所不同?

Posted

技术标签:

【中文标题】为啥 _token 和 XSRF-TOKEN 在 Laravel 中有所不同?【英文标题】:Why do _token and XSRF-TOKEN differ in Laravel?为什么 _token 和 XSRF-TOKEN 在 Laravel 中有所不同? 【发布时间】:2021-02-11 23:08:18 【问题描述】:

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

【问题讨论】:

“Laravel 将当前的 CSRF 令牌存储在一个加密的 XSRF-TOKEN cookie 中,该 cookie 包含在框架生成的每个响应中。” laravel.com/docs/8.x/csrf#csrf-x-csrf-token 为什么要加密? . ***.com/a/34783845/2797224 【参考方案1】:

1 种方法,2 种技术

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

方法是一样的:

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

有两个步骤:

服务器发送token(获取表单)(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     |
+-------------+-----------------+-----------+------------+

机构说明

1.服务器生成Token

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

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

在Session中生成并存储token后,Token会作为CSRF和XSRF发送给客户端

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

2.Server向Client发送Token

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

    在表单正文中:<input type='hidden' name='_token' value='csrf_token()' /> 在元标记中,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.');
    

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

服务器中没有生成 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;
    

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

3.Client向Server发送X-Token

在客户端:

    老式 (X-CSRF):
在发布数据中发布令牌或: 像这样进行 ajax 调用:
`$.ajaxSetup(
           headers: 
          'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
           
          );`

    SPA 框架:这些框架将 Token 作为 X-XSRF-TOKEN 放在 Post Headers 中

    服务器检查 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.');
    

有两行感兴趣:

$this->tokensMatch($request)

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

所以服务器可以在每个请求中放入多个数据:

    html 表单输入 _token(40 个字符)(CSRF) html 元标题 csrf-token(40 个字符)(CSRF) cookie XSRF-TOKEN(224 个字符)(XSRF)

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

    post 参数 _token(40 个字符)(X-CSRF) http 标头 X-CSRF-TOKEN(40 个字符)(X-CSRF) http 标头 X-XSRF-TOKEN(224 个字符)(X-XSRF)

为什么在 CSRF 令牌中是 40 个字符而在 XSRF 中是 224 个字符? 我们稍后会讨论这个问题

Http 请求必须将 Token 与上述 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;
    

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

线

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

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

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

如果需要解密,则从标头中获取$header = $request->header('X-XSRF-TOKEN') 并对其进行解密

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

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

Cookie 加密

这就是 XSRF 令牌可能是 224chars 的原因: Cookie 加密 并且您可以禁用 cookie 加密并将 XSRF 令牌设置为 40 个字符,例如 CSRF 令牌

所以区别在于 Cookie 加密。

Cookie 加密的必要性

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

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

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

结论

如果客户使用老式客户架构师。 cookie-header 技术 >(XSRF 存储在 Cookie 中)不会给他留下 cookie 中的数据泄漏

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

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

【讨论】:

【参考方案2】:

如果您查看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 长度令牌。

旁注:token本身没有加密,所有会话在laravel中默认都是加密的。

【讨论】:

【参考方案3】:

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

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

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

【讨论】:

【参考方案4】:

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

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

发送此 cookie 主要是为了方便,因为某些 javascript 框架和库(如 Angular 和 Axios)会自动将其值放在同源请求的 X-XSRF-TOKEN 标头中。

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

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

【讨论】:

以上是关于为啥 _token 和 XSRF-TOKEN 在 Laravel 中有所不同?的主要内容,如果未能解决你的问题,请参考以下文章

快速 CSRF 令牌验证

Angularjs 正在从 Spring Boot 接收 XSRF-TOKEN 但不发送 X-XSRF-TOKEN

Spring 何时发回 XSRF-TOKEN set-cookie 响应标头?

如何强制 Spring Security 更新 XSRF-TOKEN cookie?

laravel5

在标头中发送令牌时,Laravel TokenMismatch Exception