CVE-2026-34831
Executive Summary
CVE-2026-34831 is a vulnerability in the Rack Ruby web server interface affecting the Rack::Files middleware. The flaw arises from an incorrect calculation of the Content-Length header when serving 404 error pages that include multibyte UTF-8 characters. Specifically, Rack::Files#fail uses String#size (which counts characters) instead of String#bytesize (which counts bytes) to set the Content-Length header. This causes a mismatch between the declared content length and the actual bytes transmitted over the wire.
An attacker can exploit this by requesting a non-existent path containing percent-encoded UTF-8 characters, which are reflected in the 404 response body. The resulting HTTP response desynchronization can lead to request smuggling or cache poisoning in deployments that rely on the incorrect Content-Length value. The vulnerability has been patched in Rack versions 2.2.23, 3.1.21, and 3.2.6.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability affects the Rack gem, a core component of the Ruby ecosystem. Rack is a dependency for many Ruby web frameworks, including Ruby on Rails, Sinatra, and others. The supply chain risk is significant because a vulnerable Rack version can be pulled into applications via Gemfile.lock or transitive dependencies. Organizations should audit their Ruby applications for affected Rack versions and update immediately. The RubyGems package manager is the primary distribution channel, and the fix is available via gem update rack.
CVSS Breakdown & Threat Level
- CVSS Score: 4.8 (Medium)
- Attack Vector: Network (AV:N)
- Attack Complexity: Low (AC:L)
- Privileges Required: None (PR:N)
- User Interaction: None (UI:N)
- Scope: Unchanged (S:U)
- Confidentiality Impact: None (C:N)
- Integrity Impact: Low (I:L)
- Availability Impact: Low (A:L)
The medium severity reflects the low complexity of exploitation and the potential for response desynchronization, which can lead to request smuggling or cache poisoning. However, the impact is limited to integrity and availability, with no direct data exposure.
Deep Dive & Mechanics
The vulnerability is located in the Rack::Files middleware, specifically in the fail method that generates 404 error responses. When a requested path is not found, the method constructs a response body that includes the requested path. The Content-Length header is then set using String#size, which returns the number of characters in the string, not the number of bytes.
In Ruby, String#size returns the character count, which can be less than the byte count for multibyte UTF-8 characters. For example, the character "é" (U+00E9) is 2 bytes in UTF-8 but counts as 1 character. If an attacker requests a path containing percent-encoded UTF-8 characters (e.g., /%C3%A9), the decoded path is reflected in the 404 body. The Content-Length header will be set to the character count, but the actual bytes sent will be larger.
This mismatch causes HTTP response desynchronization. A proxy or load balancer that trusts the Content-Length header may interpret the response incorrectly, potentially allowing an attacker to inject malicious content into subsequent responses (request smuggling) or poison caches.
The fix involves replacing String#size with String#bytesize in the fail method, ensuring the Content-Length header accurately reflects the byte count of the response body.
Vulnerable vs. Patched Code
Vulnerable Code (Rack < 2.2.23, < 3.1.21, < 3.2.6):
# In Rack::Files#fail
def fail(status, body)
body = body.dup
content_length = body.size # Vulnerable: uses character count
[status, { "Content-Type" => "text/plain", "Content-Length" => content_length.to_s }, [body]]
end
Patched Code (Rack >= 2.2.23, >= 3.1.21, >= 3.2.6):
# In Rack::Files#fail
def fail(status, body)
body = body.dup
content_length = body.bytesize # Patched: uses byte count
[status, { "Content-Type" => "text/plain", "Content-Length" => content_length.to_s }, [body]]
end
The change is minimal but critical. By using bytesize instead of size, the Content-Length header now correctly reflects the actual number of bytes in the response body, preventing HTTP response desynchronization.
Exploitation & Lab Setup
Red Team
Prerequisites:
- A Ruby application using Rack with
Rack::Filesmiddleware (e.g., a simple file server). - Vulnerable Rack version (< 2.2.23, < 3.1.21, < 3.2.6).
Exploitation Steps:
Identify a vulnerable endpoint: The application must serve static files via
Rack::Files. For example, a Rails app withconfig.public_file_server.enabled = true.Craft a malicious request: Send a GET request to a non-existent path containing percent-encoded UTF-8 characters. For example:
GET /%C3%A9 HTTP/1.1 Host: vulnerable-app.comObserve the response: The 404 response will have a
Content-Lengthheader that is smaller than the actual body size. For example, if the path/%C3%A9decodes to/é(2 bytes), the body might be "File not found: /é" (20 characters, 21 bytes). TheContent-Lengthwill be set to 20, but the actual bytes sent are 21.Exploit desynchronization: In a chain with a proxy or load balancer, the attacker can send a second request that the proxy interprets as part of the first response, leading to request smuggling. For example:
POST / HTTP/1.1 Host: vulnerable-app.com Content-Length: 0 GET /admin HTTP/1.1 Host: vulnerable-app.comThe smuggled request may bypass authentication or access restricted resources.
Lab Setup:
Use Docker to set up a vulnerable environment:
FROM ruby:3.0 RUN gem install rack -v 3.0.0 COPY config.ru /app/ WORKDIR /app CMD ["rackup", "--host", "0.0.0.0", "--port", "4567"]config.ru:require 'rack' require 'rack/files' run Rack::Files.new("/public")Create a
/publicdirectory with some files.Test with curl:
curl -v "http://localhost:4567/%C3%A9"
Blue Team
Detection:
- Monitor for requests with percent-encoded UTF-8 characters in paths that return 404 errors.
- Look for anomalies in
Content-Lengthheaders compared to actual response body sizes. - Use WAF rules to block paths containing percent-encoded multibyte characters (e.g.,
%C2,%C3,%E0-%EF).
Mitigation:
- Immediately update Rack to patched versions (2.2.23, 3.1.21, 3.2.6 or later).
- If patching is not possible, disable
Rack::Filesor use a reverse proxy (e.g., Nginx) to serve static files instead. - Implement strict input validation to reject paths with non-ASCII characters.
Indicators of Compromise (IOCs)
Network IOCs:
- HTTP requests with paths containing percent-encoded UTF-8 characters (e.g.,
%C3%A9,%E2%82%AC). - HTTP responses with
Content-Lengthheaders that are smaller than the actual body size. - Multiple 404 responses from the same source IP with unusual paths.
- HTTP requests with paths containing percent-encoded UTF-8 characters (e.g.,
File IOCs:
- Log entries showing
Rack::Files404 responses with multibyte paths. - Application logs with
Content-Lengthmismatches.
- Log entries showing
Behavioral IOCs:
- Unexpected request smuggling patterns (e.g., multiple requests in a single TCP connection).
- Cache poisoning events where cached responses contain unexpected content.
Detection Engineering
YARA Rule
rule CVE_2026_34831_Rack_Content_Length_Mismatch {
meta:
description = "Detects Rack::Files 404 responses with Content-Length mismatch due to multibyte characters"
author = "Valters IT"
date = "2025-04-10"
cve = "CVE-2026-34831"
severity = "medium"
strings:
$s1 = "File not found:" ascii wide nocase
$s2 = "Content-Length:" ascii wide nocase
$utf8_pattern = /(%[C-Fc-f][0-9A-Fa-f]|%[Ee][0-9A-Fa-f])/ // Matches percent-encoded multibyte UTF-8
condition:
all of ($s1, $s2) and #utf8_pattern > 0
}
Sigma Rule
title: CVE-2026-34831 - Rack::Files Content-Length Mismatch
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects HTTP requests with percent-encoded UTF-8 characters in paths that may exploit CVE-2026-34831
author: Valters IT
date: 2025-04-10
tags:
- cve.2026.34831
- attack.t1190
logsource:
category: webserver
product: apache
service: access_log
detection:
selection:
cs-uri-query|contains:
- '%C2'
- '%C3'
- '%C4'
- '%C5'
- '%C6'
- '%C7'
- '%C8'
- '%C9'
- '%CA'
- '%CB'
- '%CC'
- '%CD'
- '%CE'
- '%CF'
- '%D0'
- '%D1'
- '%D2'
- '%D3'
- '%D4'
- '%D5'
- '%D6'
- '%D7'
- '%D8'
- '%D9'
- '%DA'
- '%DB'
- '%DC'
- '%DD'
- '%DE'
- '%DF'
- '%E0'
- '%E1'
- '%E2'
- '%E3'
- '%E4'
- '%E5'
- '%E6'
- '%E7'
- '%E8'
- '%E9'
- '%EA'
- '%EB'
- '%EC'
- '%ED'
- '%EE'
- '%EF'
sc-status: 404
condition: selection
falsepositives:
- Legitimate use of UTF-8 characters in URLs (e.g., internationalized domain names)
level: medium
Suricata Rule
alert http any any -> any any (msg:"CVE-2026-34831 - Rack::Files Content-Length Mismatch Exploit Attempt"; flow:to_server; content:"GET"; http_method; content:"%C3"; http_uri; pcre:"/(%[C-Fc-f][0-9A-Fa-f]|%[Ee][0-9A-Fa-f])/R"; classtype:attempted-recon; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Patch Rack: Update to version 2.2.23, 3.1.21, 3.2.6, or later. Run
gem update rackor update yourGemfileaccordingly. - Restart Services: After updating, restart all Ruby web servers (e.g., Puma, Unicorn, Passenger) to apply the fix.
Long-Term Mitigations:
- Use a Reverse Proxy: Serve static files via Nginx or Apache instead of
Rack::Files. This reduces the attack surface. - Input Validation: Implement WAF rules to block requests with percent-encoded multibyte characters in paths.
- Monitor Logs: Enable detailed logging for 404 responses and monitor for anomalies in
Content-Lengthheaders. - Regular Updates: Keep all Ruby gems and dependencies up to date to mitigate future vulnerabilities.
Workarounds (if patching is delayed):
- Disable
Rack::Filesand use an alternative static file serving mechanism. - Override the
failmethod in your application to usebytesize:module Rack class Files def fail(status, body) body = body.dup content_length = body.bytesize [status, { "Content-Type" => "text/plain", "Content-Length" => content_length.to_s }, [body]] end end end
Valters IT
Securing Digital Frontiers
{/* [Security Hash: 56DC1CAC66537098183DE986] */}