mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-01-28 10:37:52 +08:00
Paperclip file spoofing corrected/modified.
This commit is contained in:
parent
4ad384df5a
commit
04d9163ff6
5 changed files with 138 additions and 20 deletions
|
@ -2,7 +2,13 @@ FROM rails:4.2.5
|
||||||
MAINTAINER BioSistemika <info@biosistemika.com>
|
MAINTAINER BioSistemika <info@biosistemika.com>
|
||||||
|
|
||||||
# additional dependecies
|
# additional dependecies
|
||||||
RUN apt-get update -qq && apt-get install -y default-jre-headless unison sudo graphviz --no-install-recommends && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update -qq && \
|
||||||
|
apt-get install -y \
|
||||||
|
default-jre-headless \
|
||||||
|
unison \
|
||||||
|
sudo graphviz --no-install-recommends \
|
||||||
|
sudo libfile-mimeinfo-perl && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# heroku tools
|
# heroku tools
|
||||||
RUN wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh
|
RUN wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh
|
||||||
|
|
|
@ -53,9 +53,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The server checks if files are OK (correct file type, presence,
|
* The server checks if files are OK (presence, size and spoofing)
|
||||||
* size and spoofing) and only then generates posts for S3 server file
|
* and only then generates posts for S3 server file uploading
|
||||||
* uploading (each post for different size/style of the same file).
|
* (each post for different size/style of the same file).
|
||||||
*/
|
*/
|
||||||
function fetchUploadSignature(ev, fileInput, file, signUrl) {
|
function fetchUploadSignature(ev, fileInput, file, signUrl) {
|
||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
|
@ -69,17 +69,14 @@
|
||||||
contentType: false,
|
contentType: false,
|
||||||
error: function (xhr) {
|
error: function (xhr) {
|
||||||
try {
|
try {
|
||||||
var data = JSON.parse(xhr.responseText);
|
|
||||||
if (data.status === "error") {
|
|
||||||
// File error
|
// File error
|
||||||
var errMsg = jsonToValuesArray(data.errors);
|
var jsonData = $.parseJSON(xhr.responseText);
|
||||||
renderFormError(ev, fileInput, errMsg);
|
var errMsg = jsonToValuesArray(jsonData.errors);
|
||||||
}
|
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
// Connection error
|
// Connection error
|
||||||
var errMsg = I18n.t("general.file.upload_failure");
|
var errMsg = I18n.t("general.file.upload_failure");
|
||||||
renderFormError(ev, fileInput, errMsg);
|
|
||||||
}
|
}
|
||||||
|
renderFormError(ev, fileInput, errMsg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -102,9 +99,15 @@
|
||||||
data: formData,
|
data: formData,
|
||||||
processData: false,
|
processData: false,
|
||||||
contentType: false,
|
contentType: false,
|
||||||
error: function () {
|
error: function (xhr) {
|
||||||
|
try {
|
||||||
|
// File error
|
||||||
|
var $xmlData = $(xhr.responseText);
|
||||||
|
var errMsg = $xmlData.find("Message").text().strToErrorFormat();
|
||||||
|
} catch(err) {
|
||||||
// Connection error
|
// Connection error
|
||||||
var errMsg = I18n.t("general.file.upload_failure");
|
var errMsg = I18n.t("general.file.upload_failure");
|
||||||
|
}
|
||||||
renderFormError(ev, fileInput, errMsg);
|
renderFormError(ev, fileInput, errMsg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,12 +7,8 @@ class AssetsController < ApplicationController
|
||||||
def signature
|
def signature
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json {
|
format.json {
|
||||||
|
|
||||||
asset = Asset.new(asset_params)
|
asset = Asset.new(asset_params)
|
||||||
if asset.errors.any?
|
if asset.errors.any?
|
||||||
# We need to validate, although 'new' already does it, so that
|
|
||||||
# asset's after_validation gets triggered, which modifies errors
|
|
||||||
asset.valid?
|
|
||||||
render json: {
|
render json: {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
errors: asset.errors
|
errors: asset.errors
|
||||||
|
|
|
@ -42,9 +42,11 @@ class Asset < ActiveRecord::Base
|
||||||
has_many :report_elements, inverse_of: :asset, dependent: :destroy
|
has_many :report_elements, inverse_of: :asset, dependent: :destroy
|
||||||
has_one :asset_text_datum, inverse_of: :asset, dependent: :destroy
|
has_one :asset_text_datum, inverse_of: :asset, dependent: :destroy
|
||||||
|
|
||||||
# Specific file errors propagate to "fire" error hash key,
|
# Specific file errors propagate to "file" error hash key,
|
||||||
# so use just these errors
|
# so use just these errors
|
||||||
after_validation :filter_paperclip_errors
|
after_validation :filter_paperclip_errors
|
||||||
|
# Needed because Paperclip validatates on creation
|
||||||
|
after_create :filter_paperclip_errors
|
||||||
|
|
||||||
attr_accessor :file_content, :file_info, :preview_cached
|
attr_accessor :file_content, :file_info, :preview_cached
|
||||||
|
|
||||||
|
@ -343,5 +345,4 @@ class Asset < ActiveRecord::Base
|
||||||
|
|
||||||
self.file = data
|
self.file = data
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -57,3 +57,115 @@ Paperclip::Attachment.class_eval do
|
||||||
Paperclip.io_adapters.for self
|
Paperclip.io_adapters.for self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Paperclip
|
||||||
|
# Checks file for spoofing
|
||||||
|
class MediaTypeSpoofDetector
|
||||||
|
def spoofed?
|
||||||
|
if has_name? && has_extension? && (media_type_mismatch? ||
|
||||||
|
mapping_override_mismatch?)
|
||||||
|
Paperclip.log("Content Type Spoof: Filename #{File.basename(@name)} "\
|
||||||
|
"(#{supplied_content_type} from Headers, #{content_types_from_name} "\
|
||||||
|
'from Extension), content type discovered: '\
|
||||||
|
"#{calculated_content_type}. See documentation to allow this "\
|
||||||
|
'combination.')
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Determine file content type from its name
|
||||||
|
def content_types_from_name
|
||||||
|
@content_types_from_name ||=
|
||||||
|
Paperclip.run('mimetype', '-b :file_name', file_name: @name).chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine file media type from its name
|
||||||
|
def media_types_from_name
|
||||||
|
@media_types_from_name ||= extract_media_type content_types_from_name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine file content type from mimetype command
|
||||||
|
def type_from_mimetype_command
|
||||||
|
@type_from_mimetype_command ||=
|
||||||
|
Paperclip.run('mimetype', '-b :file', file: @file.path).chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine file media type from mimetype command
|
||||||
|
def media_type_from_mimetype_command
|
||||||
|
@media_type_from_mimetype_command ||=
|
||||||
|
extract_media_type type_from_mimetype_command
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine file content type from it's content (file and mimetype command)
|
||||||
|
def type_from_file_command
|
||||||
|
unless defined? @type_from_file_command
|
||||||
|
@type_from_file_command =
|
||||||
|
Paperclip.run('file', '-b --mime :file', file: @file.path)
|
||||||
|
.split(/[:;]\s+/).first
|
||||||
|
|
||||||
|
if allowed_spoof_exception?(@type_from_file_command,
|
||||||
|
media_type_from_file_command) ||
|
||||||
|
(@type_from_file_command.in?(%w(text/plain text/html)) &&
|
||||||
|
media_type_from_mimetype_command.in?(%w(text application)))
|
||||||
|
# File content type is generalized, so rely on file extension for
|
||||||
|
# correct/more specific content type
|
||||||
|
@type_from_file_command = type_from_mimetype_command
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@type_from_file_command
|
||||||
|
rescue Cocaine::CommandLineError
|
||||||
|
''
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine file media type from it's content (file and mimetype command)
|
||||||
|
def media_type_from_file_command
|
||||||
|
@media_type_from_file_command ||=
|
||||||
|
extract_media_type type_from_file_command
|
||||||
|
end
|
||||||
|
|
||||||
|
def extract_media_type(content_type)
|
||||||
|
if content_type.empty?
|
||||||
|
''
|
||||||
|
else
|
||||||
|
content_type.split('/').first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks file media type mismatch between file's name and header
|
||||||
|
def supplied_type_mismatch?
|
||||||
|
!allowed_spoof_exception?(supplied_content_type, supplied_media_type) &&
|
||||||
|
media_types_from_name != supplied_media_type
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks file media type mismatch between file's name and content
|
||||||
|
def calculated_type_mismatch?
|
||||||
|
!allowed_spoof_exception?(calculated_content_type,
|
||||||
|
calculated_media_type) &&
|
||||||
|
media_types_from_name != calculated_media_type
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks file content type mismatch between file's name and content
|
||||||
|
def mapping_override_mismatch?
|
||||||
|
!allowed_spoof_exception?(calculated_content_type,
|
||||||
|
calculated_media_type) &&
|
||||||
|
content_types_from_name != calculated_content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check if we have a file spoof exception which is allowed/safe
|
||||||
|
def allowed_spoof_exception?(content_type, media_type)
|
||||||
|
content_type == 'application/octet-stream' ||
|
||||||
|
(content_type == 'inode/x-empty' && @file.size.zero?) ||
|
||||||
|
(content_type == 'text/x-c' &&
|
||||||
|
content_types_from_name == 'text/x-java') ||
|
||||||
|
(media_type.in?(%w(image audio video)) &&
|
||||||
|
media_type == media_types_from_name) ||
|
||||||
|
(content_types_from_name.in? %W(#{}
|
||||||
|
text/plain
|
||||||
|
application/octet-stream))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in a new issue