Skip to main content

Cross-site request forgery

Need

Prevention of unauthorized actions executed by authenticated users

Context

  • Usage of Dart for building cross-platform mobile, web, and desktop applications
  • Usage of Shelf for managing and organizing data in a Node.js application

Description

Non compliant code

import 'package:shelf/shelf.dart' as shelf;

shelf.Response handleRequest(shelf.Request request) {
var action = request.url.queryParameters['action'];
var user = request.url.queryParameters['user'];

if (action != null && user != null) {
// Execute some action on behalf of the user
return shelf.Response.ok('Action executed');
} else {
return shelf.Response.forbidden('Invalid request');
}
}

In the above code, the handleRequest function is vulnerable to Cross-site request forgery (CSRF). The function takes a request and extracts the 'action' and 'user' parameters from the query string. If both parameters are present, it executes an action on behalf of the user.

However, there is no CSRF protection in place. An attacker can create a malicious link or form that includes the 'action' and 'user' parameters, and trick an authenticated user into clicking the link or submitting the form. Since the function does not validate the origin of the request or include any CSRF tokens, it will execute the action as if the authenticated user had initiated it.

This can lead to a variety of security issues, including unauthorized actions being performed on behalf of the user. The application should implement CSRF protection to prevent this type of attack. This could include validating the origin of the request, including a CSRF token in the request, and checking the token on the server side before executing any actions.

Steps

  • Implement a CSRF token in your application. This token should be unique per user session and should be included in every state-changing operation.
  • Generate the CSRF token on the server side and send it to the client side. Store it securely on the client side, for example in a cookie.
  • Include the CSRF token in every state-changing request from the client side. This can be done by including it in the request header or in the request body.
  • On the server side, validate the CSRF token for every state-changing request. If the token is not present or does not match the token on the server side, reject the request.
  • Rotate the CSRF token periodically and after every state-changing operation to reduce the risk of CSRF token theft.
  • Consider using a framework or library that has built-in CSRF protection to simplify the implementation and reduce the risk of errors.

Compliant code

import 'dart:math';
import 'package:shelf/shelf.dart' as shelf;

Map<String, String> tokens = {};

String generateToken() {
var random = Random.secure();
var values = List<int>.generate(32, (i) => random.nextInt(256));
return values.join();
}

shelf.Response handleRequest(shelf.Request request) {
var action = request.url.queryParameters['action'];
var user = request.url.queryParameters['user'];
var token = request.headers['X-CSRF-TOKEN'];

if (action != null && user != null) {
if (tokens[user] == token) {
// Rotate CSRF token
tokens[user] = generateToken();

// Execute some action on behalf of the user
return shelf.Response.ok('Action executed');
} else {
return shelf.Response.forbidden('Invalid CSRF token');
}
} else {
return shelf.Response.forbidden('Invalid request');
}
}

The updated code now includes a CSRF token for each user. This token is generated on the server side and should be included in every state-changing request from the client side. The token is stored in a map tokens with the user as the key.

In the handleRequest function, the CSRF token is retrieved from the request headers. If the action and user are not null, the function checks if the token matches the one stored on the server side. If it does, the CSRF token is rotated, the action is executed, and a response is sent back to the client. If the token does not match or is not present, the function returns a forbidden response.

This way, even if an attacker manages to trick a user into executing a state-changing operation, they would not be able to provide a valid CSRF token, and the request would be rejected. This effectively mitigates the risk of Cross-Site Request Forgery attacks.

References