Asymmetric denial of service - Content length - Ruby

Asymmetric denial of service - Content length - Ruby

Need

Prevention of Content-Length field manipulation and resource exhaustion

Context

  • Usage of Ruby for building dynamic and object-oriented applications
  • Usage of Ruby for building dynamic and object-oriented applications

Description

Non compliant code

        # vulnerable_controller.rb
class VulnerableController < ApplicationController
  def create
    # This is where the vulnerability lies.
    # The server will read the Content-Length from the request header and wait for that amount of data to arrive.
    # If an attacker sends a very high value in the Content-Length field, the server will keep waiting for the data to arrive, thus keeping the connection open and potentially causing a denial of service.
    content_length = request.headers['Content-Length'].to_i
    data = request.body.read(content_length)

    # process the data...
  end
end
        
        

The code above represents a controller in a Ruby on Rails application. The create method is vulnerable to an asymmetric denial of service attack via the Content-Length field in the request header.

When a POST request is made to this endpoint, the server reads the Content-Length from the request header and waits for that amount of data to arrive before it starts processing the request. This is done by the line content_length = request.headers['Content-Length'].to_i and data = request.body.read(content_length).

An attacker can exploit this vulnerability by sending a very high value in the Content-Length field. The server will then keep waiting for the data to arrive, thus keeping the connection open. If the attacker does this with a large number of connections, it can cause a denial of service.

The vulnerability arises from the fact that the server trusts the Content-Length value in the request header without any validation or limit checks. This allows an attacker to trick the server into waiting for more data than it should, thus tying up resources and potentially causing a denial of service.

Steps

  • Validate the Content-Length value: Before reading the request body, validate the Content-Length value to ensure it is within a reasonable range. Reject requests with excessively large Content-Length values.
  • Implement a timeout mechanism: Set a timeout for the request processing. If the data does not arrive within a reasonable time, terminate the connection.
  • Implement rate limiting: Implement rate limiting to prevent an attacker from repeatedly sending requests with high Content-Length values.
  • Implement request size limits: Set a maximum limit for the request size to prevent excessive resource consumption.
  • Use streaming or chunked transfer encoding: Instead of reading the entire request body at once, consider using streaming or chunked transfer encoding to process the data in smaller chunks.
  • Implement request validation: Validate the request data to ensure it is in the expected format and does not contain any malicious content.
  • Keep server configurations up to date: Regularly update the server configurations and dependencies to ensure any known vulnerabilities are patched.
  • Implement monitoring and logging: Implement monitoring and logging mechanisms to detect and track any suspicious or malicious requests.
  • Educate developers: Provide training and education to developers on secure coding practices and common vulnerabilities to prevent similar issues in the future.

Compliant code

        # vulnerable_controller.rb
class VulnerableController < ApplicationController
  MAX_CONTENT_LENGTH = 1000000 # Set a reasonable maximum limit for the content length
  TIMEOUT = 10 # Set a reasonable timeout value

  def create
    content_length = request.headers['Content-Length'].to_i

    # Validate the Content-Length value
    if content_length > MAX_CONTENT_LENGTH
      render json: { error: 'Content-Length is too large' }, status: 413
      return
    end

    # Set a timeout for the request processing
    Timeout.timeout(TIMEOUT) do
      data = request.body.read(content_length)

      # process the data...
    end
  rescue Timeout::Error
    render json: { error: 'Request timed out' }, status: 408
  end
end
        
        

The updated code includes several measures to mitigate the risk of an asymmetric denial of service attack via the Content-Length field.

Firstly, a maximum limit for the Content-Length is set ( MAX_CONTENT_LENGTH). If a request comes in with a Content-Length value that exceeds this limit, the request is immediately rejected with a 413 (Payload Too Large) status code.

Secondly, a timeout ( TIMEOUT) is set for the request processing. If the data does not arrive within this time, the connection is terminated and a 408 (Request Timeout) status code is returned.

These measures help to ensure that an attacker cannot keep a connection open indefinitely by sending a very high value in the Content-Length field and then sending the data very slowly. They also help to prevent the server from being overwhelmed by excessively large amounts of data.

In addition to these measures, it would also be beneficial to implement rate limiting, request size limits, request validation, and monitoring and logging mechanisms, as well as to keep server configurations up to date and educate developers about secure coding practices and common vulnerabilities.

References