— This post is part of a series of blog posts about all kinds of Security topics for Developers —
Imagine the following: You set up your blog a while ago. Everything is running smoothly. You add a few plugins here and there to make managing it easier, and you are using a custom theme so it looks enticing to new visitors.
One day, on a routine check of the comments you received, you decide to investigate one comment a bit more closely since it contains a link. You don’t want to end up supporting spam messages, so you decide to click the link and see what it’s all about.
Strangely, instead of seeing another site, you are back on your blog with some weird error message. You don’t think too much of it. Click the link again, and this time you end up on an innocent-looking website. All seemingly fine, you approve the comment.
Days later, you start receiving angry e-mails about a hidden crypto-mining script on your blog that uses your visitors’ CPU (and therefore cell phone battery!) without their consent. Or maybe even worse: A script that tries to install malware on your visitors’ devices.
You have been a victim of CSRF, or a Cross-Site Request Forgery attack. A malicious visitor found that you were using a vulnerable plugin on your blog. Instead of disclosing the vulnerability, they abused it for their personal gain.
The link you clicked on led to a page specially prepared to contain a form that was submitted as soon as you entered that page. It submitted data instructing the plugin to include the attacker’s malicious script along with the analytics scripts you were already using. The plugin’s code trusted you since you were logged in to the blog and had the right to change that setting.
To avoid suspicion, the attacker made sure that the second time around the page would just load normally, but the damage was already done.
This was just a hypothetical example for such an attack, but a search for CSRF vulnerabilities shows all kinds of similar attacks happening to real websites. These security holes allowed attackers to do everything, from just posting irritating messages on message boards, up to transferring large sums of money away from the victim’s bank account to the attacker.
Even though these kinds of CSRF attacks are well-known by now, and many web application frameworks automatically take care of them, they remain a threat. The common thread binding them together is the abuse of the trust an application has in your browser (most of the times an ongoing session using cookies), which is used to make it perform actions the user never consented to.
So, any way to make your browser perform a forged request against the vulnerable application can in theory be abused to perform such an attack. This could be as simple as forwarding you to a URL that deletes data (GET requests that perform unsafe methods), or a more elaborate setup that gets you to submit a hidden form.
The essence of the vulnerability is that the required parameters for a certain request can be guessed or are known beforehand. For example, requests to elevate the status of a user are sent to https://example.com/admin/promote_user, which simply takes a few parameters, like the id of the user that should be promoted to a higher status (‘user_id=3309b7a4-e688-4cc9-9c17-0415a180983f’). Without taking possible CSRF attacks into account, the only protection this request has is the session associated with the logged in administrator. Therefore, if an attacker manages to get an administrator to click a prepared link or submit the request in any other way, they can get them to perform the action to promote the attacker’s account to an administrator itself.
URLs with side effects
In general, all URLs with side effects can be vulnerable to CSRF attacks. While POST requests to these URLs require a form to be submitted, or an XMLHttpRequest to be sent (both things which can be more easily mitigated), even simple GET requests might be problematic.
As an example, many e-commerce sites that use affiliate programs to pay out a commission for sales to visitors that came recommended from other sites fell victim to this, even including Amazon.
Accomplishing an attack on Amazon customers was simple, and only required posting an image on a popular forum or comments section. The image’s source was the attacker’s Amazon affiliate link. While the image would most likely be broken, as the link points to an HTML page, the visitor would still receive the cookie marking them as being referred by the attacker. The victim’s next Amazon shopping trip would yield the attacker a nice commission without them really earning it.
This practice has even earned a name of its own – cookie stuffing.
How to protect against forged requests
Ever since these vulnerabilities have been disclosed, browser vendors have attempted to mitigate them. One of these measures, the “SameSite” flag for cookies, was discussed in one of our last blog posts.
However, not all browsers support this flag at the time of writing; While it is not a bad idea to use the flag anyways, you need to take other measures to protect your users from this vulnerability.
Forms are the most common target of CSRF attacks, as they can cross domain boundaries easily, and are often connected to unsafe methods. The most efficient protection to be employed here is a so-called synchronizer token that gets sent along with the form in a hidden field. The token is created on the server and stored along in the session of the user, then inserted in the form like this:
<form action=”…” method=”POST”>
[… form fields]
<input type=”hidden” name=”_csrf” value=”43722b54-6f21-4ee5-9108-e97bf82b10c8”>
Once the form has been submitted, the application can validate the token. If it is missing or incorrect, the request will not be processed. An attacker who is unable to guess this randomly generated synchronizer token is no longer able to perform any forged requests. Many frameworks, like Spring Security, already include such a protection by default.
Securing GET requests to URLs
Following best practices, GET requests shouldn’t perform any actions that produce side effects. This is for good reason as they are the hardest to protect and the easiest to exploit.
The attacker can include an Iframe loading a URL, or simply forward the victim’s browser to it. All you can do is check the Referer or Origin HTTP headers, which might be unreliable.
To avoid having to append a token as a parameter (it might end up in logs while it should be kept secret), any URLs that perform unsafe methods when called via GET should be converted into POST requests using a form so that they can be protected as described above.
Protecting against CSRF using XMLHttpRequests
For REST APIs, it is crucial to set the CORS Headers correctly so that your application is protected by the same-origin security policy of the browsers. This is especially true if you rely on cookies stored in the browser and no extra access token is passed along.
Besides the possible security holes already discussed, CSRF vulnerabilities also may enable malicious users to attack you in ways you might not directly think of.
For example, Intranet applications that may have fewer security measures implemented, because they can’t be reached externally, could be attacked through a manipulated external website. This might be especially dangerous for Internet routers or similar devices that can be reached via fixed and well-known IP addresses.
Another infrequently considered attack vector is the ‘login CSRF’, where an attacker directs your browser to log in with credentials known to them. Even Google or Yahoo were vulnerable to this at one point. This counterintuitive attack allows an attacker to see what you searched for after being logged in to their account, uploaded files, and any other data collected by the compromised service while the target browses unaware.
Regardless of whether you think a malicious party could gain from exploiting it or not, securing any form or endpoint in your application should be standard practice. Working with security in mind reduces the threat surface of any application, even with attacks you may not have considered as a developer.