CSRF vulnerabilities: How to exploit and how to defend
The past few weeks I implemented an API that relies on Cookies for authentication. When working with Cookies you should always be extremely careful not to introduce CSRF vulnerabilities.
Usually, I build APIs using a specific
Authentication HTTP header which completely removes the risk of CSRF (more on that below). So, why should you be careful when building Websites and APIs which rely on Cookies for authentication? How to attack CSRF vulnerabilities? And how to defend?
Let's find out!
This post contains excerpts of my book Black Hat Rust
What are CSRF vulnerabilities?
A Cross-Site Request Forgery is a vulnerability that allows an attacker to force a user to execute unwanted actions.
When Cookies are used for authentication, they are automatically sent for any request to the domain they are set for, as opposed to the
Authentication header, which should be set client-side for each request.
There are two families of CSRF vulnerabilities:
The first one is by using forms. Imagine a scenario where an application allows users to send money. Something like:
$from = $_COOKIE["user"]; $to = $_POST["to"]; $amount = $_POST["amount"]; transfer_money($from, $to, $amount)
If I host on my website
malicious.com a form like:
<html> <body> <form action="https://bank.com/transfer" method="POST"> <input type="hidden" name="to" value="SylvainKerkour" /> <input type="hidden" name="amount" value="1000000" /> </form> <script> document.forms.submit(); </script> </body> </html>
I'm now the happy receiver of $1,000,000 for each customer of
bank.com visiting my website.
The second one is by using URLs. The vulnerability lies in the fact that
GET requests may execute actions instead of read-only queries.
By hosting the following HTML snippet:
An attacker can make any visitor triggers the following request sending the associated Cookies:
bank.com customer visiting this URL would become a happy sponsor of me :)
Why JSON APIs are mostly immune against CSRFs?
HTML forms are limited to two methods:
POST and to three content types:
Thus, a form can't make a request with the
application/json and an attacker would need to use an XMLHttpRequest to do that. XMLHttpRequests are subject to the Cross-Origin Resource Sharing (CORS) policy, which block cross-origin requests by default. Thus, you can't make an XMLHttpRequests from
bank.com doesn't specifically allow it.
Content-Type header is set to
application/json and / or if you have an
* policy, your JSON API might be vulnerable against CSRFs.
A few CSRF case studies
I've curated a few reports about real-world exploitation of CSRF vulnerabilities so you can better apprehend what they look like in the wild:
How to defend against CSRF
The first line of defense is to never allow
GET request to perform CREATE / UPDATE / DELETE operations.
The second line of defense is to use the
SameSite Cookie attribute set to
Strict. Unfortunately, this feature is today supported by the browsers of 'only' ~93% of internet users, so it shouldn't be used alone.
Build JSON APIs
The third line of defense is to only build JSON APIs with a strict CORS policy (
[app.mysite.com, mysite.com] for example) and check that the
Content-Type header is set to
Check the Origin
The fourth line of defense is to check the
Referer headers that indicate the domain from where the request originates. If the request comes from a domain that is not yours, send back a permission denied error.
The last line of defense is Anti-CSRF tokens.
When a user requests a page with a form, the server generates a random token and embeds it into the form as a hidden field. When the user submits the form, the token is sent with the other fields of the form, and the server then checks that the token is valid.
This method is way harder to get right than the others, and I don't recommend implementing it yourself but using a popular library, such as github.com/gorilla/csrf for Go.
Want to learn more about applied security? Get my book Black Hat Rust.