CSRF Attacks & Prevention: How To Secure Your Web Application (2021)
Tags: security, webdev, programming, cybersecurity
Published: 2021-02-23 05:35:37

The original article can be read [here](https://www.appsecmonkey.com/blog/csrf-attack-and-prevention/)

## 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](https://developer.mozilla.org/en-US/docs/Web/Security/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](https://www.w3schools.com/js/js_json_jsonp.asp) hack takes advantage of this fourth rule (don't use JSONP).

For example, let's say 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: 

```yaml
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): 
```python
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: 

```html
<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`: 

```yaml
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.

```html
<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](https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html), [Express](https://expressjs.com/en/resources/middleware/csurf.html), [Symfony](https://symfony.com/doc/current/security/csrf.html), [Django](https://docs.djangoproject.com/en/3.1/ref/csrf/), [ASP.Net MVC](https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks), 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](https://tools.ietf.org/html/rfc6819#section-4.4.2.5)).

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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). When you set a cookie with `SameSite=Lax`, browsers will not include it in cross-origin POST requests.

```yaml
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](https://www.appsecmonkey.com/blog/xss-attack-and-prevention/#bonus--avoid-xss-by-implementing-strict-samesite-cookies) 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](https://www.appsecmonkey.com/blog/xss-attack-and-prevention/). 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iqd1ef4a5a0icyvvk65.jpg)](https://eepurl.com/hqAGt5)
 ☝️ [Subscribe](https://eepurl.com/hqAGt5) to AppSec Monkey's email list, get our best content delivered straight to your inbox, and <b>get our 2021 Web Application Security Checklist Spreadsheet for FREE</b> as a welcome gift!

## Don't stop here
If you like this article, check out the other application security guides we have on [AppSec Monkey](https://www.appsecmonkey.com/) as well.

Thanks for reading.