From a9099f9e2383affcdca60e86ab167216f1ec68b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 21 Dec 2015 16:08:14 +0100 Subject: [PATCH] SECURITY: ensure we never accept fake images --- app/models/upload.rb | 11 +++++++--- spec/controllers/uploads_controller_spec.rb | 21 ++++++++++++++++++++ spec/fixtures/images/fake.jpg | Bin 0 -> 2881 bytes 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 spec/fixtures/images/fake.jpg diff --git a/app/models/upload.rb b/app/models/upload.rb index 8e2150541f9..6c693730184 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -67,7 +67,7 @@ class Upload < ActiveRecord::Base def self.create_for(user_id, file, filename, filesize, options = {}) DistributedMutex.synchronize("upload_#{user_id}_#{filename}") do # do some work on images - if FileHelper.is_image?(filename) + if FileHelper.is_image?(filename) && system("identify '#{file.path}' >/dev/null 2>&1") if filename =~ /\.svg$/i svg = Nokogiri::XML(file).at_css("svg") w = svg["width"].to_i @@ -122,7 +122,7 @@ class Upload < ActiveRecord::Base upload = find_by(sha1: sha1) # make sure the previous upload has not failed - if upload && upload.url.blank? + if upload && (upload.url.blank? || is_dimensionless_image?(filename, upload.width, upload.height)) upload.destroy upload = nil end @@ -141,8 +141,9 @@ class Upload < ActiveRecord::Base upload.height = height upload.origin = options[:origin][0...1000] if options[:origin] - if FileHelper.is_image?(filename) && (upload.width == 0 || upload.height == 0) + if is_dimensionless_image?(filename, upload.width, upload.height) upload.errors.add(:base, I18n.t("upload.images.size_not_found")) + return upload end return upload unless upload.save @@ -162,6 +163,10 @@ class Upload < ActiveRecord::Base end end + def self.is_dimensionless_image?(filename, width, height) + FileHelper.is_image?(filename) && (width.blank? || width == 0 || height.blank? || height == 0) + end + def self.get_from_url(url) return if url.blank? # we store relative urls, so we need to remove any host/cdn diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 2a0ceaf22c9..4a57897e771 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -19,6 +19,13 @@ describe UploadsController do }) end + let(:fake_jpg) do + ActionDispatch::Http::UploadedFile.new({ + filename: 'fake.jpg', + tempfile: file_from_fixtures("fake.jpg") + }) + end + let(:text_file) do ActionDispatch::Http::UploadedFile.new({ filename: 'LICENSE.TXT', @@ -118,6 +125,20 @@ describe UploadsController do expect(response).to_not be_success end + it 'returns an error when it could not determine the dimensions of an image' do + Jobs.expects(:enqueue).with(:create_thumbnails, anything).never + + message = MessageBus.track_publish do + xhr :post, :create, file: fake_jpg, type: "composer" + end.first + + expect(response.status).to eq 200 + + expect(message.channel).to eq("/uploads/composer") + expect(message.data["errors"]).to be + expect(message.data["errors"][0]).to eq(I18n.t("upload.images.size_not_found")) + end + end end diff --git a/spec/fixtures/images/fake.jpg b/spec/fixtures/images/fake.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e59fa523ee03a3f7f9b9ac66eb6344d33c383837 GIT binary patch literal 2881 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 literal 0 HcmV?d00001