Moderate severityNVD Advisory· Published Jul 10, 2015· Updated May 6, 2026
CVE-2015-2963
CVE-2015-2963
Description
The thoughtbot paperclip gem before 4.2.2 for Ruby does not consider the content-type value during media-type validation, which allows remote attackers to upload HTML documents and conduct cross-site scripting (XSS) attacks via a spoofed value, as demonstrated by image/jpeg.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
paperclipRubyGems | < 4.2.2 | 4.2.2 |
Affected products
1Patches
19aee4112f360Fix a possible security issue with spoofing
6 files changed · +54 −20
lib/paperclip/locales/en.yml+1 −1 modified@@ -2,7 +2,7 @@ en: errors: messages: in_between: "must be in between %{min} and %{max}" - spoofed_media_type: "has an extension that does not match its contents" + spoofed_media_type: "has contents that are not what they are reported to be" number: human:
lib/paperclip/media_type_spoof_detector.rb+37 −17 modified@@ -1,18 +1,21 @@ module Paperclip class MediaTypeSpoofDetector - def self.using(file, name) - new(file, name) + def self.using(file, name, content_type) + new(file, name, content_type) end - def initialize(file, name) + def initialize(file, name, content_type) @file = file @name = name + @content_type = content_type || "" end def spoofed? if has_name? && has_extension? && media_type_mismatch? && mapping_override_mismatch? - Paperclip.log("Content Type Spoof: Filename #{File.basename(@name)} (#{supplied_file_content_types}), content type discovered from file command: #{calculated_content_type}. See documentation to allow this combination.") + Paperclip.log("Content Type Spoof: Filename #{File.basename(@name)} (#{supplied_content_type} from Headers, #{content_types_from_name} from Extension), content type discovered from file command: #{calculated_content_type}. See documentation to allow this combination.") true + else + false end end @@ -27,35 +30,44 @@ def has_extension? end def media_type_mismatch? - ! supplied_file_media_types.include?(calculated_media_type) + supplied_type_mismatch? || calculated_type_mismatch? + end + + def supplied_type_mismatch? + supplied_media_type.present? && !media_types_from_name.include?(supplied_media_type) + end + + def calculated_type_mismatch? + !media_types_from_name.include?(calculated_media_type) end def mapping_override_mismatch? mapped_content_type != calculated_content_type end - def supplied_file_media_types - @supplied_file_media_types ||= MIME::Types.type_for(@name).collect(&:media_type) + + def supplied_content_type + @content_type end - def calculated_media_type - @calculated_media_type ||= calculated_content_type.split("/").first + def supplied_media_type + @content_type.split("/").first end - def supplied_file_content_types - @supplied_file_content_types ||= MIME::Types.type_for(@name).collect(&:content_type) + def content_types_from_name + @content_types_from_name ||= MIME::Types.type_for(@name) end - def calculated_content_type - @calculated_content_type ||= type_from_file_command.chomp + def media_types_from_name + @media_types_from_name ||= content_types_from_name.collect(&:media_type) end - def mapped_content_type - Paperclip.options[:content_type_mappings][filename_extension] + def calculated_content_type + @calculated_content_type ||= type_from_file_command.chomp end - def filename_extension - File.extname(@name.to_s.downcase).sub(/^\./, '').to_sym + def calculated_media_type + @calculated_media_type ||= calculated_content_type.split("/").first end def type_from_file_command @@ -65,5 +77,13 @@ def type_from_file_command "" end end + + def mapped_content_type + Paperclip.options[:content_type_mappings][filename_extension] + end + + def filename_extension + File.extname(@name.to_s.downcase).sub(/^\./, '').to_sym + end end end
lib/paperclip/validators/media_type_spoof_detection_validator.rb+1 −1 modified@@ -5,7 +5,7 @@ module Validators class MediaTypeSpoofDetectionValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) adapter = Paperclip.io_adapters.for(value) - if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename).spoofed? + if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed? record.errors.add(attribute, :spoofed_media_type) end end
NEWS+4 −0 modified@@ -1,3 +1,7 @@ +New in 4.2.2: + +* Security fix: Fix a potential security issue with spoofing + New in 4.2.1: * Improvement: Added `validate_media_type` options to allow/bypass spoof check
spec/paperclip/media_type_spoof_detector_spec.rb+10 −0 modified@@ -43,4 +43,14 @@ Paperclip.options[:content_type_mappings] = {} end end + + it "rejects a file if named .html and is as HTML, but we're told JPG" do + file = File.open(fixture_file("empty.html")) + assert Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "image/jpg").spoofed? + end + + it "does not reject is content_type is empty but otherwise checks out" do + file = File.open(fixture_file("empty.html")) + assert ! Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "").spoofed? + end end
spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb+1 −1 modified@@ -30,7 +30,7 @@ def build_validator(options = {}) Paperclip::MediaTypeSpoofDetector.stubs(:using).returns(detector) @validator.validate(@dummy) - assert_equal "has an extension that does not match its contents", @dummy.errors[:avatar].first + assert_equal I18n.t("errors.messages.spoofed_media_type"), @dummy.errors[:avatar].first end it "runs when attachment is dirty" do
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/thoughtbot/paperclip/commit/9aee4112f36058cd28d5fe4a006d6981bd1eda57nvdExploitVendor AdvisoryWEB
- jvn.jp/en/jp/JVN83881261/index.htmlnvdVendor AdvisoryWEB
- jvndb.jvn.jp/jvndb/JVNDB-2015-000088nvdVendor AdvisoryWEB
- openwall.com/lists/oss-security/2015/06/19/3nvdVendor AdvisoryWEB
- github.com/advisories/GHSA-6jvm-3j5h-79f6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-2963ghsaADVISORY
- robots.thoughtbot.com/paperclip-security-releasenvdVendor AdvisoryWEB
- web.archive.org/web/20200228084907/http://www.securityfocus.com/bid/75304ghsaWEB
- www.securityfocus.com/bid/75304nvd
News mentions
0No linked articles in our index yet.