Puma has long been the go-to Ruby/Rack web server for many developers who love high performance and parallelism. But a recent vulnerability, CVE-2024-45614, exposed a subtle yet critical issue in how Puma processes HTTP headers. If you use popular headers like X-Forwarded-For for things like identifying the original IP address of users—or for any security logic—this is an issue you need to know about.

In this post, we’ll break down how attackers could bypass your protections, what the patch does, and how you can safeguard your Rack-powered apps. No deep Ruby knowledge required!

What Is CVE-2024-45614?

CVE-2024-45614 describes a bug affecting Puma web server versions before 6.4.3 and 5.6.9. In short, Puma did not properly handle HTTP headers with underscores (_) in their names when a similar header with dashes (-) existed.

X-Real-IP

Proxies usually set these headers for security or access control, assuming they're authoritative. But Puma used to treat X-Forwarded-For and X-Forwarded_For as totally separate headers. That means a malicious client could set their own value for X-Forwarded_For—potentially clobbering what your trusted proxy put in X-Forwarded-For.

In Puma before 6.4.3:

Both values make it to your Ruby app. Some gems, middleware, or Rails itself could accidentally pick the wrong header, mistaking the attacker’s spoofed value as legit.

Suppose your app checks both headers (as some libraries might)

forwarded_ip = request.env['HTTP_X_FORWARDED_FOR'] || request.env['HTTP_X_FORWARDED_FOR']
# But attacker adds HTTP_X_FORWARDED_FOR via _ version headers:
# Now, both exist! Which wins depends on implementation.

Depending on your code or dependencies, the underscore version might get used—which is what the attacker wanted.

The Official Fix

Starting with Puma 6.4.3 and 5.6.9, the server discards any underscore headers if the same header exists in the standard dash (-) format. So, your proxy’s value always takes precedence, and malicious headers are thrown away by Puma before they ever reach your code.

Example fix logic (simplified, not actual Puma code)

if header_with_dash && header_with_underscore
  # Only keep the dash version
  discard(header_with_underscore)
end

Upgrade puma with

bundle update puma
# or specify in your Gemfile:
gem 'puma', '~> 6.4.3'

Mitigation for Nginx Users (and Others)

If you can’t upgrade right away, you can mitigate at the proxy level. Nginx has a config option called underscores_in_headers. When off, any headers with underscores are NOT passed along to your app.

nginx.conf example

http {
  underscores_in_headers off;
  # your proxy config here...
}

This is a great layer of defense, but not all proxies handle this by default.

Who Is at Risk?

- Any Puma-based Ruby/Rack app that sits behind a reverse proxy AND trusts headers like X-Forwarded-For, X-Real-IP, etc.

Anyone using libraries that fallback to underscore variants (rare, but possible).

If you use these headers for authentication, rate-limiting, geo-blocking, or admin protection, you are especially vulnerable.

Recommendations

1. Upgrade Puma to 6.4.3, 5.6.9, or later: Puma Releases

Audit your code: Make sure you aren’t trusting underscores variant headers.

4. Don’t trust user-controlled headers blindly. Always lock down what your app trusts as authentic.

References

- CVE-2024-45614 Details (NIST)
- Puma GitHub Security Advisory
- Puma Release Notes
- Nginx Policy: underscores_in_headers
- Original Commit With the Fix

Conclusion

Little differences in header names can lead to surprising and serious security weaknesses. CVE-2024-45614 shows that even modern web servers can make subtle mistakes. Don’t wait—upgrade Puma, lock down your proxies, and double-check which headers your app trusts.

Timeline

Published on: 09/19/2024 23:15:11 UTC
Last modified on: 09/26/2024 13:28:30 UTC