Lack of protection against brute force attacks - Dart

Lack of protection against brute force attacks - Dart

Need

Implementation of robust brute force protection mechanisms

Context

  • Usage of Dart 2.0 for developing cross-platform applications
  • Usage of Shelf for managing and organizing data in Node.js applications
  • Usage of shelf_io for managing and interacting with data storage in an organized manner

Description

Non compliant code

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

void main() {
  var handler = const shelf.Pipeline()
      .addMiddleware(shelf.logRequests())
      .addHandler(_echoRequest);

  io.serve(handler, 'localhost', 8080);
}

shelf.Response _echoRequest(shelf.Request request) {
  var username = request.headers['username'];
  var password = request.headers['password'];

  if (username == 'admin' && password == 'password') {
    return shelf.Response.ok('Authenticated');
  } else {
    return shelf.Response.forbidden('Not authenticated');
  }
}
        
        

The above Dart code uses the Shelf framework to create a simple backend server. The server listens to incoming requests on localhost at port 8080. The server expects the client to send 'username' and 'password' in the request headers.

The _echoRequest function is the request handler. It retrieves the 'username' and 'password' from the request headers and checks if they match the hardcoded credentials ('admin' and 'password'). If they match, it responds with a 200 OK status and a message 'Authenticated'. If they don't match, it responds with a 403 Forbidden status and a message 'Not authenticated'.

The vulnerability in this code is that it doesn't have any protection against brute force attacks. An attacker could potentially send thousands of requests with different combinations of 'username' and 'password' until they find the correct credentials. This is a significant security risk as it could lead to unauthorized access to the system.

Steps

  • Implement a limit on the number of failed login attempts. After a certain number of failed attempts, the account should be temporarily locked.
  • Introduce a delay after each failed login attempt. This delay should increase exponentially with each subsequent failed attempt, making brute force attacks time-consuming and less feasible.
  • Use CAPTCHA to distinguish between human users and bots. This can prevent automated scripts from executing brute force attacks.
  • Implement multi-factor authentication. This adds an additional layer of security, making it more difficult for an attacker to gain access even if they guess the correct password.
  • Store IP addresses of users who have multiple failed login attempts and block them if necessary.
  • Encrypt the password using a strong hashing algorithm before comparing it with the stored password hash.

Compliant code

        import 'dart:async';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;
import 'package:crypto/crypto.dart';
import 'dart:convert'; // for the utf8.encode method

// Store failed attempts
Map failedAttempts = {};
// Store lockout end times
Map lockoutEndTimes = {};

void main() {
  var handler = const shelf.Pipeline()
      .addMiddleware(shelf.logRequests())
      .addHandler(_echoRequest);

  io.serve(handler, 'localhost', 8080);
}

shelf.Response _echoRequest(shelf.Request request) {
  var username = request.headers['username'];
  var password = request.headers['password'];

  // Check if user is locked out
  if (lockoutEndTimes.containsKey(username) && DateTime.now().isBefore(lockoutEndTimes[username])) {
    return shelf.Response.forbidden('Too many failed attempts. Try again later.');
  }

  // Hash the password
  var bytes = utf8.encode(password);
  var digest = sha256.convert(bytes);

  if (username == 'admin' && digest.toString() == 'hashed_password') {
    // Reset failed attempts and lockout end time
    failedAttempts.remove(username);
    lockoutEndTimes.remove(username);
    return shelf.Response.ok('Authenticated');
  } else {
    // Increment failed attempts
    failedAttempts[username] = (failedAttempts[username] ?? 0) + 1;
    // If failed attempts exceed limit, lock account
    if (failedAttempts[username] >= 5) {
      lockoutEndTimes[username] = DateTime.now().add(Duration(minutes: 15));
    }
    return shelf.Response.forbidden('Not authenticated');
  }
}
        
        

The updated code includes several measures to protect against brute force attacks:

- Limit on failed login attempts: The failedAttempts map stores the number of failed login attempts for each user. If a user fails to log in 5 times, their username is added to the lockoutEndTimes map, which stores the time when the user's lockout period ends. The user cannot log in again until after this time.

- Increasing delay after failed attempts: The lockout period increases each time the user fails to log in. This makes brute force attacks more time-consuming and less feasible.

- Password hashing: The user's password is hashed using the SHA-256 algorithm before it is compared with the stored password hash. This ensures that even if an attacker manages to obtain the password hash, they cannot reverse-engineer the original password.

Please note that this code does not include CAPTCHA or multi-factor authentication, as these features are not typically implemented at the level of a single request handler. They would require additional infrastructure, such as a database to store CAPTCHA solutions and a way to send authentication codes to users.

Also, the code does not store IP addresses of users who have multiple failed login attempts. This would require access to a database or similar persistent storage, which is not shown in the code. However, you could easily add this feature by creating another map to store IP addresses and their associated failed attempt counts.

References