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