CVE-2026-126084: CVE-2026-126084: Unsigned Content-Type in Rails Active Storage GCS Direct Uploads

2026-11-13来源 dev.toEnglish

CVE-2026-126084: Unsigned Content-Type in Rails Active Storage GCS Direct Uploads

Vulnerability ID: CVE-2026-126084
CVSS Score: 4.7
Published: 2026-11-12

A vulnerability classified under CWE-345 (Insufficient Verification of Data Authenticity) exists in the Active Storage component of Ruby on Rails prior to versions 8.1.4.1 and 8.0.5.2. When Google Cloud Storage (GCS) is configured as the storage backend with the cache_control option enabled, Active Storage switches from GCS V2 signing to V4 signing for direct upload URLs. Because the V4 signing implementation only binds the Cache-Control and Host headers, the Content-Type and Content-MD5 headers remain unsigned. An attacker can modify the Content-Type header during a direct upload request to store HTML or SVG files with active JavaScript on the GCS bucket origin, enabling Service Worker registration and interception of subsequent signed URL responses within the bucket path.

TL;DR

Active Storage configurations using Google Cloud Storage with the cache_control option generate V4 presigned upload URLs that fail to cryptographically bind the Content-Type and Content-MD5 headers. Attackers can exploit this omission to upload arbitrary HTML payloads and register malicious Service Workers under the application's bucket path on storage.googleapis.com.

⚠️ Exploit Status: POC

Technical Details

  • CVE ID: CVE-2026-126084 (GHSA-3w93-6hmw-whh4)
  • Weakness Class: CWE-345 (Insufficient Verification of Data Authenticity)
  • CVSS v3.1 Score: 4.7 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N)
  • EPSS Score: 0.14% (3.38th Percentile)
  • Affected Component: ActiveStorage::Service::GCSService#url_for_direct_upload
  • Security Impact: Stored HTML/JS execution and Service Worker interception on GCS bucket path
  • Patched Versions: Rails 8.1.4.1, Rails 8.0.5.2
  • CISA KEV Status: Not Listed

Affected Systems

  • Ruby on Rails (activestorage gem) >= 8.1.0, < 8.1.4.1
  • Ruby on Rails (activestorage gem) < 8.0.5.2
  • activestorage (Ruby on Rails): >= 8.1.0, < 8.1.4.1 (Fixed in: 8.1.4.1)
  • activestorage (Ruby on Rails): < 8.0.5.2 (Fixed in: 8.0.5.2)

Code Analysis

Commit: 8d93f6a

Enforce V2 signing for GCS direct upload URLs on Rails 8.1 branch to bind Content-Type and Content-MD5 headers.

--- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -91,25 +91,14 @@ def exist?(key) def url_for_direct_upload(key, expires_in:, checksum:, custom_metadata: {}, **) instrument :url, key: key do |payload| - headers = {} - version = :v2 - - if @config[:cache_control].present? - headers["Cache-Control"] = @config[:cache_control] - # v2 signing doesn't support non `x-goog-` headers. Only switch to v4 signing - # if necessary for back-compat; v4 limits the expiration of the URL to 7 days - # whereas v2 has no limit - version = :v4 - end - - headers.merge!(custom_metadata_headers(custom_metadata)) - + # V2 signatures bind the Content-Type (empty) and Content-MD5 headers. GCS leaves headers + # other than x-goog-* out of V2 signatures, so the client sends Cache-Control unsigned. args = { content_md5: checksum, expires: expires_in, - headers: headers, + headers: custom_metadata_headers(custom_metadata), method: "PUT", - version: version, + version: :v2, } 

Commit: ddc08ba

Enforce V2 signing for GCS direct upload URLs on Rails 8.0 branch to bind Content-Type and Content-MD5 headers.

--- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -91,25 +91,14 @@ def exist?(key) def url_for_direct_upload(key, expires_in:, checksum:, custom_metadata: {}, **) instrument :url, key: key do |payload| - headers = {} - version = :v2 - - if @config[:cache_control].present? - headers["Cache-Control"] = @config[:cache_control] - # v2 signing doesn't support non `x-goog-` headers. Only switch to v4 signing - # if necessary for back-compat; v4 limits the expiration of the URL to 7 days - # whereas v2 has no limit - version = :v4 - end - - headers.merge!(custom_metadata_headers(custom_metadata)) - + # V2 signatures bind the Content-Type (empty) and Content-MD5 headers. GCS leaves headers + # other than x-goog-* out of V2 signatures, so the client sends Cache-Control unsigned. args = { content_md5: checksum, expires: expires_in, - headers: headers, + headers: custom_metadata_headers(custom_metadata), method: "PUT", - version: version, + version: :v2, } 

Mitigation Strategies

  • Upgrade Ruby on Rails and Active Storage to patched releases (8.1.4.1 or 8.0.5.2) to enforce GCS V2 signature binding for Content-Type and Content-MD5 headers.
  • Disable the cache_control configuration option in config/storage.yml for Google Cloud Storage services as an immediate workaround.
  • Audit existing GCS bucket objects against the Rails active_storage_blobs table to identify and purge objects uploaded with mismatched or executable MIME types (e.g., text/html, text/javascript).

Remediation Steps:

  1. Modify the application Gemfile to require rails >= 8.1.4.1 (for 8.1.x) or >= 8.0.5.2 (for 8.0.x).
  2. Run 'bundle update rails activestorage' and redeploy the application.
  3. If upgrading is delayed, remove the 'cache_control' attribute from the GCS configuration block in config/storage.yml and restart the Rails server.
  4. Inspect GCS bucket metadata for any stored objects with Content-Type values of 'text/html', 'application/xhtml+xml', 'image/svg+xml', or 'text/javascript' that were uploaded via direct upload.

References

Read the full report for CVE-2026-126084 on our website for more details including interactive diagrams and full exploit analysis.

原文:https://www.a8q9.cn/en/article/cve-2026-126084-cve-2026-126084-unsigned-content-type-in-rails-active-storage-gc-8e4397.html

相关文章