How JavaScript works: CSRF attacks + 7 mitigation strategies

Alexander Zlatkov
SessionStack Blog
Published in
12 min readFeb 8, 2021

This is post # 22 of the series, dedicated to exploring JavaScript and its building components. In the process of identifying and describing the core elements, we also share some rules of thumb we use when building SessionStack, a JavaScript application that needs to be robust and highly-performant to help companies optimize the digital experience of their users.

Overview

Cross-Site Request Forgery (CSRF, sometimes pronounced “sea-surf”), also known as one-click attack or session riding is a type of malicious attack on a web app or website. In these types of attacks, the attacker performs malicious requests on behalf of the victim. There are many ways in which a malicious web app can transmit such requests such as specially-crafted image tags, hidden forms, AJAX requests, etc. They all work without the user’s interaction or even knowledge.

While Cross-Site Scripting (XSS) attacks exploit the trust a user has for a particular web app, CSRF attacks exploit the trust a web app has in a particular user’s browser.

Internals of CSRF attacks

When a CSRF attack is being performed, the victim is submitting a malicious request which they were not aware of. This may cause actions to be performed on the web app that can include client or server data leakage, change of session state, or manipulation of an end user’s account.

CSRF attacks are an example of a confused deputy attack against a web browser because the web browser is tricked into submitting a forged request by a less privileged attacker.

CSRF commonly has the following characteristics:

  • Involves web sites or web apps that rely on a user’s identity.
  • Exploits the site’s trust in that identity.
  • Tricks the user’s browser into sending HTTP requests to a target site.
  • Involves HTTP requests that have side effects.

This is an overview of the steps in a CSRF attack:

  • The victim performs an action, like visiting a web page, clicking a link, etc. which is controlled by the attacker.
  • This action sends an HTTP request to a web app on behalf of the victim.
  • If the victim has an active authenticated session on the web app, the request is processed as a legitimate request sent by the victim.

It’s important that the victim must have an active session with the web app, which is being attacked through a CSRF exploit.

In most cases, CSRF attacks don’t steal private information, but rather trigger some form of a change related to the victim’s account, such as changing their credentials or even perform a purchase. Forcing the victim to retrieve data from the server doesn’t benefit the attacker because the attacker doesn’t receive the response. The victim does. As such, CSRF attacks target requests that perform changes.

Typically, session management in web apps is based on cookies. With each request to the server, the browser sends the related cookie that identifies the current user’s session. This usually happens even if the request originated from a different web app and domain. This is the vulnerability exploited by the attacker. Although CSRF is normally described in relation to cookie-based session handling, it also arises in other contexts where the application automatically adds some user credentials to requests, such as HTTP Basic authentication and certificate-based authentication.

Example

Let’s look at the following example which illustrates a simple “Profile page” on a social network web app:

This page simply loads the user profile data from the server and populates it into the form. If the form is edited, the data can be submitted and updated by the server.

The server accepts the submitted data only if the user is currently authenticated.

Now let’s look at a malicious page that performs the CSRF attack. This page is created by the attacker and is hosted on a different domain. The goal of this page is to execute a request to the social network app on behalf of the victim, relying on the fact that the victim is authenticated:

The page contains a form with hidden fields. That form’s action points to the same endpoint as the profile page of the social network.

Once the victim opens the malicious website, the form is automatically submitting data to the server of the social network app by the script in the page.

This form is harmless when the user of the social network app is not authenticated. The vulnerable web app will refuse to change the user’s profile because of this missing authentication data. However, if the user is authenticated, the change will be applied as any other legitimate request.

This behavior is due to a cookie on the user’s browser that tracks the current session on the social network. When the vulnerable web app receives the change request, it appears legitimate since it has the correct session cookie.

CSRF Attack

So, even if the attacker has no direct access to the vulnerable web app, they exploit the victim and the CSRF vulnerability to perform unauthorized actions. In fact, unlike what may happen in XSS attacks, here, the attacker doesn’t directly read the cookie and steal it.

This example is a very oversimplified and mature attack that can be much more complex and less visible to the victim. For example, a CSRF attack can be embedded into an iframe and the victim will not be aware that an attack is occurring at all.

There are a series of approaches that should be followed in order to mitigate the risk of CSRF attacks.

Token-Based Prevention

This defense is one of the most popular and recommended methods to mitigate CSRF attacks. It can be achieved with two general approaches:

  • Stateful — synchronizer token pattern
  • Stateless — encrypted or hashed based token pattern

Many popular frameworks provide out-of-the-box implementations for these techniques.

Built-In CSRF Implementations

It is strongly recommended to research if the framework you are using has an option to achieve CSRF protection by default before trying to build your custom system.
Even if there is such a system, there is still some responsibility left for you to configure it properly, such as key management and token management.

If it is not possible to use built-in CSRF protection mechanisms in the framework you are using, you can build them on your own.

Let’s take a look at a built-in CSRF implementation in Express.
Express provides a middleware called csurf which is all that is needed to do the job.

We won’t go into details about Express or how to install packages in this article.

Here is our index.js:

Then in the views folder, we add index.ejs which looks like this:

The / route will render the index.ejs template with the csrfToken variable available in the template to interpolate the CSRF token.

In index.ejs, the csrfToken will be set as a value to the hidden field.
When the form is submitted, a request is being sent to the /profile route which is CSRF-protected.

Without the CSRF token, an invalid CSRF token error will be thrown.

Synchronizer Token Pattern

The Synchronizer Token Pattern allows the server to validate requests and ensure that they are coming from a legitimate source.

The pattern works by generating tokens on the server for each user session or each request.

When a request is sent from the client, the server must verify the existence and validity of the token in the request compared to the token found in the user session.

In most web apps, servers are using HTTP session objects to identify the logged in users. In this case, a session is generated on the server-side and a session ID is passed to the client. This session ID is most of the time saved in a client-side cookie.

If the cookie which stores the session ID is not protected with advanced configurations (httponly, samesite, secure, etc.), it is possible to access this cookie from another page that is open in the browser.

The per-request approach is more secure since the attacker has less time to interfere and exploit the token. The downside of this approach is that it can potentially damage the customer experience. If the user clicks the “Back” button in the browser, the previous page may contain a token that is no longer valid. This means that interactions with the previous page will result in the server’s inability to validate the token and the requests won’t pass.

CSRF token should have the following characteristics:

  • Uniqueness per session
  • Hard to predict — a securely generated random value

CSRF tokens can mitigate CSRF attacks because without a token, the attacker cannot create valid requests which will be executed on the server.

CSRF tokens should not be transmitted using cookies, due to potential interception or access by attackers.

It’s also not advisable to transmit CSRF tokens through “GET” requests, since leakage is possible through several locations, such as the browser history, log files, Referrer headers if the protected site links to an external site.

CSRF tokens should be transmitted through:

  • Hidden fields used in forms
  • Headers used in AJAX calls

Here is how a CSRF token can be added to a form:

The token which is a value of the input field is generated on the server, similarly to the Express example above.

Encryption-Based Token Pattern

As the name suggests, the Encryption-Based Token Pattern is based on encryption. It is a more suitable approach for modern web apps, which do not maintain any state at the server.

The token is generated by the server and is composed of the session ID of the user and a timestamp. This pair is encrypted using a secret key. Once the token is generated, it is returned to the client. As with the Synchronized Token, the Encryption-Based Token is either stored in a hidden field or added to the header for AJAX requests.

Once requests are made with the token, the server tries to decrypt it with the same key which was used for encryption.

If the server is not able to decrypt the token, it means that there was some form of intrusion and the request is treated as malicious or invalid.
If the server successfully decrypts the token, the session ID and the timestamp are extracted. The session ID is compared against the currently authenticated user, and the timestamp is compared against the current server time to verify that it’s not beyond the pre-defined expiry time.

If the session ID matches the current user and the timestamp is not expired, the request is treated as secure.

SameSite Cookies

SameSite is a cookie attribute that aims to mitigate CSRF attacks.

This attribute allows the browser to decide whether to send cookies along with cross-site requests. The possible values are:

  • Strict — Cookies will only be sent in a first-party context and not be sent along with requests initiated by third-party websites. This means that if there is a link on some website to a private GitHub repository, GitHub will not receive the session cookie if the link is clicked and the user will not be able to access the repository.
  • Lax — Cookies are not sent on CSRF-prone request methods such as POST. Cookies are sent when a user is navigating to the origin site. This is the default cookie value if SameSite has not been explicitly specified in recent browser versions. If there is a link on some website to a private GitHub repository, GitHub will receive the session cookie and the user will be able to access the repository.
  • None — Cookies will be sent in all contexts such as first-party and cross-origin requests. Additionally, the Secure flag will be required.

All desktop browsers and almost all mobile browsers now support the SameSite attribute.

This attribute should not replace having a CSRF Token. Instead, it should co-exist with that token in order to protect the user in a more robust way.

Verifying Origins

This mitigation technique consists of two steps which rely on examining the HTTP request header value:

  • Determining the source origin — where the request is coming from. This can be done through the Origin or Referer headers.
  • Determining the target origin — where is the request going.

The server has to verify that the source origin and target origin match. If there is a match, the request is considered legitimate and is accepted. If there is no match, the request is discarded, since the request originated from cross-domain.
These headers are reliable since they cannot be altered programmatically through JavaScript as they fall under the forbidden headers which can only be modified by the browser.

Double Submit Cookie

The Double Submit Cookie mitigation approach is an alternative to the CSRF token approach. It is a stateless approach.

When a user visits a web app, a cryptographically strong pseudorandom value should be generated and set as a cookie on the user’s machine, separate from the session ID.

The server then requires that every request includes this value (through a hidden form or request parameter). If both of them match on the server side, the server accepts it as a legitimate request and if they don’t, it would reject the request.

Custom Request Headers

This approach is well suited for web apps that are heavy on AJAX requests and rely on API endpoints.

The Same-Origin Policy is used in this approach, which restricts that only JavaScript can be used to add a custom header, and only within its origin. By default, browsers do not allow JavaScript to make cross-origin requests with custom headers.

The efficiency of this solution requires a robust CORS configuration since custom headers for requests coming from other domains trigger a pre-flight CORS check.

This allows you to add a custom header to your requests and simply verify its presence and value on the server.
This technique works for AJAX calls, but <form> elements should be additionally protected with tokens.

Interaction-Based Defense

User behavior can be a very efficient mechanism to prevent unauthorized operations such as CSRF attacks. There are two very common approaches:

  • Re-Authentication — enforce the user to authenticate before the request is performed,
  • CAPTCHA

While these approaches are very strong against CSRF attacks, they can create a significant impact on the user experience. They should mainly be applied for critical operations such as money transfers.

Pre-Authentication Defense

CSRF attacks are possible even on pages such as login forms where the user is still not authenticated. The impact of such attacks on pre-authenticated pages is different compared to post-authenticated pages.

Let’s consider an e-commerce website, where the victim is browsing through the items, prior to authenticating themselves. An attacker can use a CSRF attack on that website to authenticate the victim with the account of the attacker. When the victim enters their credit card information, the attacker will be able to purchase items using the victim’s card.

In order to mitigate these attacks, you can create pre-sessions while the user is still not authenticated. The login form must include a CSRF token, following the techniques mentioned in the Token-Based prevention section above.

Once the user is authenticated, the pre-session should be transitioned to a real session.

If a customer is complaining that something in their account is not as expected, it’s possible that they have been a victim of a CSRF attack. A platform like SessionStack allows you to easily pinpoint user sessions and replay them as videos to see exactly what happened. This will show you whether the user was responsible for the changes in their account or if it was some external interference.

There is a free trial if you’d like to give SessionStack a try.

SessionStack replaying a user session

If you missed the previous chapters of the series, you can find them here:

Resources:

--

--