Telerik blogs

This article covers the mechanics of CSRF attacks and common countermeasures to help prevent them. Additionally, it explores how to use CSRF tokens in a modern Next.js application.

Implementing robust security measures to help protect against attacks that exploit user sessions and trick users into running malicious code is very important. One such attack is cross-site request forgery (CSRF), which poses a significant threat to web applications.

CSRF attacks exploit a user’s session integration with a web application. Attackers gain unauthorized access to a user’s session, which they can then use to perform malicious operations on the application. These operations can result in severe damage, including data breaches and a complete application takeover.

This article provides a foundational understanding of cross-site request forgery attacks, including their mechanics and how to help prevent them. We’ll go through how to implement CSRF protection in Next.js.

Project Setup

Run the command below to set up a new Next.js project using the create-next-app templating tool. This tool automatically installs the latest version of Next.js and configures your project for you.

npx create-next-app@latest

Enter a name for the project and fill in the default prompts listed below.

Next.js set up

Run the following command to navigate into the project folder, and start up your development server:

cd next-csrf-demo
npm run dev

You should see the running app by navigating to http://localhost:3000.

Understanding CSRF Attacks

As data is sent back and forth on the web, it can be encoded in various ways. Unfortunately, as the web evolves, attackers often find new ways to exploit vulnerabilities and gain unauthorized access to applications.

Examples include SQL injection, where an attacker inserts malicious SQL statements into an application’s database query, and cross-site scripting, where an attacker injects illegitimate scripts into a trusted website. These attacks can allow the attacker to execute arbitrary code and potentially steal sensitive information from users. The list of potential attacks is ever-growing and ever-evolving.

Although web application vulnerabilities such as SQL injection and cross-site scripting are commonly discussed and addressed, cross-site request forgery (CSRF) is often overlooked.

CSRF, also known as session riding or one-click attack, is a malicious exploit in which an attacker submits unauthorized or malicious commands through a user that the application trusts. In a CSRF attack, an attacker tricks an unsuspecting user into submitting a web request that the user did not intend to submit or may not even be aware of, which can result in undesirable consequences.

These attacks exploit the trust established between users and web applications, taking advantage of the implicit trust placed in an authenticated user’s session within the context of a web application.

After a user signs into an application, the application creates a session that authenticates the user’s identity for future requests. This authentication allows the user to access protected resources and perform certain operations within the application.

Unfortunately, an attacker can exploit this trust by sending malicious requests that include necessary parameters and instructions to carry out specific actions, such as making unauthorized financial transactions. Typically, this is done by tricking the victim into visiting a specially designed webpage or clicking on a malicious link.

When users visit a malicious page or click on a link, their browser can send a forged request to a targeted web application without their knowledge. If the user was previously authenticated in the application, the server may regard the request as valid and process it, allowing undesired operations to be carried out on the user’s behalf without their knowledge or agreement.

Mechanics of CSRF Attacks

To better understand CSRF vulnerabilities, it’s important to first examine the authentication mechanisms commonly used in traditional web applications. Let’s take the example of a banking application with a user named Bob. To access the application, Bob will need to provide his username and password, and the application will then set a cookie in his browser. This cookie is often a long, random string that identifies Bob to the backend.

Authentication mechanism

When Bob makes a recurring request to an application, the browser checks its cookie storage to see if there is a relevant cookie for the application domain. If a cookie exists, it is sent with the request. The backend then examines the cookie to check if it is valid before carrying out the requested operation. If the cookie is not valid, an error is returned.

Session cookie attached to recurring request

To perform a cross-site request forgery attack on Bob, who is still authenticated, an attacker must find a way to trick him into clicking on a link that makes illegitimate requests without his knowledge. For example, an attacker may email Bob with a link to a fake giveaway page.

If he is still authenticated in the main application, he may unknowingly open the email and the fake website in another browser tab. When the page loads, an illegitimate request is triggered with pre-filled values and the cookie for that particular application. The request can be used to change Bob’s password, transfer funds or change account configuration, among other malicious operations.

CSRF attack

Based on the analogy provided above, we can conclude that the success of a CSRF attack relies on the trust established between the targeted website and the victim’s browser. This is because the website considers the authenticated request, whether it comes from Bob or the attacker, to be valid.

A CSRF attack usually consists of the following:

  1. Authentication of the victim: The attacker checks that the victim is authenticated as a legitimate user of the targeted website, with an active session on the website.
  2. Attack setup: The attacker creates a malicious webpage or uses an already compromised one. The attacker then builds a malicious request that targets the vulnerable web application and includes all the necessary parameters and instructions to perform a malicious operation on behalf of the victim.
  3. Victim interaction: The attacker tricks the victim into visiting the malicious webpage, often using social engineering techniques such as false emails, phishing or alluring links.
  4. Automatic request: The malicious webpage or link automatically sends a request to the targeted web application in the victim’s browser, transmitting the disguised malicious request along with the victim’s session cookies.
  5. Trust exploitation: Since the victim is already authenticated on the targeted website, the server considers the request legitimate and executes the illegitimate operation, such as completing a purchase, changing account settings or deleting data.

Common Countermeasures to CSRF Attacks

To better safeguard the security of web applications, it is essential to protect against CSRF attacks. Fortunately, several countermeasures can be employed to add an extra layer of protection. This section will explore some of the most commonly used techniques for defending against CSRF attacks.

Verifying Referer Headers

The Referer header is a component of the HTTP protocol that identifies the URL of the webpage that referred the user to the current page. It is included in subsequent requests sent to the server when a user performs an action on a website.

Validating the Referer header can help us check that requests come from trusted sources and prevent CSRF attacks that manipulate users into performing unwanted actions. However, because attackers can modify or remove the Referer header, it should only be used in conjunction with other security measures as a backup defense mechanism.

SameSite Cookies

SameSite is an attribute that manages how cookies are transmitted in cross-site requests. When a web application sets its cookies to SameSite (either “Strict” or “Lax”), cookies are restricted from being sent in cross-origin requests. This means they are only transmitted when the user navigates within the same site, which prohibits the automatic insertion of session cookies in cross-origin requests, a technique widely used in CSRF attacks.

It is important to note that SameSite cookies are supported by contemporary browsers, but older ones may not be fully supported. Therefore, checking for browser compatibility and considering alternative CSRF protection techniques for older browsers is critical.

Using CSRF Tokens

CSRF tokens can help safeguard against CSRF attacks. These tokens are unique, randomly generated values linked to each user session. By requiring a valid CSRF token with each request, attackers cannot obtain the token, even if they successfully deceive a user into sending a harmful request. This helps prevent unauthorized activities since the server can distinguish between legitimate and malicious requests.

Next.js simplifies the process of using CSRF tokens by providing a built-in mechanism, including CSRF protection middleware. Tokens are generated and included in forms or responses and can be validated when further requests are received.

CSRF tokens are a widely used and effective approach for preventing cross-site request forgery (CSRF) attacks. In a subsequent section, we will explore how to integrate CSRF tokens into a Next.js application.

Simulating a CSRF Attack

Assuming that validation, authentication and session management have already been handled, we will create a dummy transfer page. Then, we will demonstrate a minimal scenario of a CSRF attack on the application.

Open the project folder in your code editor and replace the code in the src/app/page.tsx file with the following:

import styles from "./page.module.css";

export default function Home() {
  return (
    <main className={styles.main}>
      <h3>Transfer Funds</h3>
      <form action="/api" method="POST">
        <div>
          <input type="text" name="username" placeholder="Username" />
        </div>
        <div>
          <input type="number" name="amount" placeholder="amount" />
        </div>
        <input type="submit" />
      </form>
    </main>
  );
}

We created a form that allows users to transfer funds in a mock banking application.

To update the appearance of the page, replace the styles in the src/app/page.module.css file with the following:

.main {
  width: 100vw;
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.main > form {
  width: 25rem;
  max-width: 90%;
  padding: 2rem;
  border: 1px solid rgba(0, 0, 0, 0.3);
  margin: 1rem 0;
}
.main > form > div {
  width: 100%;
}
.main > form > div > input {
  width: 100%;
  padding: 0.5rem 1rem;
  margin: 0.5rem 0;
  border: 1px solid rgba(0, 0, 0, 0.3);
  border-radius: 3px;
}
.main > form > input {
  padding: 0.8rem 1rem;
  margin: 1rem auto;
  display: inline-block;
  width: 100%;
  background: #ff7f50;
  border: none;
  color: #fff;
  font-weight: 600;
  cursor: pointer;
}

Also, replace the global styles in the src/app/globals.css file with the following:

* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

After saving the changes, open the application in your browser, and you should see the created form.

Demo Next.js form

To set up a dummy API route, create an api folder within the src/app directory. Within the api folder, create a new file named route.ts. Open the src/app/api/route.ts file and add the following to it:

import { NextResponse } from "next/server";
export async function POST(request: Request) {
  return NextResponse.json({ status: "Transfer done successfully" });
}

We have created an API route that returns a sample object with text. Now if you click the Submit button on the form, you should see the response from the API.

Dummy API route

Assuming that validation, authentication and session management have been handled, a CSRF attack can be simulated easily by directly calling the API route while pre-filling the form values.

While we can create a fake web page to make the request to the API route under the hood, it is simpler to use Progress Telerik Fiddler Everywhere (a powerful tool that allows you to test and debug APIs from different platforms). Alternatively, a preferred HTTP client can also be used.

Fiddler Everywhere

Let’s make a POST request to the API route using Fiddler Everywhere.

POST request to API route using Fiddler Everywhere

When making a request from Fiddler, which is a different environment from the application, we can observe what a CSRF attack looks like. With the user authentication session taken care of, the backend executes the request without a means of distinguishing between legitimate and attacker requests.

In the next section, we will explore how to better prevent this using CSRF tokens.

Implementing CSRF Protection

While numerous third-party libraries simplify the implementation of CSRF protection in web applications, only a handful of them offer built-in support for Next.js at the time of writing.

In this article, we will use the Edge-CSRF library, which uses middleware to provide a layer of CSRF protection in Next.js applications.

To install edge-csrf in the demo application, run the following command:

npm install edge-csrf

To enable CSRF protection, the library generates a token using the cookie strategy from expressjs/csurf and the crypto logic from pillarjs/csrf. This token can be accessed from the X-CSRF-Token HTTP response header on the server-side or client-side and should be included with subsequent requests.

Open the src/app/page.tsx file and update the code as shown below:

import { headers } from "next/headers";
import styles from "./page.module.css";

export default function Home() {
  const csrfToken = headers().get("X-CSRF-Token") || "no_token";

  return (
    <main className={styles.main}>
      <h3>Transfer Funds</h3>
      <form action="/api" method="POST">
        <input type="hidden" name="csrf_token" value={csrfToken} />
        <div>
          <input type="text" name="username" placeholder="Username" />
        </div>
        <div>
          <input type="number" name="amount" placeholder="amount" />
        </div>
        <input type="submit" />
      </form>
    </main>
  );
}

In this code snippet, we extract the token from the X-CSRF-Token HTTP response header and pass it as the value to a hidden input element.

Next, create a new file called middleware.ts in the /src folder and add the Edge-CSRF middleware:

import csrf from "edge-csrf";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

const csrfProtect = csrf();

export async function middleware(request: NextRequest) {
  const response = NextResponse.next();
  // csrf validation
  const csrfError = await csrfProtect(request, response);
  // if an error occurs, then token is not valid
  if (csrfError) {
    return new NextResponse("invalid csrf token", { status: 403 });
  }
  return response;
}

We first import the default csrf function from the edge-csrf library and initialized the function. We then pass the request and response objects to the instance to validate the attached token. If the token is valid, access is granted to the request; otherwise, an error response is returned.

After making the necessary changes, you can test the application. While the application works fine in the browser, you should receive an error response when you test using Fiddler.

CSRF protection

We can also configure the CSRF middleware protection by passing an object with configuration options as an argument to the initialization function. This includes the ability to customize the default cookie set by the edge-csrf library, set the secret byte length, and set the salt byte length, among other things. For more information, visit the edge-csrf library documentation.

Conclusion

For better security of web applications, it is important to remain vigilant and up-to-date on the latest security risks. Employing strong defensive measures is essential to maintaining the safety and reliability of our applications. By prioritizing web security and implementing thorough CSRF protection techniques, we can develop applications that inspire trust and confidence, delivering a more secure user experience.

This article covers the mechanics of a CSRF attack and common countermeasures to help prevent it. Additionally, it explores how to use CSRF tokens in a modern Next.js application.

Useful Resources


Ifeoma-Imoh
About the Author

Ifeoma Imoh

Ifeoma Imoh is a software developer and technical writer who is in love with all things JavaScript. Find her on Twitter or YouTube.

Related Posts

Comments

Comments are disabled in preview mode.