Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do _token and XSRF-TOKEN differ in Laravel?

Tags:

I don't understand why is the token for AJAX requests (XSRF-TOKEN) different from a _token that normal forms use. In addition, it's much longer. Why? And why have 2 tokens at all? Why not just use one which would be same for both ajax and normal requests?

like image 614
good_evening Avatar asked Oct 29 '20 13:10

good_evening


1 Answers

1 Approach, 2 Technics

Laravel Uses 2 distinct Technics to prevent CSRF Attack.

The Approaches are The same:

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

and there are 2 steps:

  • server sends token (get a form) (CSRF or XSRF)
  • client return token as X-token (post a form) (X-CSRF or X-XSRF)

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

The Reason we have 2 technics is not these uses different approaches,

its because web application Client-Side Architectures using 2 different Architectures :

  • old-fashion : server generates pure html and send it to client
  • Single Page Application : client SPA Framework (like Vue,React,Angular) send and receive data as Json or Xml and create proper Html in Dom

Now CSRF-Protection Technics Adapts with this Two Client-Side Architectures as Below:

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

Mechanism Description

1.Server Generates Token

Laravel make a CSRF Token (40 chars) and store it in session

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

After Generating and Storing token in Session, Token Will be Send To Client as CSRF and XSRF

client side will decide to use whatever it wants.

2.Server Sends Token To Client

for the old-fashioned (sync-token technic) client can receive The CSRF Token in two forms with call to csrf_token() helper method in blade:

  1. in form body : <input type='hidden' name='_token' value='{{csrf_token()}}' />
  2. in meta tag that Ajax request can use it in its header

here is how this helper method returns corresponding value:

/**
     * 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) can receive XSRF Token in the Cookie Because:

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 put token in both places since its up to client which method to use, and expect client to response to one of this methods.

3.Client Sends X- Token To Server

In client-side:

  1. old-fashion (X-CSRF):
  • post token in post data or:
  • make ajax call like this:
`$.ajaxSetup({
           headers: {
          'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
           }
          });`
  1. SPA Framework : These Framework put Token as X-XSRF-TOKEN in Post Headers

  2. Server Checks X- Token Vs in-session Token


Now Its Time To Laravel Check For The Token

in VerifyCSRFMiddleware, Laravel Checks if The Request Should Be Check For CSRF Protection Token it Checks :

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

Two of lines are in interest:

$this->tokensMatch($request)

and

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

so there are multiple data in each request that server can put:

  1. html form input _token (40 chars) (CSRF)
  2. html meta header csrf-token (40 chars) (CSRF)
  3. cookie XSRF-TOKEN (224 chars) (XSRF)

and multiple data can client send to server as response to the tokens

  1. post parameter _token (40 chars) (X-CSRF)
  2. http header X-CSRF-TOKEN (40 chars) (X-CSRF)
  3. http header X-XSRF-TOKEN (224 chars) (X-XSRF)

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

The Http Request Has To Match Token with one of the Above 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;
    }

first pattern to examine is sync-token, token from client can be in an <input name='_token' /> or it can be in Http Header if requested from an Ajax method call in the client.

the line

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

will check for that and if it can be retrieved, it will return and check via the session_token

but if (! $token is NULL it will check against the cookie-header pattern:

getting $header = $request->header('X-XSRF-TOKEN') from header and decrypt it, if its need decryption

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

if it has been encrypted before it has been added to cookie

Cookie Encryption

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.

Necessity of Cookie Encryption

But Why Cookie Needs to get Encrypted?? Why XSRF Cookie needs to get Encrypted??

In General, Laravel Store some data on cookies and cookie can be modified by client. because server dose not want modifications on client, it Encrypt Cookies . this can be config to not to Encrypt CSRF Cookie Since it is not subjected to change by user and its only subjected to be stole by cookie hijacking which encryption is not going to preventing this event.

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.

Conclusion

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.

further information on this prevention patterns can be found here:

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

like image 62
Abilogos Avatar answered Sep 22 '22 12:09

Abilogos