|
| 1 | +--- |
| 2 | +layout: advisory |
| 3 | +title: 'CVE-2026-44587 (carrierwave): CarrierWave has a denylisted_content_type bypass |
| 4 | + via Unescaped Regex Metacharacters' |
| 5 | +comments: false |
| 6 | +categories: |
| 7 | +- carrierwave |
| 8 | +advisory: |
| 9 | + gem: carrierwave |
| 10 | + cve: 2026-44587 |
| 11 | + ghsa: 7g26-2qgj-chfg |
| 12 | + url: https://www.cve.org/CVERecord?id=CVE-2026-44587 |
| 13 | + title: CarrierWave has a denylisted_content_type bypass via Unescaped Regex Metacharacters |
| 14 | + date: 2026-05-27 |
| 15 | + description: |- |
| 16 | + ### Summary |
| 17 | +
|
| 18 | + CarrierWave's content_type_denylist check fails to escape regex |
| 19 | + metacharacters in string entries, causing the denylist to silently |
| 20 | + not match the content types it is intended to block. |
| 21 | +
|
| 22 | + **Note**: CarrierWave is aware `#content_type_denylist is deprecated |
| 23 | + for the security reason`, but it still used by developers, and the |
| 24 | + problem here isn't denylist allows any filetype, and thats not a |
| 25 | + vulnerability in carrierwave, its an implementation problem in |
| 26 | + developers using CarrierWave, the problem is its denylist entries |
| 27 | + are interpolated directly into a regex without `Regexp.quote` or |
| 28 | + anchoring. The denylist is still useful when developers want to |
| 29 | + ban specific content types but allow everything else. |
| 30 | +
|
| 31 | + ### Details |
| 32 | +
|
| 33 | + In `lib/carrierwave/uploader/content_type_denylist.rb:57`, string |
| 34 | + denylist entries are interpolated directly into a regex without |
| 35 | + `Regexp.quote` or anchoring: |
| 36 | +
|
| 37 | + ```ruby |
| 38 | + def denylisted_content_type?(denylist, content_type) |
| 39 | + Array(denylist).any? { |item| content_type =~ /#{item}/ } |
| 40 | + end |
| 41 | +
|
| 42 | + The entry "image/svg+xml" becomes the regex /image\/svg+xml/ where + |
| 43 | + is a quantifier meaning "one or more g", not a literal +. This |
| 44 | + regex never matches the real MIME type "image/svg+xml" which contains |
| 45 | + a literal +. This is inconsistent with the allowlist implementation |
| 46 | + at lib/carrierwave/uploader/content_type_allowlist.rb:53-57, which |
| 47 | + correctly applies both Regexp.quote and a \A anchor: |
| 48 | +
|
| 49 | + rubydef allowlisted_content_type?(allowlist, content_type) |
| 50 | + Array(allowlist).any? do |item| |
| 51 | + item = Regexp.quote(item) if item.class != Regexp |
| 52 | + content_type =~ /\A#{item}/ |
| 53 | + end |
| 54 | + end |
| 55 | + ``` |
| 56 | +
|
| 57 | + Other affected MIME types include `application/xhtml+xml` and any |
| 58 | + type containing regex metacharacters. |
| 59 | +
|
| 60 | + Fix: Apply Regexp.quote for string entries and anchor with \A, |
| 61 | + matching the existing allowlist implementation: |
| 62 | +
|
| 63 | + ``` |
| 64 | + rubydef denylisted_content_type?(denylist, content_type) |
| 65 | + Array(denylist).any? do |item| |
| 66 | + item = Regexp.quote(item) if item.class != Regexp |
| 67 | + content_type =~ /\A#{item}/ |
| 68 | + end |
| 69 | + end |
| 70 | + ``` |
| 71 | + ### Impact |
| 72 | +
|
| 73 | + Any application that uses content_type_denylist to block image/svg+xml |
| 74 | + — the most common use case, specifically to prevent stored XSS — is |
| 75 | + silently unprotected. |
| 76 | + cvss_v3: 4.7 |
| 77 | + patched_versions: |
| 78 | + - "~> 2.2.7" |
| 79 | + - ">= 3.1.3" |
| 80 | + related: |
| 81 | + url: |
| 82 | + - https://www.cve.org/CVERecord?id=CVE-2026-44587 |
| 83 | + - https://github.com/carrierwaveuploader/carrierwave/releases |
| 84 | + - https://github.com/carrierwaveuploader/carrierwave/commit/21221cc6e260633f7da78c6133a88666a5529d27 |
| 85 | + - https://github.com/carrierwaveuploader/carrierwave/security/advisories/GHSA-7g26-2qgj-chfg |
| 86 | + - https://github.com/advisories/GHSA-7g26-2qgj-chfg |
| 87 | +--- |
0 commit comments