handle dangerous extensions

pull/604/merge
Pete Matsyburka 1 month ago
parent 6806772346
commit 354bccd6e8

@ -11,6 +11,7 @@ module Api
before_action :set_noindex_headers before_action :set_noindex_headers
before_action :set_security_headers before_action :set_security_headers
# rubocop:disable Metrics
def show def show
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid]) blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
@ -22,6 +23,12 @@ module Api
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid) blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
if Submitters::DANGEROUS_EXTENSIONS.include?(blob.filename.extension.to_s.downcase)
Rollbar.error('Dangerous extension') if defined?(Rollbar)
return head :unprocessable_content
end
attachment = blob.attachments.take attachment = blob.attachments.take
@record = attachment.record @record = attachment.record
@ -46,6 +53,7 @@ module Api
end end
end end
end end
# rubocop:enable Metrics
private private

@ -19,6 +19,12 @@ module Api
return head :not_found unless blob return head :not_found unless blob
if Submitters::DANGEROUS_EXTENSIONS.include?(blob.filename.extension.to_s.downcase)
Rollbar.error('Dangerous extension') if defined?(Rollbar)
return head :unprocessable_content
end
is_permitted = blob.attachments.any? do |a| is_permitted = blob.attachments.any? do |a|
(current_user && a.record.account.id == current_user.account_id) || (current_user && a.record.account.id == current_user.account_id) ||
a.record.account.account_configs.any? { |e| e.key == 'legacy_blob_proxy' } || a.record.account.account_configs.any? { |e| e.key == 'legacy_blob_proxy' } ||

@ -11,6 +11,12 @@ class UserInitialsController < ApplicationController
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank? return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank?
extension = File.extname(file.original_filename).delete_prefix('.').downcase
if Submitters::DANGEROUS_EXTENSIONS.include?(extension)
raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed."
end
blob = ActiveStorage::Blob.create_and_upload!(io: file.open, blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename, filename: file.original_filename,
content_type: file.content_type) content_type: file.content_type)

@ -11,6 +11,12 @@ class UserSignaturesController < ApplicationController
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank? return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank?
extension = File.extname(file.original_filename).delete_prefix('.').downcase
if Submitters::DANGEROUS_EXTENSIONS.include?(extension)
raise Submitters::MaliciousFileExtension, "File type '.#{extension}' is not allowed."
end
blob = ActiveStorage::Blob.create_and_upload!(io: file.open, blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename, filename: file.original_filename,
content_type: file.content_type) content_type: file.content_type)

@ -212,8 +212,8 @@ module Submitters
elsif type.in?(%w[signature initials]) && value.length < 60 elsif type.in?(%w[signature initials]) && value.length < 60
find_or_create_blob_from_text(account, value, type) find_or_create_blob_from_text(account, value, type)
elsif (data = Base64.decode64(value.sub(BASE64_PREFIX_REGEXP, ''))) && elsif (data = Base64.decode64(value.sub(BASE64_PREFIX_REGEXP, ''))) &&
Marcel::MimeType.for(data).exclude?('octet-stream') (mime_type = Marcel::MimeType.for(data)).exclude?('octet-stream')
find_or_create_blob_from_base64(account, data, type) find_or_create_blob_from_base64(account, data, type, mime_type:)
elsif type == 'image' && (value.starts_with?('<html>') || value.starts_with?('<!DOCTYPE')) elsif type == 'image' && (value.starts_with?('<html>') || value.starts_with?('<!DOCTYPE'))
raise InvalidDefaultValue, "Invalid #{type} value" unless purpose == :api raise InvalidDefaultValue, "Invalid #{type} value" unless purpose == :api
@ -236,15 +236,27 @@ module Submitters
raise InvalidDefaultValue, "HTML content is not allowed: #{value.first(200)}..." raise InvalidDefaultValue, "HTML content is not allowed: #{value.first(200)}..."
end end
def find_or_create_blob_from_base64(account, data, type) def find_or_create_blob_from_base64(account, data, type, mime_type: nil)
checksum = Digest::MD5.base64digest(data) checksum = Digest::MD5.base64digest(data)
blob = find_blob_by_checksum(checksum, account) blob = find_blob_by_checksum(checksum, account)
blob || ActiveStorage::Blob.create_and_upload!( return blob if blob
io: StringIO.new(data),
filename: "#{type}.png" mime_type ||= Marcel::MimeType.for(data)
)
detected_extensions = Marcel::TYPE_EXTS[mime_type].to_a.map(&:downcase)
if detected_extensions.any? { |e| Submitters::DANGEROUS_EXTENSIONS.include?(e) }
raise InvalidDefaultValue, "File type '.#{detected_extensions.first}' is not allowed."
end
extension = detected_extensions.first
extension = 'png' if extension.blank? && type.in?(%w[signature initials stamp image])
filename = extension.present? ? "#{type}.#{extension}" : type
ActiveStorage::Blob.create_and_upload!(io: StringIO.new(data), filename:)
end end
def find_or_create_blob_from_text(account, text, type) def find_or_create_blob_from_text(account, text, type)
@ -261,6 +273,13 @@ module Submitters
end end
def find_or_create_blob_from_url(account, url) def find_or_create_blob_from_url(account, url)
filename = Addressable::URI.parse(url).path.split('/').last.to_s
extension = File.extname(filename).delete_prefix('.').downcase
if Submitters::DANGEROUS_EXTENSIONS.include?(extension)
raise InvalidDefaultValue, "File type '.#{extension}' is not allowed."
end
cache_key = [account.id, url].join(':') cache_key = [account.id, url].join(':')
checksum = CHECKSUM_CACHE_STORE.fetch(cache_key) checksum = CHECKSUM_CACHE_STORE.fetch(cache_key)
@ -276,10 +295,7 @@ module Submitters
blob = find_blob_by_checksum(checksum, account) blob = find_blob_by_checksum(checksum, account)
blob || ActiveStorage::Blob.create_and_upload!( blob || ActiveStorage::Blob.create_and_upload!(io: StringIO.new(data), filename:)
io: StringIO.new(data),
filename: Addressable::URI.parse(url).path.split('/').last
)
end end
def find_blob_by_checksum(checksum, account) def find_blob_by_checksum(checksum, account)

Loading…
Cancel
Save