Too many CSRF tokens generated (PHP), how do I deal with them? - php

I run into a problem. Following OWASP cheatsheet, I implemented a one-time-use CSRF token system in PHP (basically copy&paste from OWASP). Each form or link (link that generate some action) will create its own CSRF token, once it's used, it will be deleted. Application is a website, so multiples tabs could be opened at the same time.
The problem is that each time it load a page, it create a new CSRF token (even if you just hit reload and not send the form). For example, in the admin panel, there's a list of the items, each item could be deleted with a link which have a CSRF token (same CSRF token for all links), but if you reload the page, a new csrf is generated.
At the end of the day, I ended with more un-used tokens than I wanted to. This is a problem in certain servers.
TLDR; I generate a token per request. I deleted all used token (except for ajax request tokens, that after an hour I delete then). My problem is with UNUSED tokens, at the end of the day, there're too many of them.

There is no (practical) way of knowing if your user will still use the token or not. Thus, you will have to automatically delete (and thus invalidate) them after x hours, for example by using a cronjob.
Just a suggestion: Are you sure that you really need one-time tokens? OWASP themselves say that it is not absolutely necessary for security: "In general, developers need only generate this token once for the current session. [...]) (https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet).
Keep in mind that one-time tokens also make tabbed browsing or using the browser history a huge pain.

Related

CSRF - Gracefully Fail Stale Sessions

We built a SaaS platform that allows users to create a message and share it across any advertising medium via an pre-determined alphanumeric code.
All of our forms use a CSRF token, but I noticed a problem. If a user is editing their message, then takes a break, and comes back to finish editing and hits save, they are faced with a 'CSRF mismatch' warning message. This is a critical problem, as they have to refresh the page, losing their changes, in order to regenerate a valid token.
What's happening is that the initial CSRF token is generated based on static params such as filename and php_uname(), but it is also being generated via session_id(). So if the session_id changes, a new CSRF token is generated and obviously does not match the old token resulting in the warning message.
It would be easy enough to disable CSRF protection on forms that require user authentication, but we have a few forms that do not require authentication, such as user login. Granted it's not a big deal to show the 'CSRF mismatch' on a user login, but it does look wonky to users who have no idea what that means, and end up going 'screw this' and leaving the site.
So I am looking for an alternative solution, to maintain some semblance of form protection, without generating a worthless warning message.
Two potential solutions...
1) Instead of generating a CSRF based off session_id, generate it based off some cookie value, this will allow graceful cross session handling.
2) Drop CSRF altogether. Instead, validate the REFERER of all form posts, and if they match the parent domain, then proceed. This will ensure other sites are not posting to our forms (the primary purpose of CSRF). We actually use this solution for our ajax requests.
We are leaning towards option #2 but wanted to check to see if there were any other graceful solutions to consider.
I ended up going with option #1 (Cookie solution), and treat the CSRF seeding just like a persistent login cookie. If the seed isn't available in current session, check cookie, if exists, populate current session. Seems to be working well and transcends stale sessions.

Secure CSRF protection without sessions or database?

i'm trying to implement secure CSRF protection to HTML login form,
i know the best way to implement CSRF protection is storing random csrf_key in a session,
but i want to add CSRF to my login & register forms... and i do not want to store many sessions for any anonymous unregistered users...
so i want to create the best secure posibble without using sessions or database, with only form hidden field /& a cookie, and after the login i will use sessions csrf protection.
my idea of secured user_storage only csrf:
csrf_token= AES(ip+useragent+timestamp+random_data, csrf_aes_site_key)
when csrf_aes_site_key is hard-coded in config file.
and after every login/register i will decrypt the AES string + velidate that the ip&ua is matching the request ip&ua, and timestamp is not too match ahead, let say 5 min (if csrf_timestamp + 18000>=current_ts), and random_data is just randomness (and make sure that the same user will not get the same csrf_token if requested multiple times in the same ts)...
so ... is it secure enough, is it good solution?
if not, any other suggestions to solve this dilemma?
thank!
EDIT:
the implementation i just created, and it's working fine, but is it good enough?
full example:
https://github.com/itaiarbel/aes_based_csrf_protection
issue 1:
user may take the csrf_token and submit to the form successfuly using the same token for the next 5min
bug? what do i care if the user submit many times? as long as it not csrf attack...
issue 2:
if the page is left open for 5min, the user will faild login,
(refrash login page automaticaly every 5 min? maby change it to 1h?)
can you spot any specific security risk with this implementation? or can i assume this is a secure way of doing CSRF protection?
The method with storing the CSRF token in cookie is quite widely used (AngularJS, Django) but it works a bit differently. The server sends the token in cookie, the client uses JavaScript to read the cookie and reflect the token in a HTTP header. The server should only verify the value from the HTTP header, even though the cookie will be sent automatically as well.
The actual cookie and header names are not important as soon as both JavaScript frontend and backend are aware of them.
This prevents CSRF because only JavaScript running from the authentic origin will be able to read the cookie (see detailed discussion on Wikipedia). The token can be a HMAC of the session cookie, which avoids the need to remember token state on the server side.
The main advantage is that this approach (unlike the one with token in form fields) works with single-page, JavaScript based applications where you don't generate the HTML on the server and can't really embed the CSRF token in their code.
It will work for most clients - but will fail horribly where the client is accessing your site via load balanced proxies (the client IP you see with change). A more correct solution would use the organization data from the whois record or the ASN number, but there's an expense in looking these up - depending on the volume of traffic simply using the forst 16 bits of the (IPV4) address is probably sufficient.
You may also run into problems depending on how much of the user agent you store (Google Chrome can update itself on the fly).
Looks highly insecure to me. Do not use this. Login forms and registration forms need full CSRF protection; using any kind of reduced protection is not acceptable.
Your setup seems easily attackable by anyone behind the same NAT (and thus sharing the IP address), which is very common in people's homes and even many workplaces. Here's an example scenario:
Malicious coworker (MC) registers a new account on the website
MC figures out victim's user-agent, which is quite easy to do
MC fetches a CSRF token from the website while sending the victim's user-agent
MC crafts a malicious website, containing:
A hidden login form that auto-submits on load
The CSRF token from above
A funny cat meme
MC sends link to victim saying "lol, look at this"
Victim enjoys the meme, but is now logged into the website without their knowledge
MC convinces victim to start using the website, generating data or metadata on the logged in account
MC can view all this data, since they have access to the same account
Steps 3 to 6 can easily happen within 5 minutes.

CSRF token collisions with multiple tabs

I built CSRF protection in my application, by simply generating a random token on every page load, putting it into session, and then binding the token to the <body> tag attribute like:
<body data-csrf-token="csrf_GeJf53caJD6Q5WzwAzfy">
Then on every form action or ajax request, I simply grab the token from the body tag and send it along.
This works great, except for a huge issue. Users are opening multiple tabs of the application, and I am seeing token collisions. For example, a user loads the first page and it generates a token, then they switch tabs, load another page, which generates a new token. Finally they switch back to the first page and submit a format action. This results in an invalid CSRF token error.
What is the best way to re-architect this to prevent collisions with multiple tabs, while keeping it as secure as possible.
Is simply generating a single token upon login the correct solution, instead of generating a new token on every page load?
Assuming that your app is secured with SSL, then there is really no value created by generating new tokens on every page load. It doesn't stop an attacker who has exploited an XSS vulnerability – they'd have access to the freshly generated token anyway.
Remember what a CSRF token defends against: a malicious third-party page blindly trying to post data to your app in hopes that the user is logged in. In this kind of attack, the attacker would never have access to the CSRF token, so changing it frequently does no good.
Do not waste time and resources keeping track of multiple tokens per session. Just generate one at the start and be done.
You could use a single token upon login. As #Josh3736 points out, this works just fine.
If you really want to have one token per page, you could store an array of valid tokens in $_SESSION. You would then expire individual tokens as they are used. You could also optionally expire them after some timeout period, but that is only meaningful if the timeout is shorter than your session timeouts. But, again, what are you really accomplishing with this? A single token is perfectly fine for CSRF purposes.
I've run into this exact problem, on page load I was generating a CSRF token like this:
$_SESSION["token"] = bin2hex(random_bytes(32));
Multiple tabs was causing CSRF mismatches, so I changed to this:
if (!isset($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
Server side I do this (watered down version):
$csrf = preg_replace("/[^a-zA-Z0-9]+/", "", $_POST["token"]);
if ($csrf !== $_SESSION["token"]) {
// Give an error
die ("No valid CSRF token provided");
}
This may protect against XSS attacks, but it wouldn't stop someone going to the page, getting the PHP session ID (from headers) and the CSRF token and using a tool like Postman or WGET to put together hack API posts, etc.
That may be why this question exists... understanding the scope of what the CSRF token is for protecting against.

CSRF Token Multiple tab issue

I am implementing CSRF token in my website on every post method.
But when i am accessing my webpages in different tabs then token gets change on both pages and token mismatches.
My token is stored in DOM and i am matching token using SESSION.
How to solve this.?
i change the token on every successful request
Yeah this is why we don't invalidate the token on every successful request. That doesn't just break multi-tab browsing, it also means you can't do stuff like hit the back button then submit.
“Invalidate token on every request” is the kind of bogus security recommendation you get from pentest reports where the tester hasn't found much that's really vulnerable. It's a trade-off as always whether you do, but the usability downside almost always outweighs the minimal security benefit.
You only really need to invalidate the CSRF token (along with the session token) on a privilege level change, most notably on login. This mitigates session fixation attacks, by preventing an attacker who knows the session and CSRF tokens prior to login from exploiting those tokens after you've logged in.
You can achieve this easily:
In the server side, store the CSRF tokens in session like this:
$_SESSION['csrf_tokens']['form1'] = //code to generate csrf token
While validating the token on form submit, you can check,
$_SESSION['csrf_tokens']['form1'] === $_POST['csrf_token']
Please post an example code, unless you are using ajax (which I wouldn't recommend for CSRF tokens the code shouldn't change in both tabs if you open a new tab). Also, I disagree with bobince, you are doing the right thing to implement this measure as once you have the logic in place you can easily and effortlessly use it in all your forms. The best way to implement this is to just have each token expire after a certain amount of time.
bobince: CSRF tokes are used to prevent CSRF attacks not session fixation attacks, both are different the former prevents scripts from executing actions on behalf of the user whereas the latter is an attack in which a malicious user impersonates a normal user by guessing or stealing their session id.
Generate two values – one random key (f.e. via uniqid), and a random token.
You generate both every time a form is rendered – and put them both into hidden fields. And you save the token into the session using the random key. Then when the form data is received, you check if the token send is in the session under the key send. (And if so, you delete the entry with this key after processing the form of course.)
Anything else (f.e. expiration time of tokens, binding of tokens to a certain form type out of several) you implement the same as you would before.
is unnecessarily and unsafe like this why you dont create a token based on session with openssl_random_pseudo_bytes() ,which will produce a safe token, and check if is correct or not or you can use also to expire after 2-5 min.also you can check on owasp about tokens on dom,can be easy spoffed !!!

For securing forms, when do I issue the token?

So, I have a form, to make it a little more secure and potentially help prevent CSRF attacks I want to add a random token value in a hidden field that value is also stored server side in my session data.
When should I issue a new token? Per form? Per page load where there is any form? Per session? I can render it invalid as soon as a form is successfully submitted but I'm wondering when to generate one.
I ask as if I issue it per form or per page do I not risk the chance of a duplicate token value overwriting the existing (valid) token if a user opens a separate window but submitting the first form (with the now overwritten value)?
The simplest way to prevent concurrency issues is to generate it only once per login. The chance of an attacker "guessing" your CSRF is about the same chance (or lower) as them stealing your PHPSESSID. You could also regenerate it whenever the user's access level changes, such as after they change their password or something.
If you want to be really thorough, you can generate and store an array of tokens, one for each form on the website. But if they can steal a CSRF token, they might as well have just stolen a session ID and done some real damage.
If you use one Token per session,
then you need a technique to check
duplicate submitted forms (when user
refreshes a page or click back
button).
If use it per form then if user opens
multiple pages then only the last
form will work.
I would prefer using array of
generated tokens per session, and
when a token is submitted it should be
removed. I read that this approach might be a
concern if you have high volume
traffic website.
I am not sure if you read this article before, but I think it is great resource about CSRF security:
http://shiflett.org/articles/cross-site-request-forgeries

Categories