SECURITY: force IM decoder based on file extension

This commit is contained in:
Régis Hanol 2018-07-25 22:00:04 +02:00
parent 0e84024958
commit 4bf3bf6786
3 changed files with 46 additions and 15 deletions

View File

@ -118,6 +118,14 @@ class OptimizedImage < ActiveRecord::Base
end end
end end
IM_DECODERS ||= /\A(jpe?g|png|tiff?|bmp|ico)\z/i
def self.prepend_decoder!(path)
extension = File.extname(path)[1..-1]
raise Discourse::InvalidAccess unless extension[IM_DECODERS]
"#{extension}:#{path}"
end
def self.thumbnail_or_resize def self.thumbnail_or_resize
SiteSetting.strip_image_metadata ? "thumbnail" : "resize" SiteSetting.strip_image_metadata ? "thumbnail" : "resize"
end end
@ -125,6 +133,9 @@ class OptimizedImage < ActiveRecord::Base
def self.resize_instructions(from, to, dimensions, opts = {}) def self.resize_instructions(from, to, dimensions, opts = {})
ensure_safe_paths!(from, to) ensure_safe_paths!(from, to)
prepend_decoder!(from)
prepend_decoder!(to)
# NOTE: ORDER is important! # NOTE: ORDER is important!
%W{ %W{
convert convert
@ -159,6 +170,9 @@ class OptimizedImage < ActiveRecord::Base
def self.crop_instructions(from, to, dimensions, opts = {}) def self.crop_instructions(from, to, dimensions, opts = {})
ensure_safe_paths!(from, to) ensure_safe_paths!(from, to)
prepend_decoder!(from)
prepend_decoder!(to)
%W{ %W{
convert convert
#{from}[0] #{from}[0]
@ -191,6 +205,9 @@ class OptimizedImage < ActiveRecord::Base
def self.downsize_instructions(from, to, dimensions, opts = {}) def self.downsize_instructions(from, to, dimensions, opts = {})
ensure_safe_paths!(from, to) ensure_safe_paths!(from, to)
prepend_decoder!(from)
prepend_decoder!(to)
%W{ %W{
convert convert
#{from}[0] #{from}[0]

View File

@ -135,13 +135,19 @@ class UploadCreator
def convert_to_jpeg! def convert_to_jpeg!
jpeg_tempfile = Tempfile.new(["image", ".jpg"]) jpeg_tempfile = Tempfile.new(["image", ".jpg"])
OptimizedImage.ensure_safe_paths!(@file.path, jpeg_tempfile.path) from = @file.path
to = jpeg_tempfile.path
OptimizedImage.ensure_safe_paths!(from, to)
OptimizedImage.prepend_decoder!(from)
OptimizedImage.prepend_decoder!(to)
begin begin
execute_convert(@file, jpeg_tempfile) execute_convert(from, to)
rescue rescue
# retry with debugging enabled # retry with debugging enabled
execute_convert(@file, jpeg_tempfile, true) execute_convert(from, to, true)
end end
# keep the JPEG if it's at least 15% smaller # keep the JPEG if it's at least 15% smaller
@ -155,15 +161,18 @@ class UploadCreator
end end
end end
def execute_convert(input_file, output_file, debug = false) def execute_convert(from, to, debug = false)
command = ['convert', input_file.path, command = [
'-auto-orient', "convert",
'-background', 'white', from,
'-interlace', 'none', "-auto-orient",
'-flatten', "-background white",
'-quality', SiteSetting.png_to_jpg_quality.to_s] "-interlace none",
command << '-debug' << 'all' if debug "-flatten",
command << output_file.path "-quality #{SiteSetting.png_to_jpg_quality}"
]
command << "-debug all" if debug
command << to
Discourse::Utils.execute_command(*command, failure_message: I18n.t("upload.png_to_jpg_conversion_failure_message")) Discourse::Utils.execute_command(*command, failure_message: I18n.t("upload.png_to_jpg_conversion_failure_message"))
end end
@ -208,8 +217,13 @@ class UploadCreator
end end
def fix_orientation! def fix_orientation!
OptimizedImage.ensure_safe_paths!(@file.path) path = @file.path
Discourse::Utils.execute_command('convert', @file.path, '-auto-orient', @file.path)
OptimizedImage.ensure_safe_paths!(path)
OptimizedImage.prepend_decoder!(path)
Discourse::Utils.execute_command('convert', path, '-auto-orient', path)
extract_image_info! extract_image_info!
end end

View File

@ -93,7 +93,7 @@ describe OptimizedImage do
}.not_to raise_error }.not_to raise_error
end end
it "raises nothing on paths" do it "raises InvalidAccess error on paths" do
expect { expect {
OptimizedImage.ensure_safe_paths!("/a.png", "/b.png", "c.png") OptimizedImage.ensure_safe_paths!("/a.png", "/b.png", "c.png")
}.to raise_error(Discourse::InvalidAccess) }.to raise_error(Discourse::InvalidAccess)