laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.43k stars 10.99k forks source link

419 Error despite valid token being passed correctly #26106

Closed Xerotherm1c closed 6 years ago

Xerotherm1c commented 6 years ago

Description:

Using an out-of-the-box Laravel setup from laravel new appname, CSRF functionality appears to be broken on v5.7.9 but v5.7.3 works perfectly with the server software listed above.

Steps To Reproduce:

https://stackoverflow.com/questions/52764590/laravel-419-error-verifycsrftoken-issue

laurencei commented 5 years ago

@KeitelDOG - as I mentioned earlier - what you've described is simply a session persistence issue and has nothing to do with CSRF (but the CSRF error you get is because of the session failure).

Looking at your code above, if you have different permissions, then once again it's nothing to do with Laravel. If you google around, there's a few examples of how to change the user settings for directories; essentially you need to make sure the web user has the right permission to write to the storage folder along with your console user.

It sounds like your web user does not, and therefore sessions are failing.

You could also switch to a different session system, like Redis or Memcache, which is probably better for a production server.

PieterjanDeClippel commented 5 years ago

Can anyone reproduce this issue on a fresh Laravel installation?

Yes. Downloaded a laravel version yesterday. The odd thing is I created 2 projects at once. The one project gives the 419 on login, the other doesn't... Same server, same configuration. The only difference is that the failing project has a SESSION_DOMAIN=.example.com environment variable... When I remove this, the login is working fine

magusd commented 5 years ago

Hi, just hit the same issue and fixed it and many of you are partially correct. It IS a CSRF issue caused by the lack of session. And it is only happening because the project is missing write permissions. So, not a laravel issue, it's an installation issue. Whoever is acting as the webserver must have write permissions on the storage folder.

KeitelDOG commented 5 years ago

I finally see that it's probably not a Laravel issue. @magusd the are many ways of solving this according to what happened. I have reproduced this error in so many ways that it started to freaking me out. But at the end, it's a permission access to that file before Laravel accesses it. Here the many Layers that could be denying the permission :

But I resolved and controlled all of this, but the permission problem persist sometimes. So if Laravel did not messed up with the file permissions just before accessing it, it can't be a Laravel bug for sure. Maybe the System itself create a strange behavior to limit the permission on some special cases that I cannot catch.

I give up on this cause this doesn't seem a Laravel coding error. Instead thanks to @laurencei I'm already changing to Redis for Staging to test before I apply it to production, this way I won't get anymore Sessions permission access.

zyglobe commented 5 years ago

I've been using the redis driver all along (so file permission issues, while already set correctly, wouldn't have been the problem). However, I discovered that I'm dealing with an intermittent redis or predis connection issue (even with read_write_timeout set to -1).

local.ERROR: Error while reading line from the server. [tcp://127.0.0.1:6379]

Just offering what I found in case someone else stumbles upon this thread with a similar issue/scenario.

KeitelDOG commented 5 years ago

RESOLVED

I finally resolved it, so this is NOT a Laravel Bug, but I think Laravel developers could handle this behavior themselves.

It was a problem with the Cookie conflicts between different environment on the same browser. That's why other browsers that access only production didn't have that problem.

To resolve any of the 419 related error :

Check Permission

After making sure your permission is ok for creating and accessing the file as I suggested earlier :

I finally see that it's probably not a Laravel issue. @magusd the are many ways of solving this according to what happened. I have reproduced this error in so many ways that it started to freaking me out. But at the end, it's a permission access to that file before Laravel accesses it. Here the many Layers that could be denying the permission :

  • storage/framework/sessions owner or group does not belong to Apache or Nginx User let's say www-data
  • storage/framework/sessions owner or group belongs to Apache or Nginx User let's say www-data, but does not have read and write permission to either owner or group. You should change permission with chmod and give 6 at least to owner or group.
  • umask has been changed in your Apache or Nginx settings. Default umask in Apache is 0002 => 0664 which gives read and write permission for owner and group, and read for everyone, for created files with config user www-data, and => 0775 for created directories. Default umask in lastest NGinx is 0022 => 0644 which gives read and write only to owner for created files, and => 0755 for created directories. So that could be some server config has changed and does not give enough permission to read existing sessions files. So you should checkup to.
  • umask in PHP can be dynamically changed, so if you changed it early in your code to umask(0666); and not changed it back to umask($oldUmask); before Laravel create the session files, then the newly created session file will have permission 0000 with no read access at all.

But I resolved and controlled all of this, but the permission problem persist sometimes. So if Laravel did not messed up with the file permissions just before accessing it, it can't be a Laravel bug for sure. Maybe the System itself create a strange behavior to limit the permission on some special cases that I cannot catch.

I give up on this cause this doesn't seem a Laravel coding error. Instead thanks to @laurencei I'm already changing to Redis for Staging to test before I apply it to production, this way I won't get anymore Sessions permission access.

Change your ENVIRONMENT Variables

Then I used this configuration in my LOCAL .env file :

APP_NAME=Megalobiz
...
SESSION_COOKIE=mbdck
SESSION_DOMAIN=localhost
...

and this in my STAGING .env file :

APP_NAME=Megalobiz
...
SESSION_COOKIE=mbsck
SESSION_DOMAIN=10X.24X.X.4X
...

APP_NAME doesn't have to be unique. But I heard some places that Laravel had issues with underscore SESSION_COOKIE, so instead of using default :

'cookie' => env(
        'SESSION_COOKIE',
        str_slug(env('APP_NAME', 'Megalobiz'), '_').'_session'
    ),

to produce a session cookie name megalobiz_session, I just add my own and UNIQUE session cookie name SESSION_COOKIE=mbsck from MegaloBiz Staging CooKie. This way the Browser won't get the same cookie name for your LOCAL, STAGING and PRODUCTION environment.

After that I don't know if it's necessary, but I put SESSION_DOMAIN as localhost, IP or domain name if Server is running through DNS.

Clear Cookies

AND FINALLY, after making those changes, *DO NOT FORGET TO CLEAR THE COOKIES on the LOCAL or STAGING or PRODUCTION if you changed the Environment variables because the Browser will keep the old Session Cookie name because it's not expired yet, megalobiz_session in my case.

And I get my new SESSION COOKIE name as mbsck. And I logged in the 1st time 💯

AVOID MAKING THIS IN PRODUCTION

BE CAREFUL NOT TO CHANGE THIS ON YOUR ALREADY RUNNING PRODUCTION SERVER because it will invalidate sessions for all other browsers (Users) that has the old session cookie name not expired and they won't know if they must clear that cookie in their browser. Instead clear your other cookies for other environment and keep your default config for production.

KeitelDOG commented 5 years ago

Can anyone reproduce this issue on a fresh Laravel installation?

@staudenmeir Easy now to reproduce if you want to investigate more, just run the same App twice, as Dev and Staging, or 2 devs, with the same SESSION_COOKIE name and one of them will fail cause I think the browser will send the incorrect Session value.

@zyglobe see if my answer above help you resolve it.

KeitelDOG commented 5 years ago

I'm still getting the same error on the other Server with Laravel 5.6, so I went to look for how Chrome Browser handle cookies on request. Chrome is more strict than other browsers. The answer to the StackOverflow Question Why does the session cookie work when serving from a domain but not when using an IP? explains a lot of important things. He mentioned an HTTP RFC 2019 Document where they state :

4.3.2  Rejecting Cookies

   To prevent possible security or privacy violations, a user agent
   rejects a cookie (shall not store its information) if any of the
   following is true:

   * The value for the Path attribute is not a prefix of the request-
     URI.

   * The value for the Domain attribute contains no embedded dots or
     does not start with a dot.

   * The value for the request-host does not domain-match the Domain
     attribute.

   * The request-host is a FQDN (not IP address) and has the form HD,
     where D is the value of the Domain attribute, and H is a string
     that contains one or more dots.

   Examples:

   * A Set-Cookie from request-host y.x.foo.com for Domain=.foo.com
     would be rejected, because H is y.x and contains a dot.

Kristol & Montulli          Standards Track                     [Page 7]

RFC 2109            HTTP State Management Mechanism        February 1997

   * A Set-Cookie from request-host x.foo.com for Domain=.foo.com would
     be accepted.

   * A Set-Cookie with Domain=.com or Domain=.com., will always be
     rejected, because there is no embedded dot.

   * A Set-Cookie with Domain=ajax.com will be rejected because the
     value for Domain does not begin with a dot.

4.3.3  Cookie Management

   If a user agent receives a Set-Cookie response header whose NAME is
   the same as a pre-existing cookie, and whose Domain and Path
   attribute values exactly (string) match those of a pre-existing
   cookie, the new cookie supersedes the old.  However, if the Set-
   Cookie has a value for Max-Age of zero, the (old and new) cookie is
   discarded.  Otherwise cookies accumulate until they expire (resources
   permitting), at which time they are discarded.

   ...

Those specs would tell browsers to ignore malformed or non-standard Set-Cookie format so that sometimes Chrome just won't send those cookies in the requests to your server.

KeitelDOG commented 5 years ago

Today I tested, confirmed and resolved the 419 error (that is not related to file permission) for the 4th times for that same month. Cause it also happen with Redis as the Session Driver.

What I can confirm is that happens :

And the conflict and error are resolved when :

I tried with IP, error, activate DNS, no error, deactivate DNS, error appears again.

Tested with my 2 Old VPS and 2 New VPS (staging + production) with IPs and 2 domain names.

diadal commented 5 years ago

@Xerotherm1c ware you able to fix this issue am facing same on MacOs Nginx only on production

KeitelDOG commented 5 years ago

@diadal, it happened to me even with Redis so it was not the file permission in my case. I'm using MacOS + Nginx for dev, Ubuntu 18.04 + Nginx for production. But a mixing of sessions when developing, staging and producing on the same browser. But what's strange is that once I use domain name with HTTPS (Certbot), this behavior stops completely. Also when it happens, even after updating configuration, browser seems to put something in cache that has the effect of creating 419 error for some hours, and suddenly BOOM it works.

diadal commented 5 years ago

I use Redis also file for session same issue MacOS + Nginx for dev & Production same issue all Safari & Chrome I clear all cache & cookies same no solution so far Laravel Framework 5.7.22

zyglobe commented 5 years ago

@KeitelDOG I'm also able to reproduce this on Ubuntu 18.04, redis, and the issue occurring with http, and resolved over https. So this lead me to my latest trial, playing with the session (cookie) config. After changing some of the cookie settings (from strict, http_only, and secure to lax). I was able to login successfully in a new private browser - my existing browser session still failed. So I tried manipulating the XSRF-Token cookie's strict/http-only settings (didn't work). I deleted the XSRF-Token, so a reload of the page renewed it. I STILL got the error. So the kicker here is... I deleted the SESSION cookie, reloaded the page, and it worked.

KeitelDOG commented 5 years ago

@diadal are you using a domain name with SSL for production? I never get it with Domain + SSL I think Laravel can handle this behavior better, cause I also get this error from other websites build with laravel.

diadal commented 5 years ago

@KeitelDOG yes full SSL for production still @zyglobe no luck is not working

KeitelDOG commented 5 years ago

@zyglobe you are right, and that is really strange. I wonder why it's suddenly happen with latest version of Laravel, I think they need to collaborate with highest Browsers and Cookies experts from Google or Mozilla to handle this behavior better.

I suspected when you're developing with Laravel, then your Browser expose you to conflicts with other Laravel websites sending cookies to it, maybe with same name or not.

Thanks for posting your experience results.

KeitelDOG commented 5 years ago

I don't know for sure if that really counts, but I decided to put different name for session cookie for each environment in .env file in case this would create conflicts with other cookie names :

I believe it could help, I read it in a StackOverflow answer once, so I'm using it just in case.

But at the end, I think you risk nothing for your users if they are not Laravel developers.

diadal commented 5 years ago

@KeitelDOG no luck

<?php

if (env('APP_ENV') === 'local') {

    $cookie = 'Test-developer';
}else{
    $cookie = 'Test_Prod';
}

return [

    /*
    |--------------------------------------------------------------------------
    | Default Session Driver
    |--------------------------------------------------------------------------
    |
    | This option controls the default session "driver" that will be used on
    | requests. By default, we will use the lightweight native driver but
    | you may specify any of the other wonderful drivers provided here.
    |
    | Supported: "file", "cookie", "database", "apc",
    |            "memcached", "redis", "array"
    |
    */

    'driver' => env('SESSION_DRIVER', 'redis'),

    /*
    |--------------------------------------------------------------------------
    | Session Lifetime
    |--------------------------------------------------------------------------
    |
    | Here you may specify the number of minutes that you wish the session
    | to be allowed to remain idle before it expires. If you want them
    | to immediately expire on the browser closing, set that option.
    |
    */

    'lifetime' => env('SESSION_LIFETIME', 20),

    'expire_on_close' => false,

    /*
    |--------------------------------------------------------------------------
    | Session Encryption
    |--------------------------------------------------------------------------
    |
    | This option allows you to easily specify that all of your session data
    | should be encrypted before it is stored. All encryption will be run
    | automatically by Laravel and you can use the Session like normal.
    |
    */

    'encrypt' => true,

    /*
    |--------------------------------------------------------------------------
    | Session File Location
    |--------------------------------------------------------------------------
    |
    | When using the native session driver, we need a location where session
    | files may be stored. A default has been set for you but a different
    | location may be specified. This is only needed for file sessions.
    |
    */

    'files' => storage_path('framework/sessions'),

    /*
    |--------------------------------------------------------------------------
    | Session Database Connection
    |--------------------------------------------------------------------------
    |
    | When using the "database" or "redis" session drivers, you may specify a
    | connection that should be used to manage these sessions. This should
    | correspond to a connection in your database configuration options.
    |
    */

    'connection' => null,

    /*
    |--------------------------------------------------------------------------
    | Session Database Table
    |--------------------------------------------------------------------------
    |
    | When using the "database" session driver, you may specify the table we
    | should use to manage the sessions. Of course, a sensible default is
    | provided for you; however, you are free to change this as needed.
    |
    */

    'table' => 'sessions',

    /*
    |--------------------------------------------------------------------------
    | Session Cache Store
    |--------------------------------------------------------------------------
    |
    | When using the "apc" or "memcached" session drivers, you may specify a
    | cache store that should be used for these sessions. This value must
    | correspond with one of the application's configured cache stores.
    |
    */

    'store' => null,

    /*
    |--------------------------------------------------------------------------
    | Session Sweeping Lottery
    |--------------------------------------------------------------------------
    |
    | Some session drivers must manually sweep their storage location to get
    | rid of old sessions from storage. Here are the chances that it will
    | happen on a given request. By default, the odds are 2 out of 100.
    |
    */

    'lottery' => [2, 100],

    /*
    |--------------------------------------------------------------------------
    | Session Cookie Name
    |--------------------------------------------------------------------------
    |
    | Here you may change the name of the cookie used to identify a session
    | instance by ID. The name specified here will get used every time a
    | new session cookie is created by the framework for every driver.
    |
    */

    'cookie' => env('SESSION_COOKIE',$cookie.'_session'),

    // 'cookie' => env(
    //     'SESSION_COOKIE',
    //     str_slug(env('APP_NAME', 'laravel'), '_').'_session'
    // ),

    /*
    |--------------------------------------------------------------------------
    | Session Cookie Path
    |--------------------------------------------------------------------------
    |
    | The session cookie path determines the path for which the cookie will
    | be regarded as available. Typically, this will be the root path of
    | your application but you are free to change this when necessary.
    |
    */

    'path' => '/',

    /*
    |--------------------------------------------------------------------------
    | Session Cookie Domain
    |--------------------------------------------------------------------------
    |
    | Here you may change the domain of the cookie used to identify a session
    | in your application. This will determine which domains the cookie is
    | available to in your application. A sensible default has been set.
    |
    */

    'domain' => env('SESSION_DOMAIN', 'test.com'),

    /*
    |--------------------------------------------------------------------------
    | HTTPS Only Cookies
    |--------------------------------------------------------------------------
    |
    | By setting this option to true, session cookies will only be sent back
    | to the server if the browser has a HTTPS connection. This will keep
    | the cookie from being sent to you if it can not be done securely.
    |
    */

    'secure' => env('SESSION_SECURE_COOKIE', true),

    /*
    |--------------------------------------------------------------------------
    | HTTP Access Only
    |--------------------------------------------------------------------------
    |
    | Setting this value to true will prevent JavaScript from accessing the
    | value of the cookie and the cookie will only be accessible through
    | the HTTP protocol. You are free to modify this option if needed.
    |
    */

    'http_only' => true,

    /*
    |--------------------------------------------------------------------------
    | Same-Site Cookies
    |--------------------------------------------------------------------------
    |
    | This option determines how your cookies behave when cross-site requests
    | take place, and can be used to mitigate CSRF attacks. By default, we
    | do not enable this as other CSRF protection services are in place.
    |
    | Supported: "lax", "strict"
    |
    */

    'same_site' => "strict",

];
fires3as0n commented 5 years ago

I was just fighting the same problem for a couple of hours, then I found that my .env file contains SESSION_LIFETIME=0 I've set it to 120, and cleared cache+cookies both on server and browser sides and now it's working. I don't know if it is the reason for everyone, but in my case it was, guys, check your .env file

driesvints commented 5 years ago

Hi everyone. If anyone's experiencing this please try one of the support channels listed above.