Implementation of CSRF protection in scalable APIs

2022-09-28 - 6 min read

With the rise of cloud-based applications and microservices, it's more important than ever to have secure and scalable APIs. One common attack vector against APIs is cross-site request forgery (CSRF), allowing an attacker to inject malicious code into a legitimate user's session. This article will discuss how to implement CSRF protection in a scalable API.

What is CSRF in one minute?

CSRF, or Cross-Site Request Forgery, is an attack that occurs when a malicious user tricks a victim into submitting a request to a website without their knowledge or consent. That can be done by embedding malicious code in a webpage the victim visits or sending the victim a malicious link via email or social media. Suppose the victim's browser has an active session with the target website. In that case, the request will be executed with the victim's credentials, allowing the attacker to perform actions on behalf of the victim.

An example of a vulnerable request is as follows:

POST /api/change-password HTTP/1.0
Cookie: sessionId=d35c6ce994e4ea2e20ca5f5101f29105
Content-Type: application/x-www-form-urlencoded
newPassword=c0rrectH0rseBatteryStaple

This request might result from the following HTML form:

There are a few important elements here. First, the request does something potentially harmful. Second, a cookie is solely used for authentication.

The attack is simple. The attacker sets up a slightly altered form on evil.com:

The next step is to trick a victim into visiting the page above. The victim's password is changed if the victim has a valid session cookie (if logged in). Such events might go unnoticed when the code above is loaded in a visually hidden iframe.

Is CSRF an issue for REST APIs?

It could be. We mentioned the session cookie for authentication as a crucial element. The point is that the browser will send this authentication, even if the origin (the form on evil.com) doesn't have it. That is the case for cookies, basic authentication, and IP safelisting.

The OAuth2 Bearer tokens are not vulnerable to CSRF because the browser will not send them automatically. The front end must provide these tokens explicitly, but the malicious website doesn't have them. The same applies when you use a custom header for sending API tokens.

The Synchronizer Token Pattern

The synchronizer token pattern works by embedding a token in the HTML page that is also stored on the server. The server verifies this token before processing the request, ensuring that the request is legitimate and not part of a CSRF attack.

We can implement this in the example above by adding a hidden field "csrf_token” to the form. The request then becomes

POST /api/change-password HTTP/1.0
Cookie: sessionId=d35c6ce994e4ea2e20ca5f5101f29105
Content-Type: application/x-www-form-urlencoded
newPassword=c0rrectH0rseBatteryStaple&csrf_token=8974f33be5136f71

The evil website cannot obtain the token and, therefore, cannot construct a valid form.

While the synchronizer token pattern effectively prevents CSRF attacks, it can be challenging to implement in scalable APIs. That is because the token must be unique to each user, preferably each request, which we need to store in a database. Exactly the database is the most critical part of scalable apps. Therefore, we would welcome a technique that doesn't require storage.

The double submit cookie pattern is a method that uses both the browser's cookie mechanism and a custom header. The idea is to set a cookie with a random value when the user first visits the site. When the user submits a form, that random value is sent in a custom header. The server then checks the cookie and the header values to verify that they match before processing the form submission. So, the server response for the form looks like this:

HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: csrf_token=8974f33be5136f71

The csrf token is random and is not stored by the server. The request to change the password now looks like this:

POST /api/change-password HTTP/1.0
Cookie: sessionId=d35c6ce994e4ea2e20ca5f5101f29105; csrf_token=8974f33be5136f71
Content-Type: application/x-www-form-urlencoded
newPassword=c0rrectH0rseBatteryStaple&csrf_token=8974f33be5136f71

The server allows the request if the tokens from the request body and the cookie are identical.

What if the hacker adds the csrf_token field to the evil form? The hacker can use a random value for this, but it will not match the value in the cookie, or the cookie is missing if the user didn't visit the page before on the genuine site.

The double submit cookie technique is relatively easy to implement. However, there are a few potential weaknesses with this approach.

First, the attacker could also use a man-in-the-middle (MITM) attack to set the cookie. That requires additional sophisticated techniques but might still work on sites using TLS (https). The reason is that browsers will first try the non-TLS version if you just type in the domain.

Second, if the attacker can control a subdomain, it can set a cookie on the top-level domain with a more specific path. That cookie is then also valid for the genuine website.

Both risks require additional conditions to be exploitable. Nevertheless, these are potential weaknesses to consider when using this approach.

Mitigations

Web browsers have implemented new security headers and cookie flags to mitigate the described risks.

The first risk (MITM-attack) requires the victim to visit a website over an unencrypted connection (plain http). The HSTS header prevents this by instructing the browser that the site is only available over TLS. This only works for users that visited the site in the last year (or a shorter period provided in the header). A few new top-level domains (including .app) only support TLS by their standards and offer the same security for new and returning visitors.

TLS does not mitigate the second security risk if the attacker can still control subdomains by other means. Also, setting the domain and other flags at the cookie doesn't help much, as the attacker can still set his own cookie. This risk is mitigated by setting the cookie value to a cryptographic hash that incorporates secrets only known to the server. The result is that the attacker cannot generate a correct cookie value without knowing the secret.

Another simple but strong defense is to use a custom header name to send the CSRF security token instead of adding it to the body. HTML forms or links cannot implement these custom headers. They must be set by JavaScript, but an external script will result in a cross-origin (CORS) request and is therefore blocked by the browser security mechanisms. An example request with a custom header is:

POST /api/change-password HTTP/1.0
Cookie: sessionId=d35c6ce994e4ea2e20ca5f5101f29105; csrf_token=8974f33be5136f71
Content-Type: application/x-www-form-urlencoded
X-CSRF-TOKEN: 8974f33be5136f71
newPassword=c0rrectH0rseBatteryStaple

Implementing in APIs

To prevent CSRF attacks, developers must implement proper security measures in their web applications. Flowlet allows you to add CSRF protection to any endpoint and implements all mitigations mentioned in this article.

Can't wait to write secure APIs with Flowlet?