Hi there, I've recently implemented CSRF to an application by setting csrf_autoload = true in the config and ensuring the CSRF token is submitted as a hidden input. For AJAX requests, we're calling Security::js_fetch_token() and posting this value as an input with all POST requests.
All of the above seems to be working as expected, and a security exception is thrown if the token does not match, but we've ran into an issue in that we seem to be able to re-submit requests successfully using previous tokens. If for example, a datatable makes 3 separate post requests for page 1, 2 and 3, each one of these requests use a different token and the data is successfully returned. If I was to then resubmit my request for say page 1 again by resending the exaxct same request via the Network tab in Developer Tools on my browser, the data is successfully returned where as I would expect an exception to be thrown as (I believe) the CSRF token should have rotated after each time it was checked successfully.
From what I can see, it seems to be checking if the value of my input posted as fuel_csrf_token matches the value of the fuel_csrf_token cookie in my request, and because both of those values match in my previous request then the data is successfully returned.
Are you able to shed any light on the way this works, as I wouldn't have expected this to be normal behaviour and I feel like there's something else I'm missing.
The token only exists as a cookie value. This cookie is send to the server with your request, and server side, the value of the cookie is compared with the value in the POST data.
If your ajax request doesn't update the cookie when it receives the response, the old token remains valid until the browser does a new page request (which will always send the cookie and you'll get a new one back.
The js_fetch_token() function fetches the token from the cookie, so on sending the request, the cookie value and the input value are the same on your ajax requests to.
So, the token in the cookie is renewed whenever you receive a new cookie from the server. When that is, depends on your client implementation.
Note that it can actually be dangerous to rotate the token on ajax requests, due to the async and parallel nature of those requests.
Say you fire two requests in succession. The first one finishes quickly, and rotates the token, upon which your client gets an updated cookie. If that cookie arrives between you runing js_fetch_token() and doing the actual request, the input would contain a different token than the cookie, and the ajax request would fail.
We're adding the CSRF token to all ajax post requests using js_fetch_token() and an ajaxPrefilter. I can see the token updating on each request if I look in the Storage -> Cookies tab in my browser, but the issue is that despite the token being rotated several requests ago, any of my previous requests can be submitted seemingly as long as the cookie request matches the input post.
I've edited my request and changed the fuel_csrf_token request cookie to "blah", and my fuel_csrf_token input post to "blah" and resumitted my request, and this successfully returns data.
From what I can see, as long as the value in the cookie matches the input post then the data is successfully returned, but if I can just set this to anything I like in my request and ensure that it matches my post data then what would be preventing an attacker from simply sending a cookie with their request that matches the fuel_csrf_token input that's posted?
Thank you in advance, I appreciate your help with this.
Yes, this is how the CSRF tokens work, there is a token in the data and a token in the cookie, and they must match. There isn't any more too it.
It is a mechanism called the "double submit" defense, which was the mechanism of choice at the time, which also works when there are no sessions active, and/or there is no server side session storage (which is possible in a Fuel app).
I agree that it isn't watertight, in that it does prevent CSRF, but it doesn't prevent a hacker sending a direct POST request. This should be prevented by proper authentication and authorisation.
With the current 1.9/dev codebase the SameSite feature of a cookie is supported, I think this should be used for CSRF token cookies as well when enabled. I have to check that.