This is the third part of our blog series "Things that security auditors will nag about and why you shouldn't ignore them". In these blogs, Nixu's security consultants explain issues that often come up when assessing the security of web applications, platforms and other computer systems.
Missing or incomplete protection for cross-site request forgery (CSRF) is one of the high-level findings in many web application security audits.
Despite the fact that the web is full of explanations of this attack and OWASP has published an excellent CSRF Prevention Cheat Sheet, it still seems to be a bit unclear that somehow CSRF actually works, what is the importance of protecting against it, and how do you actually implement CSRF protection the right way. You get to see a lot of makeshift solutions that do not actually work.
Cross-site... wait, what?
Cross-site request forgery, or CSRF, is an attack where the user is forced or tricked to performing unauthorized actions in a web application where they are currently authenticated. It is not about stealing information, it's about doing something on the user's behalf without them knowing.
All we multitaskers know how it goes: First, you login to a web store to buy some cool gadgets and find the item you are looking for. Next, you get an urge to compare prices and google for alternatives in another browser tab. You click open some of the pages but nah, what they offer is not quite as good. Then, you suddenly feel you must go to Facebook and you spend good 15 minutes viewing the funny animal videos your friends are posting, reading a couple blog posts that someone linked to and getting annoyed about pop-up ads. Then you remember that you were supposed to buy something, go back to the store tab, add the item and a couple other things to the shopping cart. You're in a bit of a hurry, so click next-next-next on the rest of the order process. Your addressing information is already saved from your previous purchase from last month and it's still valid, so no reason to recheck that. Done.
What happened? A very much simplified illustration of this process is in the picture. The imaginary web store had no CSRF protections in place. While logged in to the web store and browsing other pages, the poor user accidentally stumbled to a page that has a hidden form. The form contained a request that modifies the delivery address which is an optional step in the ordering process. This works, because browsers always add all cookies belonging to the target domain in the request. Now the victim's browser processes the hidden form, adds the cookies in the request, and sends it to the web store. So the payment was done with your credit card but the delivery address and phone number belongs to someone else.
The actual impact and severity of cross-site request forgery exploits depends a lot on the application functionality. Does the vulnerable system transfer funds? Modify user information? Handle backups? Execute commands on an IoT device? The user privileges have an impact too: it's can be messier if the victim has an administrative account.
For example, a recent CSRF vulnerability in Pulse Connect Secure allows the attacker to use the diagnostics features of the application to run ping, traceroute or port probes to arbitrary IP addresses if an administrator can be tricked to visit a malicious page.
And how to get someone to visit a malicious page? The same thing that works for phishing, works here too. A convincing, authentic looking email, pressing for security matters, urgency or offers-that-you-can't-refuse, and... Voilà!
The presence of other vulnerabilities has an impact, too. An example is a bundle of vulnerabilities in an EE 4GEE wireless router: a CSRF vulnerability exists, allowing device reboot and configuration file upload if the users can be lured to visit a malicious URL. Bad enough as it is. But the device also contains a cross-site scripting (XSS) vulnerability in its SMS message handling functionality in the admin panel. So you could send a SMS with a XSS payload to the device (providing that you know the number, but hey, you can iterate phone numbers) and if the admin clicks on the message, they will get directed to a URL that will exploit the CSRF vulnerability. Other interesting chaining examples are presented in the advisory.
Remember to not get confused with cross-site scripting despite the somewhat similar name. Despite the previous example, they do not require each other to work. XSS protections do not prevent CSRF or vice versa. However, any XSS vulnerability will make your CSRF protections useless, as the attacker could now steal or alter the token, cookie or header values that the server uses for verifying the request origin.
I find your lack of anti-CSRF tokens disturbing
The usual approach that effectively protects from cross-site request forgery is using anti-CSRF tokens. The basic idea is that the server issues the user a secret and unique token that must be present in the header or body of the next request from that user. With anti-CSRF tokens in place, the steps 2-6 of the previous scenario look very different as illustrated below. When logging in to the web store, the server issues an anti-CSRF token to the user. The victim's browser will still process the malicious form, add domain and session cookies and send the forged request, but it cannot add the token. Therefore, the server rejects the request.
The most obvious problem is of course the total lack of anti-CSRF tokens. The fun part is that this makes the web application tester's life very easy, as you can replay messages as much you want in search of other vulnerabilities. However, this usually is a high-severity finding unless the web application has very limited functionality.
Another issue is insufficient validation of the anti-CSRF tokens. To be clear: you need to check the validity of the token content, not only the existence of it!
Sometimes, application frameworks bring along an assorted selection of synchronizer tokens for keeping user interface states and associating requests to sessions. While these tokens might accidentally protect from the simplest CSRF attack attempts, they may provide only weak protection. For example, the hidden field javax.faces.ViewState in JavaServer Faces (JSF) looks like an anti-CSRF token. However, as this is not this token's primary purpose, in JSF 1.2 the token value is static throughout the session and is also relatively short and predictable. In JSF 2.0 and 2.2 the situation has improved. Another example is the ASP.NET ViewState, which does not protect you unless you use the ViewStateUserKey property.
The newest OWASP top 10 2017 dropped cross-site request forgery from the list, as many frameworks include built-in anti-CSRF functionality. It's best to use them instead of creating your own. It will probably also get updates, in case a flaw is found. Of course, you can implement additional sanity and origin checks. They won't harm you. Some security configurations also protect from other issues.
What does not work as a prevention?
Sometimes, there are some misconceptions about what works as a countermeasure for cross-site request forgery. There is an excellent recent article about CSRF misconceptions and pitfalls by NCC Group - go ahead and read it!
As a short summary, the following methods do not work, sorry!
- Only accepting POST requests.
- Using only cookies for storing the token.
- Multi-step transactions.
- Using a static token value throughout the session.
All cookies related to the session and domain, whether they have the secure and HttpOnly flags on or not, are sent in every request to server regardless of the origin of the request. Storing the anti-CSRF token only in a cookie won't work, since it's just another cookie that will be sent along the attacker's forged request.
There is an interesting exception to this called same-site cookies: Setting the attribute SameSite=strict disables the browser from sending the cookie unless it was a direct request from the user of that application. Unfortunately, same-site cookies are not currently supported by all major browsers.
User interface design has a slight impact in a sense that it is much harder to craft a CSRF exploit if the application has a wizard-like user interface or requires multiple steps to complete the transaction. However, multi-step transactions as such do not protect you if the attacker knows or can predict the sequence.
Static token value throughout the session is not a good idea, either. While it is an actual protection mechanism unlike the other three, there may be other vulnerabilities present that allow guessing or leaking the tokens. As an example, python-fedora had an open redirect vulnerability which results in CSRF tokens leaking outside. If your algorithm for token generation is not random enough, the tokens could also be predicted. Especially with long session validity timeouts static anti-CSRF tokens can be a real issue.
Related issues - check these too!
Some other configuration flaws may make it easier to perform on cross-site request forgery on your website, so check also for these issues:
- GET is used for committing changes.
- GET and POST can be used interchangeably.
- BREACH attack.
It is not a good idea to stuff all your parameters in GET - you should use POST instead. The main reason for this is that all the parameters in a GET request will be shown in the URL and sensitive data can be exposed because the URLs are stored or seen in browsing history, logs, Referer headers, proxies or other network appliances, or someone intercepting the traffic. Now if you had a CSRF token in the URL, it would be leaked as well. The other reason is that it is simple to craft a GET request, hide it an email link and use a little social engineering to get user to click on it.
Okay, so maybe you are following the best practice to use POST requests for submitting changes. Have you tried what happens if you change the request method from POST to GET? By the way, in case you didn't know, the popular wrb application testing tool Burp Suite has a nice feature called Change request method that will do this and mangle all the parameters for you. If your application accepts that, exploiting CSRF might become a bit easier.
The BREACH attack is a category of vulnerabilities and not specific to any particular software, TLS version or cipher suite. If your server is using HTTP gzip compression, the server responses contain secrets (in this case, especially anti-CSRF tokens), and the application reflects user-input in HTTP response bodies, you might be vulnerable to BREACH. This means, that the attacker could be able to guess the secret one character at a time. So if you have HTTP gzip compression on and the CSRF token is not unique per request, it could be guessed! There's an interesting article by Facebook on what they did to prevent potential BREACH attacks.
Do not take any shortcuts. The implications of cross-site request forgery attacks can be quite nasty.