Commit 263a4981 by Jon Yurek

Don't spoof-check files with no extensions

There's nothing they can be spoofing, and there's little danger of a
webserver accidentally sending a dangerous Content-Type with a file that
has no extension. If the browser chooses to interpret it in a dangerous
manner, no server-side library on earth can change that.

This isn't specifically related to RSpec, but is built off the branch
and is part of an effort to get the CI build green.
parent 743e2e00
......@@ -101,6 +101,14 @@ module Paperclip
File.extname(attachment.original_filename).gsub(/\A\.+/, "")
end
# Returns the dot+extension of the file. e.g. ".jpg" for "file.jpg"
# If the style has a format defined, it will return the format instead
# of the actual extension. If the extension is empty, no dot is added.
def dotextension attachment, style_name
ext = extension(attachment, style_name)
ext.empty? ? "" : ".#{ext}"
end
# Returns an extension based on the content type. e.g. "jpeg" for
# "image/jpeg". If the style has a specified format, it will override the
# content-type detection.
......
......@@ -13,7 +13,7 @@ module Paperclip
def cache_current_values
@content_type = ContentTypeDetector.new(@tempfile.path).detect
original_filename = @target.original_filename if @target.respond_to?(:original_filename)
original_filename ||= "data.#{extension_for(@content_type)}"
original_filename ||= "data"
self.original_filename = original_filename.strip
@size = @target.size
end
......@@ -26,11 +26,6 @@ module Paperclip
destination
end
def extension_for(content_type)
type = MIME::Types[content_type].first
type && type.extensions.first
end
end
end
......
......@@ -10,7 +10,7 @@ module Paperclip
end
def spoofed?
if @name.present? && media_type_mismatch? && mapping_override_mismatch?
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.")
true
end
......@@ -18,6 +18,14 @@ module Paperclip
private
def has_name?
@name.present?
end
def has_extension?
File.extname(@name).present?
end
def media_type_mismatch?
! supplied_file_media_types.include?(calculated_media_type)
end
......
......@@ -337,7 +337,11 @@ describe Paperclip::Attachment do
context "An attachment with :hash interpolations" do
before do
@file = StringIO.new("...\n")
@file = File.open(fixture_file("5k.png"))
end
after do
@file.close
end
it "raises if no secret is provided" do
......@@ -360,15 +364,15 @@ describe Paperclip::Attachment do
end
it "results in the correct interpolation" do
assert_equal "dummies/avatars/original/data.txt",
assert_equal "dummies/avatars/original/5k.png",
@attachment.send(:interpolate, @attachment.options[:hash_data])
assert_equal "dummies/avatars/thumb/data.txt",
assert_equal "dummies/avatars/thumb/5k.png",
@attachment.send(:interpolate, @attachment.options[:hash_data], :thumb)
end
it "results in a correct hash" do
assert_equal "e1079a5c34ddbd197ebd0280d07952d98a57fb30", @attachment.path
assert_equal "d740189bd3e49ef226fab84c8d45f7ae4126d043", @attachment.path(:thumb)
assert_equal "0a59e9142bba11576de1d353d8747b1acad5ad34", @attachment.path
assert_equal "b39a062c1e62e85a6c785ed00cf3bebf5f850e2b", @attachment.path(:thumb)
end
end
end
......
......@@ -71,6 +71,20 @@ describe Paperclip::Interpolations do
assert_equal "jpe", interpolations.content_type_extension(attachment, :style)
end
it "returns the extension of the file with a dot" do
attachment = mock
attachment.expects(:original_filename).returns("one.jpg")
attachment.expects(:styles).returns({})
assert_equal ".jpg", Paperclip::Interpolations.dotextension(attachment, :style)
end
it "returns the extension of the file without a dot if the extension is empty" do
attachment = mock
attachment.expects(:original_filename).returns("one")
attachment.expects(:styles).returns({})
assert_equal "", Paperclip::Interpolations.dotextension(attachment, :style)
end
it "returns the latter half of the content type of the extension if no match found" do
attachment = mock
attachment.expects(:content_type).at_least_once().returns('not/found')
......
......@@ -18,8 +18,8 @@ describe Paperclip::DataUriAdapter do
@subject = Paperclip.io_adapters.for(@contents)
end
it "returns a file name based on the content type" do
assert_equal "data.png", @subject.original_filename
it "returns a nondescript file name" do
assert_equal "data", @subject.original_filename
end
it "returns a content type" do
......
......@@ -9,7 +9,7 @@ describe Paperclip::StringioAdapter do
end
it "returns a file name" do
assert_equal "data.txt", @subject.original_filename
assert_equal "data", @subject.original_filename
end
it "returns a content type" do
......
......@@ -21,6 +21,11 @@ describe Paperclip::MediaTypeSpoofDetector do
assert ! Paperclip::MediaTypeSpoofDetector.using(file, "").spoofed?
end
it 'does not reject a file that does have an extension' do
file = File.open(fixture_file("empty.html"))
assert ! Paperclip::MediaTypeSpoofDetector.using(file, "data").spoofed?
end
it 'does not reject when the supplied file is an IOAdapter' do
adapter = Paperclip.io_adapters.for(File.new(fixture_file("5k.png")))
assert ! Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename).spoofed?
......
......@@ -130,7 +130,7 @@ describe Paperclip::Storage::Fog do
fog_credentials: @credentials,
fog_host: nil,
fog_file: {cache_control: 1234},
path: ":attachment/:basename.:extension",
path: ":attachment/:basename:dotextension",
storage: :fog
}
......@@ -227,7 +227,7 @@ describe Paperclip::Storage::Fog do
end
it "provides a public url" do
assert @dummy.avatar.url =~ /^http:\/\/example\.com\/avatars\/data\.txt\?\d*$/
expect(@dummy.avatar.url).to match(/^http:\/\/example\.com\/avatars\/data\?\d*$/)
end
end
......@@ -237,7 +237,7 @@ describe Paperclip::Storage::Fog do
fog_directory: @fog_directory,
fog_credentials: @credentials,
fog_host: 'http://img%d.example.com',
path: ":attachment/:basename.:extension",
path: ":attachment/:basename:dotextension",
storage: :fog
)
@dummy = Dummy.new
......@@ -246,7 +246,7 @@ describe Paperclip::Storage::Fog do
end
it "provides a public url" do
assert @dummy.avatar.url =~ /^http:\/\/img[0123]\.example\.com\/avatars\/data\.txt\?\d*$/
expect(@dummy.avatar.url).to match(/^http:\/\/img[0123]\.example\.com\/avatars\/data\?\d*$/)
end
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment