Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mitigate against CVE-2016-3714 (ImageTragick) #1934

Merged
merged 1 commit into from Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* Add Indonesian, Taiwanese and Chinese i18n translations for errors (@saveav, @st0012, @msyesyan)

### Changed
* Use the MimeMagic gem to inspect file headers for the mime type. This allows for mitigation of CVE-2016-3714, in combination with a `content_type_whitelist`. (@locriani [#1934](https://github.com/carrierwaveuploader/carrierwave/pull/1934))
* [BREAKING CHANGE] Allow non-ASCII filename by default (@shuhei [#1772](https://github.com/carrierwaveuploader/carrierwave/pull/1772))
* [BREAKING CHANGE] `to_json` behavior changed when serializing an uploader (@jnicklas and @lisarutan [#1481](https://github.com/carrierwaveuploader/carrierwave/pull/1481))
* [BREAKING CHANGE] Rename `extension_white_list` ~> `extension_whitelist` (@mehlah [#1819](https://github.com/carrierwaveuploader/carrierwave/pull/1819))
Expand Down
16 changes: 16 additions & 0 deletions README.md
Expand Up @@ -245,6 +245,22 @@ class NoJsonUploader < CarrierWave::Uploader::Base
end
```

### CVE-2016-3714 (ImageTragick)
This version of CarrierWave has the ability to mitigate CVE-2016-3714. However, you **MUST** set a content_type_whitelist in your uploaders for this protection to be effective, and you **MUST** either disable ImageMagick's default SVG delegate or use the RSVG delegate for SVG processing.


A valid whitelist that will restrict your uploader to images only, and mitigate the CVE is:

```ruby
class MyUploader < CarrierWave::Uploader::Base
def content_type_whitelist
[/image\//]
end
end
```

**WARNING**: A `content_type_whitelist` is the only form of whitelist or blacklist supported by CarrierWave that can effectively mitigate against CVE-2016-3714. Use of `extension_type_whitelist` will not inspect the file headers, and thus still leaves your application open to the vulnerability.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo here. extension_type_whitelist should be just extension_whitelist.


### Filenames and unicode chars

Another security issue you should care for is the file names (see
Expand Down
2 changes: 2 additions & 0 deletions carrierwave.gemspec
Expand Up @@ -24,6 +24,8 @@ Gem::Specification.new do |s|
s.add_dependency "activesupport", ">= 4.0.0"
s.add_dependency "activemodel", ">= 4.0.0"
s.add_dependency "mime-types", ">= 1.16"
s.add_dependency "mimemagic", ">= 0.3.0"

if RUBY_ENGINE == 'jruby'
s.add_development_dependency 'activerecord-jdbcpostgresql-adapter'
else
Expand Down
27 changes: 21 additions & 6 deletions lib/carrierwave/sanitized_file.rb
@@ -1,5 +1,6 @@
require 'pathname'
require 'active_support/core_ext/string/multibyte'
require 'mimemagic'

begin
# Use mime/types/columnar if available, for reduced memory usage
Expand Down Expand Up @@ -264,12 +265,10 @@ def to_file
# [String] the content type of the file
#
def content_type
return @content_type if @content_type
if @file.respond_to?(:content_type) and @file.content_type
@content_type = @file.content_type.to_s.chomp
elsif path
@content_type = ::MIME::Types.type_for(path).first.to_s
end
@content_type ||=
existing_content_type ||
mime_magic_content_type ||
mime_types_content_type
end

##
Expand Down Expand Up @@ -329,6 +328,22 @@ def sanitize(name)
return name.mb_chars.to_s
end

def existing_content_type
if @file.respond_to?(:content_type) && @file.content_type
@file.content_type.to_s.chomp
end
end

def mime_magic_content_type
MimeMagic.by_magic(File.open(path)).try(:type) if path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we can't use the file object instead as an argument for by_magic? I am trying to use this fix to support StringIO objects I am storing through CarrierWave, but it does not work due to lack of file path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also causes a file handle to be left open. When upgrading from 0.11.0 to 0.11.1, all my upload tests started to fail as I always check all files are closed https://github.com/obduk/cms/blob/master/spec/support/all/bad_tests.rb#L2

I tracked it down to this code, which as also been backported in to 0.11.1 in #1936

I don't mind fixing it, but don't want to cause collisions with lines that people are already working on?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to do it. We still don't have a proper solution. Would appreciate it very much.

rescue Errno::ENOENT
nil
end

def mime_types_content_type
::MIME::Types.type_for(path).first.to_s if path
end

def split_extension(filename)
# regular expressions to try for identifying extensions
extension_matchers = [
Expand Down
Binary file added spec/fixtures/zip.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions spec/sanitized_file_spec.rb
Expand Up @@ -202,6 +202,21 @@

expect(sanitized_file.content_type).to eq("image/jpeg")
end

it "does not allow spoofing of the mime type" do
file = File.open(file_path("zip.png"))

sanitized_file = CarrierWave::SanitizedFile.new(file)
expect { sanitized_file.content_type }.not_to raise_error

expect(sanitized_file.content_type).to eq("application/zip")
end

it "does not raise an error if the path is not present" do
sanitized_file = CarrierWave::SanitizedFile.new(nil)

expect { sanitized_file.content_type }.not_to raise_error
end
end

describe "#content_type=" do
Expand Down
2 changes: 1 addition & 1 deletion spec/uploader/content_type_blacklist_spec.rb
Expand Up @@ -31,7 +31,7 @@
end

it "raises an integrity error if the file has a blacklisted content type" do
allow(uploader).to receive(:content_type_blacklist).and_return(['image/gif'])
allow(uploader).to receive(:content_type_blacklist).and_return(['image/png'])

expect { uploader.cache!(ruby_file) }.to raise_error(CarrierWave::IntegrityError)
end
Expand Down
2 changes: 1 addition & 1 deletion spec/uploader/content_type_whitelist_spec.rb
Expand Up @@ -25,7 +25,7 @@
let(:bork_file) { File.open(file_path('bork.txt')) }

it "does not raise an integrity error when the file has a whitelisted content type" do
allow(uploader).to receive(:content_type_whitelist).and_return(['image/gif'])
allow(uploader).to receive(:content_type_whitelist).and_return(['image/png'])

expect { uploader.cache!(ruby_file) }.not_to raise_error
end
Expand Down