CSRF Attacks & Prevention: A Developer's Guide

CSRF Attacks & Prevention: A Developer's Guide

An explanation of CSRF (Cross-Site Request Forgery) vulnerabilities and practical steps for avoiding them

The original article can be read here

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.

  1. Use CSRF tokens provided by your application platform/library.
  2. Don't make changes using other HTTP verbs than POST, PUT, PATCH, or DELETE.
  3. 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:

  1. Two URLs are of the same origin if their protocol, port (if specified), and host are the same.
  2. A website from any origin can freely send GET, POST, HEAD, and OPTIONS requests to any other origin. Furthermore, the request will include the user's cookies (including the session ID) to that origin.
  3. While sending requests is possible, a website from one origin cannot directly read the responses from another origin.
  4. 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).

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-origin 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.

Get the web security checklist spreadsheet!

Subscribe to AppSec Monkey now and get the 2021 Web Application Security Checklist Spreadsheet as a welcome gift for FREE!

Don't stop here

If you like this article, check out the other application security guides we have on AppSecMonkey.com as well.