Mar 24, 2026·6 min read·3 visits
Unbounded HTTP Range requests in Rails Active Storage proxy mode cause excessive heap allocations. This leads to Out-of-Memory (OOM) crashes and Denial of Service. Administrators must patch to versions 7.2.3.1, 8.0.4.1, or 8.1.2.1.
Rails Active Storage is vulnerable to a denial of service attack due to improper handling of HTTP Range headers in proxy mode. By supplying an unbounded byte range, an attacker can force the application to perform excessive memory allocation, leading to process termination via the Out-Of-Memory (OOM) killer.
Ruby on Rails Active Storage facilitates file uploads and downloads, integrating cloud storage services securely. The framework includes a proxy mode feature designed to stream files directly through the Rails application rather than redirecting clients to pre-signed cloud provider URLs. This mode relies on the Blobs::ProxyController and the ActiveStorage::Streaming concern to process incoming client requests and transmit file data.
CVE-2026-33174 identifies a severe memory allocation flaw within this proxy streaming implementation. The vulnerability is classified under CWE-789, Memory Allocation with Excessive Size Value. The application fails to enforce upper bounds on the size of byte ranges requested by clients, allowing actors to manipulate the memory footprint of the Rails worker process.
By transmitting a crafted HTTP request containing an unbounded Range header, an attacker forces the application to allocate heap memory proportional to the target file's size. This excessive allocation triggers system-level memory exhaustion, invoking the Out-Of-Memory (OOM) killer to terminate the Rails process. The resulting disruption causes a reliable denial of service condition for the affected application.
The root cause of CVE-2026-33174 stems from the direct translation of user-controlled HTTP header values into memory allocation requests. When a client requests a specific byte range, the ActiveStorage::Streaming module utilizes Rack::Utils.get_byte_ranges to parse the Range header. This utility extracts the requested start and end byte offsets without validating them against application-level memory constraints.
Prior to the patch, the application attempted to read the entire specified byte range from the storage backend into a single contiguous Ruby string buffer. The Ruby interpreter fulfills this by requesting a proportional block of heap memory from the operating system. Because strings in Ruby are mutable and stored contiguously in memory, a request for a multi-gigabyte file segment requires a contiguous memory allocation matching that size.
The OS kernel monitors process memory consumption and intervenes when physical RAM and swap space are exhausted. The kernel's OOM killer heuristically selects processes consuming excessive memory and sends a SIGKILL signal to terminate them. The Rails worker process, having requested the oversized allocation, is immediately terminated, abruptly severing all active connections handled by that worker.
The architectural design of proxy mode necessitates reading data into memory before transmission, differentiating it from redirect mode where the client interacts directly with the storage provider. This fundamental design requirement amplifies the vulnerability, as the application serves as a mandatory intermediary for all data transfer, bearing the full memory cost of the transaction.
The vulnerable code path initiates within the send_blob_byte_range_data method. The original implementation parsed the range header and immediately proceeded to process the data without verifying the total size of the requested chunks.
The patched implementation introduces a mandatory validation step before processing the ranges. The patch adds the ranges_valid? method, which iterates through the requested ranges, calculates the byte difference between the start and end offsets, and sums these values.
# activestorage/app/controllers/concerns/active_storage/streaming.rb
def send_blob_byte_range_data(blob, range_header, disposition: nil)
ranges = Rack::Utils.get_byte_ranges(range_header, blob.byte_size)
return head(:range_not_satisfiable) unless ranges_valid?(ranges)
# Execution continues to stream data...
end
def ranges_valid?(ranges)
return false if ranges.blank? || ranges.all?(&:blank?)
ranges.sum { |range| range.end - range.begin } < ActiveStorage.streaming_chunk_max_size
endThis validation logic rejects requests where the aggregate size exceeds the ActiveStorage.streaming_chunk_max_size configuration variable, defaulting to 100 megabytes. By failing early and returning an HTTP 416 Range Not Satisfiable response, the application prevents the subsequent excessive heap allocation.
Exploitation requires network access to an endpoint serving files via Active Storage in proxy mode. The attacker identifies a target URL associated with a large file, such as a software binary or a high-definition media asset. No authentication is inherently required by the vulnerability, though application-specific access controls on the endpoint must be satisfied.
The attacker issues a standard HTTP GET request to the identified endpoint, appending a malicious Range header. A common payload utilizes an open-ended byte range, such as Range: bytes=0-. This syntax instructs the server to return the entire file starting from byte zero.
Upon receiving the request, the Rails worker process attempts to allocate memory for the entire file payload. For a file exceeding available system memory, this allocation blocks the worker and triggers the system OOM killer. The process is forcefully terminated, terminating the attacker's connection and any concurrent connections handled by the same worker.
The primary impact of CVE-2026-33174 is a localized denial of service affecting the target application's availability. Successful exploitation forcefully terminates the specific Rails worker handling the malicious request. This degrades the overall request processing capacity of the application cluster.
Repeated exploitation by an attacker systematically terminates replacement workers as they are spawned. This sustained attack results in a complete denial of service, rendering the application entirely unresponsive to legitimate user traffic. The CVSS vector reflects this with a High Availability metric (VA:H), while Confidentiality and Integrity remain unaffected (VC:N, VI:N).
In containerized deployment environments, such as Kubernetes or Amazon ECS, the OOM termination often triggers container restarts. Frequent container restarts introduce additional overhead, including application boot times and cache warming delays. This cascading effect exacerbates the initial denial of service, extending the duration of application unavailability.
Administrators must update the activestorage gem to a patched version immediately. The official vulnerability advisory identifies versions 7.2.3.1, 8.0.4.1, and 8.1.2.1 as containing the necessary fixes. Upgrading the gem ensures the new range validation logic is enforced across all proxy streaming endpoints.
Following the upgrade, administrators should evaluate the default ActiveStorage.streaming_chunk_max_size configuration. The 100-megabyte default provides a reasonable baseline, but environments with constrained memory limits require lower thresholds. Modifying config.active_storage.streaming_chunk_max_size = 10.megabytes restricts individual allocations to safer levels for memory-limited hardware.
For systems where immediate patching is unfeasible, administrators can deploy mitigation rules at the Web Application Firewall (WAF) or load balancer level. Rules configured to drop or strip HTTP Range headers on requests directed to /rails/active_storage/blobs/proxy/ paths eliminate the attack vector. While this mitigation breaks legitimate byte-range request functionality, it preserves application availability until the software patch is applied.
A comprehensive defense strategy also incorporates the related fix for CVE-2026-33658, which limits the total number of distinct ranges processed per request. Configuring config.active_storage.streaming_max_ranges = 1 prevents attackers from circumventing chunk size limits by requesting numerous small, adjacent ranges.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:U| Product | Affected Versions | Fixed Version |
|---|---|---|
activestorage Ruby on Rails | < 7.2.3.1 | 7.2.3.1 |
activestorage Ruby on Rails | >= 8.0.0.beta1, < 8.0.4.1 | 8.0.4.1 |
activestorage Ruby on Rails | >= 8.1.0.beta1, < 8.1.2.1 | 8.1.2.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-789 |
| Attack Vector | Network |
| CVSS v4.0 Score | 6.6 (Medium) |
| Impact | Denial of Service (Availability) |
| Exploit Status | Proof of Concept |
| CISA KEV | Not Listed |
Memory Allocation with Excessive Size Value