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.

CSRF

How?

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[0].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:

<img src="https://bank.com/transfer?to=SylvainKerkour&amount=1000000">

An attacker can make any visitor triggers the following request sending the associated Cookies:

GET https://bank.com/transfer?to=SylvainKerkour&amount=1000000

Any logged-in 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: GET and POST and to three content types: application/x-www-form-urlencoded, multipart/form-data, and text/plain.

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 attacker.com to bank.com if bank.com doesn't specifically allow it.

That being said, if your JSON API use Cookies, doesn't check that the Content-Type header is set to application/json and / or if you have an Access-Control-Allow-Origin: * 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

Read-only GET requests

The first line of defense is to never allow GET request to perform CREATE / UPDATE / DELETE operations.

SameSite Cookies

The second line of defense is to use the SameSite Cookie attribute set to Lax or 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 application/json.

Check the Origin

The fourth line of defense is to check the Origin and 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.

Anti-CSRF tokens

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.

1 email / week to learn how to (ab)use technology for fun & profit: Programming, Hacking & Entrepreneurship.
I hate spam even more than you do. I'll never share your email, and you can unsubscribe at any time.

Tags: hacking, tutorial, security

Want to learn Rust, Cryptography and Security? Get my book Black Hat Rust!