Mar 24, 2026·7 min read·3 visits
A parameter injection flaw in Rails Active Storage allows attackers to bypass file type validation by setting internal metadata flags (e.g., 'identified: true') during direct uploads. This enables the uploading of dangerous file types, leading to potential Stored Cross-Site Scripting (XSS).
Ruby on Rails Active Storage versions prior to 7.2.3.1, 8.0.4.1, and 8.1.2.1 contain an insecure parameter handling vulnerability in the DirectUploadsController. Attackers can inject internal state flags into the metadata JSON column during file upload initialization, bypassing server-side content type verification and enabling the upload of malicious payloads such as HTML files masquerading as benign images.
Active Storage is a core framework in Ruby on Rails responsible for attaching cloud and local files to application records. The DirectUploadsController manages the initialization phase of client-direct uploads, allowing browsers to upload files directly to backend storage services like Amazon S3 or Google Cloud Storage. During this initialization, the client provides metadata about the file, including its filename, byte size, checksum, and content type.
The vulnerability exists within the handling of the metadata parameter during the creation of an ActiveStorage::Blob record. The metadata field is a JSON-serialized column in the database used to store both user-supplied context and internal framework state. Because the framework does not sanitize the incoming metadata hash, external clients can specify internal state flags that Active Storage relies on for security and processing logic.
By manipulating these state flags, an attacker can coerce the framework into trusting a spoofed MIME type. This allows the upload of arbitrary content that bypasses application-level file type restrictions. The flaw is classified under CWE-915 (Improperly Controlled Modification of Dynamically-Determined Object Attributes) and affects multiple release lines of Ruby on Rails.
The root cause of CVE-2026-33173 is the insecure merging of untrusted client input into a structured data column used for internal state management. Active Storage tracks the processing status of a blob using specific boolean flags within the metadata hash. The most critical of these flags is identified, which indicates whether the system has definitively verified the file's MIME type via magic byte inspection.
When a client initiates a direct upload via the DirectUploadsController#create action, the controller extracts the metadata dictionary from the request payload. This dictionary is passed directly to the ActiveStorage::Blob.create_before_direct_upload! method without any filtering or sanitization. The framework blindly persists the user-supplied hash into the active_storage_blobs table.
If the incoming payload contains "identified": true, Active Storage processes subsequent interactions with the blob under the assumption that the content_type attribute has already been securely validated. The framework skips the standard MIME type detection routines. Consequently, the declared content_type is trusted unconditionally, severing the link between the actual file content and the application's verification mechanisms.
The vulnerable code path allowed arbitrary key-value pairs to enter the database. Prior to the patch, the create_before_direct_upload! method accepted the metadata argument and passed it directly to the creation routine. Any keys provided by the client were retained.
The official patch mitigates this by implementing an explicit blocklist for protected internal keys. The Rails maintainers introduced a PROTECTED_METADATA constant containing the specific flags utilized by Active Storage state machines.
# activestorage/app/models/active_storage/blob.rb
PROTECTED_METADATA = %w(analyzed identified composed)
def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil, custom_metadata: nil)
metadata = filter_metadata(metadata)
create!(
filename: filename,
byte_size: byte_size,
checksum: checksum,
content_type: content_type,
metadata: metadata,
custom_metadata: custom_metadata
)
end
private
def filter_metadata(metadata)
if metadata.is_a?(Hash)
metadata.without(*PROTECTED_METADATA)
else
metadata
end
endThe filter_metadata private method ensures that the analyzed, identified, and composed keys are stripped from the hash before persistence. While this blocklist approach effectively resolves the immediate vulnerability, developers must ensure no future internal state keys are added to the metadata hash without also updating the PROTECTED_METADATA constant.
Exploiting this vulnerability requires the attacker to have authorization to initiate direct uploads within the target application. The attacker intercepts or scripts the initial POST request to the /rails/active_storage/direct_uploads endpoint. They modify the JSON payload to include the protected metadata flags while specifying a malicious file payload and a benign content type.
The following JSON structure demonstrates the payload required to trigger the vulnerability. The attacker sets the content_type to image/png to bypass application-level validation filters, while the actual file contents uploaded to the storage provider contain an HTML payload with embedded JavaScript.
{
"blob": {
"filename": "profile_picture.html",
"byte_size": 2048,
"checksum": "[VALID_CHECKSUM]",
"content_type": "image/png",
"metadata": {
"identified": true,
"analyzed": true,
"composed": true
}
}
}Upon receiving this request, the server persists the blob record with the spoofed metadata and returns a signed direct upload URL. The attacker uploads the malicious HTML file to the provided storage URL. When the application subsequently serves this file to users, the content_type is rendered as image/png, or the file bypasses image-only restrictions. Depending on the storage configuration and content-disposition headers, a browser accessing the raw file URL may execute the HTML payload, resulting in Stored Cross-Site Scripting (XSS).
The following diagram illustrates the vulnerable direct upload flow and how the injected metadata persists through the Active Storage architecture.
This execution path demonstrates the critical failure in input validation at the boundary of the DirectUploadsController and the ActiveStorage::Blob model.
The vulnerability allows unauthenticated or authenticated users (depending on the application's upload requirements) to bypass critical file type restrictions. The assigned CVSS v4.0 vector is CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N, resulting in a Base Score of 5.3 (Medium). The impact is primarily localized to the integrity of the data processing pipeline.
The concrete security consequence is the ability to store files with mismatched MIME types and internal state flags. The primary attack vector is Stored Cross-Site Scripting (XSS). If the targeted application serves uploaded files directly from its domain without forcing a Content-Disposition: attachment header, browsers may sniff the file contents or render malicious HTML/JavaScript payloads.
Secondary impacts include the disruption of backend processing jobs. By artificially setting the analyzed flag, attackers can prevent Active Storage from generating variants (such as image thumbnails) or extracting required metadata (such as image dimensions). This can lead to application errors or layout breakage when the application attempts to render unanalyzed blobs.
The definitive remediation for CVE-2026-33173 is upgrading to the patched versions of the Ruby on Rails framework. Administrators must update their Gemfile to use Active Storage version 7.2.3.1, 8.0.4.1, or 8.1.2.1. Following the version bump, a full test suite run is required to ensure compatibility with the updated filtering logic.
If immediate patching is not technically feasible, developers can mitigate the flaw by implementing a custom before_action in the DirectUploadsController. This filter should inspect the incoming params[:blob][:metadata] parameter and forcefully remove the keys analyzed, identified, and composed before the parameter reaches the Active Storage models.
As a defense-in-depth measure, applications should enforce strict Content-Security-Policy (CSP) headers on all domains serving user-uploaded content. Additionally, configuring cloud storage buckets to serve all user uploads with the Content-Disposition: attachment header will prevent browsers from rendering malicious payloads inline, neutralizing the XSS risk regardless of the underlying metadata state.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Rails Active Storage Ruby on Rails | < 7.2.3.1 | 7.2.3.1 |
Rails Active Storage Ruby on Rails | >= 8.0.0.beta1, < 8.0.4.1 | 8.0.4.1 |
Rails Active Storage Ruby on Rails | >= 8.1.0.beta1, < 8.1.2.1 | 8.1.2.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-915 |
| Attack Vector | Network |
| CVSS v4.0 | 5.3 |
| Impact | Content Type Bypass / Stored XSS |
| Exploit Status | None |
| CISA KEV | False |
Improperly Controlled Modification of Dynamically-Determined Object Attributes