SECURITY: protect upload params, only allow very strict filenames

This commit is contained in:
Sam 2016-12-19 10:16:18 +11:00
parent 30e0154e5d
commit 15b5fddd49
4 changed files with 75 additions and 0 deletions

View File

@ -4,6 +4,9 @@ class UploadsController < ApplicationController
def create
type = params.require(:type)
raise Discourse::InvalidAccess.new unless type =~ /^[a-z\-\_]{1,100}$/
file = params[:file] || params[:files].try(:first)
url = params[:url]
client_id = params[:client_id]
@ -73,6 +76,7 @@ class UploadsController < ApplicationController
# convert pasted images to HQ jpegs
if filename == "blob.png" && SiteSetting.convert_pasted_images_to_hq_jpg
jpeg_path = "#{File.dirname(tempfile.path)}/blob.jpg"
OptimizedImage.ensure_safe_paths!(tempfile.path, jpeg_path)
`convert #{tempfile.path} -quality #{SiteSetting.convert_pasted_images_quality} #{jpeg_path}`
# only change the format of the image when JPG is at least 5% smaller
if File.size(jpeg_path) < File.size(tempfile.path) * 0.95

View File

@ -97,7 +97,24 @@ class OptimizedImage < ActiveRecord::Base
!(url =~ /^(https?:)?\/\//)
end
def self.safe_path?(path)
# this matches instructions which call #to_s
path = path.to_s
return false if path != File.expand_path(path)
return false if path !~ /\A[_\-a-zA-Z0-9\.\/]+\z/m
true
end
def self.ensure_safe_paths!(*paths)
paths.each do |path|
raise Discourse::InvalidAccess unless safe_path?(path)
end
end
def self.resize_instructions(from, to, dimensions, opts={})
ensure_safe_paths!(from, to)
# NOTE: ORDER is important!
%W{
convert
@ -115,6 +132,8 @@ class OptimizedImage < ActiveRecord::Base
end
def self.resize_instructions_animated(from, to, dimensions, opts={})
ensure_safe_paths!(from, to)
%W{
gifsicle
--colors=256
@ -126,6 +145,8 @@ class OptimizedImage < ActiveRecord::Base
end
def self.crop_instructions(from, to, dimensions, opts={})
ensure_safe_paths!(from, to)
%W{
convert
#{from}[0]
@ -141,6 +162,8 @@ class OptimizedImage < ActiveRecord::Base
end
def self.crop_instructions_animated(from, to, dimensions, opts={})
ensure_safe_paths!(from, to)
%W{
gifsicle
--crop 0,0+#{dimensions}
@ -152,6 +175,8 @@ class OptimizedImage < ActiveRecord::Base
end
def self.downsize_instructions(from, to, dimensions, opts={})
ensure_safe_paths!(from, to)
%W{
convert
#{from}[0]

View File

@ -33,6 +33,20 @@ describe UploadsController do
})
end
it 'fails if type is invalid' do
xhr :post, :create, file: logo, type: "invalid type cause has space"
expect(response.status).to eq 403
xhr :post, :create, file: logo, type: "\\invalid"
expect(response.status).to eq 403
xhr :post, :create, file: logo, type: "invalid."
expect(response.status).to eq 403
xhr :post, :create, file: logo, type: "toolong"*100
expect(response.status).to eq 403
end
it 'is successful with an image' do
Jobs.expects(:enqueue).with(:create_thumbnails, anything)

View File

@ -5,6 +5,38 @@ describe OptimizedImage do
let(:upload) { build(:upload) }
before { upload.id = 42 }
describe ".safe_path?" do
it "correctly detects unsafe paths" do
expect(OptimizedImage.safe_path?("/path/A-AA/22_00.TIFF")).to eq(true)
expect(OptimizedImage.safe_path?("/path/AAA/2200.TIFF")).to eq(true)
expect(OptimizedImage.safe_path?("/tmp/a.png")).to eq(true)
expect(OptimizedImage.safe_path?("../a.png")).to eq(false)
expect(OptimizedImage.safe_path?("/tmp/a.png\\test")).to eq(false)
expect(OptimizedImage.safe_path?("/tmp/a.png\\test")).to eq(false)
expect(OptimizedImage.safe_path?("/path/\u1000.png")).to eq(false)
expect(OptimizedImage.safe_path?("/path/x.png\n")).to eq(false)
expect(OptimizedImage.safe_path?("/path/x.png\ny.png")).to eq(false)
expect(OptimizedImage.safe_path?("/path/x.png y.png")).to eq(false)
expect(OptimizedImage.safe_path?(nil)).to eq(false)
end
end
describe "ensure_safe_paths!" do
it "raises nothing on safe paths" do
expect {
OptimizedImage.ensure_safe_paths!("/a.png", "/b.png")
}.not_to raise_error
end
it "raises nothing on paths" do
expect {
OptimizedImage.ensure_safe_paths!("/a.png", "/b.png", "c.png")
}.to raise_error(Discourse::InvalidAccess)
end
end
describe ".local?" do
def local(url)