Shrine does a lot to make your file uploads secure, but there are still a lot of security measures that could be added by the user on the application's side. This guide will try to cover all the well-known security issues, ranging from the obvious ones to not-so-obvious ones, and try to provide solutions.
Almost always you will be accepting certain types of files, and it's a good idea to create a whitelist (or a blacklist) of extensions and MIME types.
By default Shrine stores the MIME type derived from the extension, which means
it's not guaranteed to hold the actual MIME type of the the file. However, you
can load the determine_mime_type
plugin which by default uses the file
utility to determine the MIME type from magic file headers.
class MyUploader < Shrine
plugin :validation_helpers
plugin :determine_mime_type
Attacher.validate do
validate_extension_inclusion %w[jpg jpeg png gif]
validate_mime_type_inclusion %w[image/jpeg image/png image/gif]
end
end
It's a good idea to generally limit the filesize of uploaded files, so that
attackers cannot easily flood your storage. There are various layers at which
you can apply filesize limits, depending on how you're accepting uploads.
Firstly, you should probably add a filesize validation to prevent large files
from being uploaded to :store
:
class MyUploader < Shrine
plugin :validation_helpers
Attacher.validate do
validate_max_size 20*1024*1024 # 20 MB
end
end
In the following sections we talk about various strategies to prevent files from being uploaded to cache and the temporary directory.
If you're doing direct uploads with the upload_endpoint
plugin, you can pass
in the :max_size
option to reject files that are larger than the specified
limit:
plugin :upload_endpoint, max_size: 20*1024*1024 # 20 MB
If you're doing direct uploads to Amazon S3 using the presign_endpoint
plugin, you can pass in the :content_length_range
presign option:
plugin :presign_endpoint, presign_options: -> (request) do
{ content_length_range: 0..20*1024*1024 }
end
If your application is accepting file uploads, it's good practice to limit the
maximum allowed Content-Length
before calling params
for the first time,
to avoid Rack parsing the multipart request parameters and creating a Tempfile
for uploads that are obviously attempts of attacks.
if request.content_length >= 100*1024*1024 # 100MB
response.status = 413 # Request Entity Too Large
response.body = "The uploaded file was too large (maximum is 100MB)"
request.halt
end
request.params # Rack parses the multipart request params
Alternatively you can allow uploads of any size to temporary Shrine storage,
but tell Shrine to immediately delete the file if it failed validations by
loading the remove_invalid
plugin.
plugin :remove_invalid
If you want to make sure that no large files ever get to your storages, and
you don't really care about the error message, you can use the hooks
plugin
and raise an error:
class MyUploader
plugin :hooks
def before_upload(io, context)
if io.respond_to?(:read)
raise FileTooLarge if io.size >= 20*1024*1024
end
end
end
It's possible to create so-called image bombs, which are images that have a small filesize but very large dimensions. These are dangerous if you're doing image processing, since processing them can take a lot of time and memory. This makes it trivial to DoS the application which doesn't have any protection against them.
Shrine uses the fastimage gem for determining image dimensions which has built-in protection against image bombs (ImageMagick for example doesn't), but you still need to prevent those files from being attached and processed:
class MyUploader < Shrine
plugin :store_dimensions
plugin :validation_helpers
Attacher.validate do
validate_max_width 2500
validate_max_height 2500
end
end
If you're doing processing on caching, you can use the fastimage gem directly in a conditional.
When cached file is retained on validation errors or it was direct uploaded, the uploaded file representation is assigned to the attacher. This also includes any file metadata. By default Shrine won't attempt to re-extract metadata, because for remote storages that requires an additional HTTP request, which might not be feasible depending on the application requirements.
However, this means that the attacker can directly upload a malicious file
(because direct uploads aren't validated), and then modify the metadata hash so
that it passes Shrine validations, before submitting the cached file to your
app. To guard yourself from such attacks, you can load the
restore_cached_data
plugin, which will automatically re-extract metadata from
cached files on assignment and override the received metadata.
plugin :restore_cached_data
When doing direct uploads, it's a good idea to apply some kind of throttling to the endpoint, to ensure the attacker cannot upload an unlimited number files, because even with a filesize limit it would allow flooding the storage. A good library for throttling requests is rack-attack.
Also, it's generally a good idea to limit the minimum filesize as well as maximum, to prevent uploading large amounts of small files:
class MyUploader < Shrine
plugin :validation_helpers
Attacher.validate do
validate_min_size 10*1024 # 10 KB
end
end