Complete Guide to CSRFPublished:
What is CSRF?
CSRF stands for Cross-Site Request Forgery and is one of the most "popular" web application vulnerabilities around, although, one of the more subtle ones. Before going into details, I like giving a simple yet relevant example onto which we can build upon.
Just like the classic XSS (Cross-Site Scripting) example is the GET Search form, the classic CSRF example is the GET change-password form. The fact that the form is GET is of little significance, it's just easier to create a Proof-Of-Concept.
Suppose we have a web application with the following form at
<html> --- SNIP --- <form method="GET"> <input name="new_password" type="text"/> <input name="repeat_password" type="text" /> <input type="submit" /> </form> --- SNIP --- </html>
We can set up a CSRF attack if we can trick somebody to make a request to this URL:
http://web.site/auth/change_password. The simplest way to do that is to load the page into an
<iframe>. Let's say we can make an authenticated user (authenticated on the
web.site web app) visit a page we control (
http://bad.site/attack) that contains:
<iframe src="http://web.site/auth/change_password" style="display: none"></iframe>
Now, we have forged the request but didn't do anything. We didn't force the user to change the password. We just need to include the GET parameters:
<iframe src="http://web.site/auth/change_password?new_password=hackerpass&repeat_password=hackerpass" style="display: none"></iframe>
There, you just changed the user password. Let's recap what were the preconditions that helped us achieve this:
- The user was already authenticated in the web application - When forging the request the browser includes the Cookies and everything. It treats it as a legit request.
- The attacker tricked the user to visit a page under its control.
- The page contains an iframe that makes a GET request with the form parameters to the vulnerable web application.
POST CSRF Attack
Your first objection might be that usually, forms are not using the GET method. And you are right, state altering operations should NEVER be done via GET requests. This doesn't mean that you won't find these GET forms in the wild.
There's a simple way to forge POST requests too. Let's say our vulnerable page now has a POST form:
<html> --- SNIP --- <form method="POST"> <input name="new_password" type="text"/> <input name="repeat_password" type="text" /> <input type="submit" /> </form> --- SNIP --- </html>
<html> --- SNIP --- <form id="hackerform" method="POST" action=" http://web.site/auth/change_password"> <input type="hidden" name="new_password" value="hackerpass" /> <input type="hidden" name="repeat_password" value="hackerpass" /> </form> <script> // Submit the form document.hackerform.submit() </script> --- SNIP --- </html>
We had to include this page in an iframe so that the user doesn't notice the redirection.
Now open this file in your browser
Notice how you get immediately redirected to
http://web.site/auth/change_password. If this page would have been opened in an invisible iframe, the user wouldn't notice anything and yet his/her password was changed.
Keep in mind ...
Here are a few more things to consider:
- Exploiting CSRF can be the silver bullet that compromises the entire website if you manage to change the password of an admin account
- The attacker cannot read the response of the GET/POST request. This is due to Same-Origin Policy. This means for example that if you launch a CSRF attack that changes the admin password, you need to be constantly checking whether that password was successfully changed.
- By default other types of requests cannot be forged, also due to the Same-Origin Policy. This becomes possible if the website adds the Header:
You can also persist such attacks. Imagine including the invisible iframe in a popular forum and wait for people to visit the forum while being authenticated on the vulnerable web app as well. After a while you can go to the web application and perform a password spray using the password you've set for all the users. Chances are you've compromised a few accounts.
Protecting against CSRF Attacks
There are 2 main ways of defending against CSRF Attacks, both of them requiring the server to sent a CSRF token to the client and the client to present the CSRF Token back. Most web frameworks these days implement some sort of CSRF protection.
Method 1: Keeping CSRF Tokens in a database
The first method emitting tokens is to generate them and then simply store them in a database/cache/key-value store. You can make them expire after a period so that your database doesn't get filled up. Once a client presents a token you check its validity by looking in the database. If you find the token there, make sure you invalidate it so that it can't be used multiple times. This is the most simple to understand but also the hardest to implement because you need to keep state on the server.
Method 2: Cryptography based Tokens
There are various flavours of this technique. Some use encryption, others use a digest function.
Implies sending a token formed using this method:
encrypt(SESSION_ID + TIMESTAMP).
The server verifies the token by decrypting it, checking the validity of SESSION_ID and optionally checking TIMESTAMP.
Using a Digest Function
Pretty much the same as using encryption but this time we are sending this token
digest(SESSION_ID + TIMESTAMP) + TIMESTAMP.
We included the TIMESTAMP outside the digested part as well to be able to regenerate the digested part. The server verifies the token by trying to recreate it.
Double-Submit Cookie Technique