Secure File Upload – Restrict files uploaded via WFFM by Mime type and file size
At the beginning of the year I posted a blog on a secure way to validate uploaded files that goes beyond checking the file extension. I have since redeveloped it from the ground up to exist as a self-contained module which is now available on the Sitecore Marketplace for Sitecore 8 all the way back to 6.5;
https://marketplace.sitecore.net/Modules/S/Secure_File_Upload.aspx
The Module is a secure and robust way to ensure that users are only able to upload files of a certain type and within a certain file size. Preventing any malicious attacks and human error. The default File Upload existing within WFFM doesn’t limit the type of file uploaded nor its size.
Whats more the Module now allows Content Editors to define the allowed File Types and Size for each Webforms for Marketers Form the Secure File Upload Field is added too. Freeing the Field to be used in a variety of applications.
The module ships with 23 Mime Types already defined. Furthermore, as the Mime Types are now stored as Sitecore Items, new ones can be added to meet all File Types used by the Client.
Secure File Upload Field
The Module contains a new Custom Field Type
for Webforms for Marketers – Secure File Upload. Once added to the Form there are a number of properties the Content Editor can change;
- Upload To – Location in Sitecore where the Files will be stored once uploaded
- Max file size – The maximum size the file can be in MB
- Items – The folder containing all possible Mime Types to select
- Selected Values – One or many Mime Types that are allowed to be uploaded
The Field implements the existing Sitecore.Form.UI.Adapters
; FileUploadAdapter
and ListItemsAdapter
with significant modifications. Code to give control of the file size to the Content Editor and to handle if no size is provided.
The property to hold the list of Mime Types is defaulted to the File Types folder in Sitecore. The list’s Value property set to the Item’s ID, saving the Content Editor from searching for the folder each time and understanding that they must select the ID for the Value property.
namespace JonathanRobbins.SecureFileUpload.Webforms.Controls | |
{ | |
[ListAdapter("Items", typeof(ListItemsAdapter))] | |
[Adapter(typeof(FileUploadAdapter))] | |
public class SecureFileUpload : ValidateControl, IHasTitle | |
{ | |
[VisualFieldType(typeof(SelectDirectoryField))] | |
[VisualProperty("Upload To:", 0)] | |
[DefaultValue("/sitecore/media library")] | |
[VisualCategory("Upload")] | |
public string UploadTo | |
{ | |
get { return this._uploadDir; } | |
set { this._uploadDir = value; } | |
} | |
[PersistenceMode(PersistenceMode.InnerProperty)] | |
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] | |
[Browsable(false)] | |
[VisualCategory("File Types")] | |
[VisualFieldType(typeof(ListField))] | |
[TypeConverter(typeof(ListItemCollectionConverter))] | |
[DefaultValue("%3cquery+t%3d%22root%22+vf%3d%22__ID%22%3e%3cvalue%3e%7b7C75E0B5-3C17-4471-9F81-0490306EC71E%7d%3c%2fvalue%3e%3c%2fquery%3e")] | |
[Description("Collection of items.")] | |
[VisualProperty("Items:", 100)] | |
public ListItemCollection Items | |
{ | |
get | |
{ | |
return this._items; | |
} | |
set | |
{ | |
this._items = value; | |
} | |
} | |
[TypeConverter(typeof(ListItemCollectionConverter))] | |
[VisualFieldType(typeof(MultipleSelectedValueField))] | |
[VisualCategory("File Types")] | |
[Browsable(false)] | |
[VisualProperty("Selected Values:", 450)] | |
public ListItemCollection SelectedValue | |
{ | |
get | |
{ | |
return this._selectedItems; | |
} | |
set | |
{ | |
this._selectedItems = value; | |
} | |
} | |
[VisualFieldType(typeof(EditField))] | |
[VisualProperty("Max file size limit (MB) :", 5)] | |
[VisualCategory("Upload")] | |
public string FileSizeLimit | |
{ | |
get | |
{ | |
return this._fileSizeLimit.ToString(); | |
} | |
set | |
{ | |
int result; | |
if (!int.TryParse(value, out result)) | |
result = 5; | |
this._fileSizeLimit = result; | |
} | |
} |
File Type Validator
One of two validators in the Module, File Type Validator completes the Mime Type
sniffing of the file and compares the findings against known Mime Types.
The validator retrieves the permitted file types from File Types selected in the Secure File Upload field. The selectedvalue
property contains the Id
of File Type Sitecore Items which hold the values needed for the comparison.
private List<FileType> DeterminePermitedMimeTypes() | |
{ | |
var fileTypes = new List<FileType>(); | |
if (FieldItem != null) | |
{ | |
var itemIds = new List<string>(); | |
var regexSelectedValue = new Regex(@"<selectedvalue>(.*?)</selectedvalue>".ToLower()); | |
var selectedValueNodes = regexSelectedValue.Matches(FieldItem["Parameters"].ToLower()); | |
if (selectedValueNodes.Count > 0) | |
{ | |
foreach (Match selectedValueNode in selectedValueNodes) | |
{ | |
var regexItems = new Regex(@"<item>(.*?)</item>".ToLower()); | |
var itemNodes = regexItems.Matches(selectedValueNode.ToString()); | |
itemIds.AddRange(from Match itemNode in itemNodes | |
where itemNode.Groups[1] != null | |
&& !string.IsNullOrEmpty(itemNode.Groups[1].Value) | |
&& TryParseGuid(itemNode.Groups[1].Value) | |
select itemNode.Groups[1].Value); | |
} | |
if (itemIds.Any()) | |
{ | |
var fileTypeItems = itemIds.Select(i => Sitecore.Context.Database.GetItem(i)); | |
fileTypes = fileTypeItems.Select(i => new FileType(i)).ToList(); | |
} | |
} | |
} | |
return fileTypes; | |
} |
The File Type Item is a simple Template that contains fields for Mime Type,
Byte Array Sequence
of the mime type as a comma separated values and an optional File Extension
to help with accuracy. By default they are installed a path within Webforms for Marketers /sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data/File Types
From the collection of FileTypes chosen by the Content Editor the MimeTypeAllowed
method loops through them and compares the uploaded file ByteArray
for sequence matches.
File extension is used for further accuracy between similar Mime Types but is optional. If a file is matched to more than one Mime Type a match with the File Extension will be favoured.
public FileType MimeTypeAllowed(byte[] file, string fileName, List<FileType> permittedMimeTypes) | |
{ | |
FileType uploadedFileType = null; | |
string extension = Path.GetExtension(fileName) != null | |
? Path.GetExtension(fileName).ToUpper() | |
: string.Empty; | |
var matchedByByte = new List<FileType>(); | |
var matchedByByteAndExtension = new List<FileType>(); | |
foreach (var permittedMimeType in permittedMimeTypes) | |
{ | |
int byteCount = permittedMimeType.ByteArray.Count(); | |
if (file.Take(byteCount).SequenceEqual(permittedMimeType.ByteArray)) | |
{ | |
if (!string.IsNullOrEmpty(permittedMimeType.FileExtension)) | |
{ | |
if (permittedMimeType.FileExtension.Equals(extension, StringComparison.InvariantCultureIgnoreCase)) | |
{ | |
matchedByByteAndExtension.Add(new FileType(permittedMimeType.MimeType, file, extension)); | |
} | |
} | |
else | |
{ | |
matchedByByte.Add(new FileType(permittedMimeType.MimeType, file, extension)); | |
} | |
} | |
} | |
uploadedFileType = matchedByByteAndExtension.Any() | |
? matchedByByteAndExtension.FirstOrDefault() | |
: matchedByByte.Any() ? matchedByByte.FirstOrDefault() : null; | |
return uploadedFileType; | |
} |
File Size Validator
The File Size validator is as simple as the name suggests. It compares the size of the file uploaded to the size set against the Secure File Upload field. It could easily be applied to other fields providing they have a property of max file size.
If the limit is set left empty by the Content Editor any file size will be permitted.
public class FileSizeValiadtor : FormCustomValidator | |
{ | |
protected override bool EvaluateIsValid() | |
{ | |
bool isValid = false; | |
if (!String.IsNullOrEmpty(base.ControlToValidate)) | |
{ | |
Control control = this.FindControl(base.ControlToValidate); | |
var fileUpload = control as FileUpload; | |
if (fileUpload != null && fileUpload.HasFile) | |
{ | |
isValid = ValidateFileSize(fileUpload.PostedFile); | |
} | |
} | |
return isValid; | |
} | |
private bool ValidateFileSize(HttpPostedFile postedFile) | |
{ | |
bool valid = false; | |
int? fileSizeLimitinBytes = DetermineFileSizeLimit(); | |
if (fileSizeLimitinBytes.HasValue) | |
{ | |
int sizeInBytes = postedFile.ContentLength; | |
valid = (sizeInBytes <= fileSizeLimitinBytes); | |
} | |
else | |
{ | |
// No file size limit | |
valid = true; | |
} | |
return valid; | |
} |
And that’s it!
Content Editors given full control of the types of files and size their users upload on every form. Ensuring accuracy of what is being uploaded by their users and preventing malicious attacks. All that’s left if to use the default Captcha field to prevent automated attacks and everything is all good!
The full source code an be found on Github – https://github.com/islaytitans/FileUploadValidator
Pingback: Interesting Links – The Unwise Cakes Edition – Andy Burns' Blog
I am using WFFM 8.1 rev. 160304 Update-2, I uploaded your package and attached “Secure file upload ” control in WFFM form, but control itself not rendering. I am Using Sitecore MVC 8.2 with WFFM.
Using “MVC Form” Rendering to Display the Form.
LikeLike
Hey, this is something I’ve been meaning to address with the version on the Sitecore Marketplace. It’s because there isn’t an MVC control only a WebForms Control. If you can’t wait for a new release you can fork the code to add the MVC control here – https://github.com/islaytitans/FileUploadValidator
Hope that helps 🙂
LikeLike
Pingback: Sitecore Webforms for Marketers – Custom Field Validator MIME Type Sniffer | Exercising Sitecore