Symmetric denial of service - Ruby

Symmetric denial of service - Ruby

Need

Prevention of service outages caused by repeated resource-intensive requests

Context

  • Usage of Ruby 2.7 for developing Ruby applications
  • Usage of Rails framework for building web applications

Description

Non compliant code

        class VulnerableController < ApplicationController
  def index
    while true do
      # This is a resource-intensive operation
      @data = ExpensiveModel.calculate_large_dataset
      render json: @data
    end
  end
end
        
        

The above code represents a vulnerable part of a Ruby on Rails backend application. This is a controller action that continuously performs a resource-intensive operation in an infinite loop.

The index action within the VulnerableController is designed to calculate a large dataset by calling ExpensiveModel.calculate_large_dataset, a method which we can assume performs some sort of heavy computation or database operation. This operation is resource intensive and takes a long time to complete.

The issue arises from the fact that this operation is performed within an infinite loop ( while true do). This means that, once a request is made to this action, it will continuously calculate the large dataset and attempt to render it as JSON. This will consume a lot of server resources and eventually make the server unresponsive as it gets stuck in a loop of performing this resource-intensive operation.

A malicious user could exploit this vulnerability by repeatedly sending requests to this action, causing the server to become overloaded and unresponsive, effectively causing a Denial of Service (DoS) attack.

Steps

  • Identify the resource-intensive operation that is causing the denial of service.
  • Optimize the resource-intensive operation to reduce its impact on server performance.
  • Implement a rate limiting mechanism to prevent excessive requests from overwhelming the server.
  • Consider implementing caching mechanisms to reduce the need for repetitive resource-intensive operations.
  • Implement timeouts or limits on the processing time of requests to prevent long-running requests from causing denial of service.
  • Monitor server performance and resource usage to identify and address any potential bottlenecks or performance issues.

Compliant code

        class VulnerableController < ApplicationController
  before_action :rate_limit

  def index
    @data = Rails.cache.fetch('large_dataset', expires_in: 12.hours) do
      ExpensiveModel.calculate_large_dataset
    end
    render json: @data
  end

  private

  def rate_limit
    client_ip = request.remote_ip
    limit = 100
    period = 1.minute

    if Rails.cache.read(client_ip).to_i >= limit
      render json: { error: 'Rate limit exceeded' }, status: 429
    else
      Rails.cache.increment(client_ip, 1, expires_in: period)
    end
  end
end
        
        

The updated code includes several changes to address the denial of service vulnerability:

1. Rate Limiting: A before_action filter is added to the controller to implement rate limiting. This filter runs before the index action and checks if the client has exceeded a predefined limit of requests within a certain period. If the limit is exceeded, the server responds with a 429 status code (Too Many Requests) and an error message. Otherwise, the request count for the client is incremented.

2. Caching: The resource-intensive operation ( ExpensiveModel.calculate_large_dataset) is wrapped in a Rails.cache.fetch block. This means that the result of the operation is stored in the cache the first time it is executed, and subsequent requests within the cache expiry period (12 hours in this case) will retrieve the result from the cache instead of executing the operation again. This reduces the load on the server and speeds up response times.

3. Timeouts: Although not explicitly shown in the code, it is recommended to implement a timeout for the calculate_large_dataset method to prevent it from running indefinitely and causing a denial of service. This could be done at the database level (for SQL queries) or at the application level using Ruby's Timeout module.

4. Monitoring: While not shown in the code, it is recommended to monitor server performance and resource usage to identify potential bottlenecks or performance issues. This could be done using tools like New Relic or Datadog, or through built-in Rails features like the Active Support Instrumentation API.

References