The Basic Idea
An attacker crafts code to create an HTTP request that, if it were run in the browser of a logged in user, would do something dangerous such as transfer money or delete data. The attacker then finds a way—typically through email—to get the malicious code into a victim’s browser. For example, an attacker might craft code that would change the shipping address on a shopping site. The attacker spams a phishing email to hundreds of thousands of users. If any of the users who open the email happen to be logged in to the shopping site, the attack might redirect their shipments to an address the attacker chose.
The attack works because the victim is already logged in and so the browser already has an authenticated session cookie. When the attacker’s email executes its request, the browser automatically appends to the request all the cookies it has for the shopping site, including the session cookie. The code runs in the context of a user’s legitimate session and performs actions the user does not intend.
Why CSRF is Bad
A severe CSRF vulnerability can produce devastating consequences such as fraudulent financial transactions and account takeover. CSRF vulnerabilities have been found on major sites including Netflix, YouTube, and the banking web application ING Direct. Facebook once paid a bug bounty of $25,000 for a severe CSRF finding.
The configuration web pages built into home routers can be vulnerable to CSRF. A 2008 attack against ADSL routers, for example, changed the router’s entry for a leading bank, causing any subsequent traffic from that router to the bank to pass through the attacker’s server. A search in the National Vulnerability Database shows that even in 2021 some router manufacturers still have not learned the lesson.
In 2017 OWASP dropped CSRF from their Top Ten web vulnerabilities list. By that time most major development platforms included CSRF defenses in their frameworks. Nevertheless, as recently as 2020 HackerOne still included CSRF as one of the most lucrative vulnerabilities for pen testers in bug bounty programs.
Conditions for Attack
A CSRF attack may be possible whenever the following conditions are present:
A state-changing request can be performed over HTTP.
The syntax of the state-changing request is predictable.
The browser automatically includes session info in requests.
Most web forms are built to change the state of something somewhere, so most interactive websites meet the first condition.
The syntax of the state-changing request must be predictable in order for the attacker to forge it. A CSRF attacker blindly pushes code into the user’s browser without being able to see what the user is doing. Typically, the malicious request posts a form, and the attacker must know what name/value pairs to post for the server to accept it without errors.
The third condition has to do with sessions. To be vulnerable, a system’s session-handling mechanism must automatically include the user’s authentication tokens with each request. The tokens may be certificates or HTTP basic authentication credentials, but most often they are session cookies. The browser always appends to any request all the cookies it has for the target domain, including the session cookie.
Attack Delivery Mechanisms
Having once identified a vulnerable system meeting all three conditions, the attacker must then find a way to deliver malicious code to the user’s browser. Concealing the attack code directly in an email may work if the user reads email in a browser. Or the email may contain a link to a site owned by the attacker who has hidden the code in a page on the malicious server. Either way, if the user opens the email (in the first case) or clicks a link in the email (the second case) the attack code reaches the user’s browser—where it may succeed only if the user currently has a valid session open at the target website.
A POST Attack
Suppose an attacker observes a website that lets users send money to others using this (simplified) form:
<form method="post" action="https://bankapp.com/transfer"> How much do you want to send? <input name="amount" /> To whom do you want to send it? <input name="receiver" /> <input type="submit" value="Send" /> </form>
The attacker opens an account on the BankApp site with the user name @evil. The attacker composes two separate phishing emails. The first pretends to come from BankApp and says “Alert! Potential fraud detected in your BankApp account.” The second says “You may have won $1,000.” The purpose of the first email is to encourage users to log in to BankApp. The second email contains this markup:
<form method="post" action="https://bankapp.com/transfer"> <input name="amount" type="hidden" value="1000" /> <input name="recipient" type="hidden" value="@evil" /> </form> <script>document.forms.submit();</script>
The user doesn’t even have to click on anything. This second email submits its form and executes the attack as soon as it renders in the user’s browser. The attacker spams 100,000 users with those two emails. Some of them probably have BankApp accounts. A few of them will open the emails, and from each of those the attacker gets $1000.
Any state change that the server exposes through HTTP could be subject to a forged request. CSRF attacks can change passwords, make social media posts, delete accounts, configure routers, and many other dangerous or destructive operations.
Other HTTP Verbs
The BankApp example imagines that you have to POST a form to transfer money. Not all web endpoints are written that way. If the BankApp site allowed transferring money with a GET request, then the attacker’s email might simply do this:
By the time the user sees a broken image tag in the phishing email, the $1000 will be gone.
Other HTTP verbs that change state can also be exploited. Only PATCH, POST, PUT, and DELETE should allow state changes, but GET, HEAD, and OPTIONS can be vulnerable if they are allowed to make changes too.
Do not ignore CSRF warnings on Login pages. At login, even though the user has not yet authenticated, CSRF attacks can still be a threat—though the method and the risks differ from authenticated CSRF. In Login CSRF attacks, the attacker forges a request that logs the user into a site using the attacker’s login credentials. Victims, believing themselves to have logged in to their own accounts, may leave behind information that the attacker can use. Login CSRF attacks against Google and Yahoo could expose the victim’s search history to the attacker. A login attack against a site like PayPal might lure the user to provide credit card information that the attacker could then log in and use.
CSRF attacks rely on being able to predict the contents of, and so forge, a browser request. The most robust defense against CSRF is to make the contents of each request unpredictable. Token-based defenses rely on a randomly generated value to do just that.
To defend itself, a server adds a hidden field to each form, assigning the field a different random value for each session (or for each request.) The server confirms that each subsequent request has this field and that its value matches the one assigned for the session. This is called the synchronizer token pattern.
A synchronizer token requires the server to store random session values server side. If you don’t want to keep state on the server, you can instead encrypt the token value using a private key known only on the server. The encrypted value should be a time stamp. Validate subsequent requests by confirming that each contains the hidden field, that you can decrypt the field value, that the decrypted value is a timestamp, and that the timestamp is recent (the token has not expired.)
Encrypted tokens are one stateless CSRF defense, and double-submitted cookies are another. In this defense the random token value is saved in a cookie on the client. Every subsequent request must include the same value in a hidden field. On each request the server confirms that the hidden field is present and that its value matches that in the cookie.
For this defense to be reliable you must use https (which is recommended in any case) and ensure that your subdomains are also secure.
Anti-CSRF Tokens in AJAX
AJAX calls take place in the context of a cookie-based browser session and can also be vulnerable to CSRF. AJAX calls generally do not post forms where you can place a hidden field. Instead, return the token to the server in a custom AJAX header.
var xhr = new XMLHttpRequest(); xhr.setRequestHeader(tokenname, tokenvalue);
tokenvalue with the name and value of the hidden random token field in the page that generates the AJAX request.
Many programming frameworks such as Spring, Django, .NET and AngularJS come with built-in implementations of token-based CSRF defenses that generate cryptographically strong random token values and validate them on each request. Addons are available for some other frameworks: OWASP CSRFGuard for Java, for example, and the CSRFProtector Project for PHP. Implementing token-based defenses correctly can be tricky, so don’t build your own if someone has already made a reliable implementation for you.
Token-based defenses work best, but a defense-in-depth approach requires considering additional measures to impede possible CSRF attacks.
SameSite Cookie Attribute
SameSite is a cookie attribute like Secure, HTTPOnly, and Expires. The SameSite attribute lets site designers decide when the browser should include cookies on cross-site requests. The value of SameSite may be Strict or Lax, and either value will block at least some CSRF requests. This is a useful defense but not sufficient by itself because not all browsers support it (most do), and it can be bypassed.
To confirm that a request is valid, examine the HTTP headers to confirm that the origin and target values match. To determine the origin of a request, examine the Origin or Referer headers. Compare that to where the request is going: the target origin. These values cannot be forged. Only the browser is allowed to set them.
This defense has the advantage of identifying cross-site requests even before authentication. But a small percentage of your web traffic may, for legitimate reasons, omit the request origin, and determining the target origin is non-trivial if your application is behind a proxy.
Sometimes—particularly for high-risk transactions—involving the user directly is a good mitigation for CSRF and other forged requests. Requiring the user to supply a password or a one-time token, or to pass a CAPTCHA, can provide effective protection.
These tips will help you avoid common problems when defending against CSRF.
Token-based defenses can be penetrated if they are not implemented correctly. In order to ensure that the random token value cannot be predicted, it should be created with a cryptographically strong random number generator. Also, be careful with the logic that matches the value generated with the value received in a request. An error in the validation logic allowed a CSRF attack against GlassDoor. Under certain circumstances in GlassDoor’s implementation, a failed check would throw an exception, but the exception was merely logged and the code proceeded as though validation had succeeded.
Also, cross-site scripting vulnerabilities (XSS) can defeat any CSRF protection. Even a perfectly implemented token-based CSRF defense provides little protection if the attacker can retrieve tokens from the user’s browser. Solid XSS defenses are a prerequisite for CSRF defenses.
These measures are sometimes considered as mitigations but do not work:
Dividing the operation that changes server state across several HTTP requests does not help if the attacker can still predict each step of the transaction.
Creating a secret cookie that the attacker cannot predict does not help because the browser always sends all the relevant cookies back on every request. The attacker doesn’t need to guess the secret cookie.
Merely encrypting web traffic does nothing to stop CSRF attacks. It is a necessary step, however, for some other mitigations (such as a double-submitted cookie) and desirable in all cases to make other measures more reliable.
You can take session cookies out of the equation by modifying the URL while the page is loading, but doing so exposes the session ID on the URL, and that increases the risk of an attacker capturing the session ID.
CSRF vs XSS
Cross-site scripting (XSS) vulnerabilities share some of the characteristics of CSRF vulnerabilities. Both aim to run malicious code in the context of a victim’s legitimate web session. XSS, however, aims to inject malicious code directly into a vulnerable page, where CSRF typically relies on social engineering (such as phishing emails) to put malicious code in on an unrelated page in the victim’s browser. XSS depends on flaws in the targeted website that allow injecting malicious code that the server will deliver to the victim as part of its own web page.
The differences are substantial. Malicious XSS script has direct access to a page in the targeted site. In fact, an XSS vulnerability can expose to the attacker everything on the page, including any anti-CSRF token value. Perfect CSRF defenses provide little protection to a site that is vulnerable to XSS.
Testing for CSRF
Vulnerability testing is a fundamental piece of any security program. Scanning tools are capable of finding many CSRF (and XSS) vulnerabilities. Given the potential severity of such vulnerabilities, anyone with a web application should test for them systematically—preferably by incorporating automatic scans as part of the CI/CD pipeline. Vulnerability testing is required to comply with best practices and standards such as NIST SP 800-53 and ISO 27001. High-risk applications should additionally include manual penetration testing in their security program.
We built StackHawk to help engineering teams find and fix security bugs like CSRF, XSS and more, but whatever tool you choose, automated testing is key to ensuring a safe codebase. Getting started is easy – sign up for a free account today.