- Published on
CSRF (Cross-Site Request Forgery) Attacks and Prevention
- Authors
- Name
- Teo Selenius
- Follow @TeoSelenius
What are CSRF vulnerabilities?
CSRF (Cross-Site Request Forgery) vulnerabilities usually arise when a web application that uses cookies for session management fails to verify an HTTP POST request's origin.
They can also happen when a web application misuses the GET method by using it to make changes. Both of these scenarios allow for a malicious website to perform unwanted actions on the logged-in user's behalf.
How to prevent CSRF vulnerabilities?
Follow these steps.
- Use CSRF tokens provided by your application platform/library.
- Don't make changes using other HTTP verbs than POST, PUT, PATCH, or DELETE.
- Secure your cookies with the 'SameSite' directive.
Same Origin Policy
Before we get started, it's helpful to understand SOP (Same Origin Policy), which is the heart and soul of the web browser security model. It is a rule that more or less says:
- Two URLs are of the same origin if their protocol, port (if specified), and host are the same.
- A website from any origin can freely send
GET
,POST
,HEAD
, andOPTIONS
requests to any other origin. Furthermore`, the request will include the user's cookies (including the session ID) to that origin. - While sending requests is possible, a website from one origin cannot directly read the responses from another origin.
- A website can still consume resources from those HTTP responses, such as by executing scripts, using fonts/styles, or displaying images. The JSONP hack takes advantage of this fourth rule (don't use JSONP).
- A website can from one origin can have restricted access to a window in another origin, if it gets a handle to the window. Most notably windows from different origins can change each other's URL (
anotherWindow.location.replace("https://www.evil.com"
).
For example, this website's origin is https://www.appsecmonkey.com/
where the protocol
is https
, the host is www.appsecmonkey.com
, and the port is not specified (which is implicitly 443
because of the https
protocol).
A simple example
Let's pretend that users could log in to AppSec Monkey and update their email address like so:
POST /user/update-email/ HTTP/1.1
Host: www.appsecmonkey.com
Cookie: SessionId=ABC123
...
new_email=bob@example.com
The backend code would perhaps look like this (at least if you use Django):
def update_email(request):
new_email = request.POST['new_email']
set_new_email(request.user, new_email)
Now let's say there's an evil website evil.example.com
with the following HTML form and auto-submit script:
<form method="POST" action="https://www.appsecmonkey.com/user/update-email/">
<input type="hidden" name="new_email" value="evil@example.com" />
</form>
<script type="text/javascript">
document.badform.submit()
</script>
When a user that is currently logged in to www.appsecmonkey.com
enters the malicious website, the HTML form is auto-submitted on the user's behalf, and the following HTTP POST request gets immediately sent to www.appsecmonkey.com
:
POST /user/update-email/ HTTP/1.1
Host: www.appsecmonkey.com
Cookie: SessionId=ABC123
...
new_email=evil@example.com
And Bob's email address gets changed to evil@example.com
.
CSRF tokens
The usual way to protect against these attacks is to use something called a CSRF token. It works so that you add a hidden value, not guessable by an attacker, inside your HTML forms.
<form method="POST" action="/user/update-email/">
<input type="text" name="new_email" />
<input type="hidden" name="csrf-token" value="SomeRandomValue123456" />
</form>
When another website like evil.example.com
tries to submit the form, the webserver rejects the POST request because it doesn't contain the user's CSRF token.
All modern web application frameworks (Spring, Express, Symfony, Django, ASP.Net MVC, etc). worth their salt have a CSRF protection mechanism like this, so don't create your own.
That's your first defense.
Don't forget the login endpoint
It is also a vulnerability, often exploitable in some way, if malicious websites can unwittingly log your user in with their account.
To prevent this, make sure to use CSRF tokens for the login form as well.
You should tie the token to the pre-authenticated session (which you should discard when the user authenticates and give the user a new session ID, but that's another story).
Or, if you use OAuth/OIDC, make sure you are verifying the state
parameter correctly (see here for more on that).
In both scenarios, modern application frameworks should be able to handle it without any hassle.
Mind your HTTP verbs
CSRF-token mechanisms tend to protect POST requests only. So if you accept a GET request such as https://www.appsecmonkey.com/user/update-email?new_email=evil@example.com
, you will generally be vulnerable regardless of any CSRF protection that you might use.
Make sure you do not use anything except POST/PUT/PATCH/DELETE for making changes.
Modern platforms tend to be explicit about the verb, but you sometimes have to be careful with legacy frameworks.
Use SameSite cookies
These days browsers support a cool feature called SameSite cookies. When you set a cookie with SameSite=Lax
, browsers will not include it in cross-site POST requests.
Set-Cookie: SessionId=123; ...other options... SameSite=Lax
That's it, simple and effective. But do not rely on this feature alone for your application's security. Use CSRF tokens as your primary defense and apply SameSite cookies as a bonus layer of protection.
You can also set the cookie in SameSite=Strict
mode. In this case, also GET requests will be protected. However, this shouldn't be necessary for CSRF protection if you follow the above rule of minding your HTTP verbs. And it breaks functionality somewhat, as links to your application will no longer work as expected (user won't be logged in anymore when the tab/window opens).
Using the Strict
variant has the benefit of protecting against some XSS (Cross-Site Scripting) attacks so you might want to at least consider it.
Use cookieless session management
CSRF is a vulnerability that affects applications that use cookies for session management. One way to avoid them is to use something else, such as JavaScript session tokens. But there are downsides to this approach as well. For example, the tokens will be accessible to XSS attacks. In contrast, a web application can protect cookies from JavaScript code with the HttpOnly
attribute.
Conclusion
CSRF attacks can be dangerous. Luckily, they are also easy to avoid as long as you use a decent, modern application platform that supports CSRF tokens and is explicit about HTTP verbs. SameSite cookies provide an excellent additional security layer for your application but should not be relied upon for security alone.