aspnetmvc-405015-security-considerations-file-upload-control-access-to-server-files.md
To prevent unauthorized access to files/folders stored on a server, you should follow industry-accepted best practices including the following:
Always validate file paths that include untrusted file or folder names. Do not trust user-entered file/folder names as this can generate a path outside the upload folder when combining file names with the upload folder path.
The following example validates uploaded file paths:
@using(Html.BeginForm()) {
@Html.DevExpress().UploadControl(settings => {
settings.Name = "uploadControl";
settings.CallbackRouteValues = new {Controller = "UploadFiles", Action = "UploadFilesHandler"};
settings.ShowUploadButton = true;
}).GetHtml()
}
public class UploadFilesController : Controller {
public ActionResult UploadFilesHandler([ModelBinder(typeof(UploadFilesBinder))] IEnumerable<UploadedFile> uploadControl) {
return null;
}
public class UploadFilesBinder : DevExpressEditorsBinder {
public UploadFilesBinder() {
UploadControlBinderSettings.FileUploadCompleteHandler = uploadControl_FileUploadComplete;
}
protected void uploadControl_FileUploadComplete(object sender, DevExpress.Web.FileUploadCompleteEventArgs e) {
string uploadFolder = "~/App_Data/UploadFiles/";
string fileName = Path.Combine(uploadFolder, e.UploadedFile.FileName);
if (e.IsValid && IsValidVirtualPath(fileName, uploadFolder)) {
e.UploadedFile.SaveAs(System.Web.HttpContext.Current.Request.MapPath(fileName), true);
}
}
bool IsValidVirtualPath(string sourceVirtualPath, string folderVirtualPath) {
var sourceFullPath = System.Web.HttpContext.Current.Request.MapPath(sourceVirtualPath);
return IsValidFullPath(sourceFullPath, folderVirtualPath);
}
bool IsValidFullPath(string sourceFullPath, string folderVirtualPath) {
var folderFullPath = System.Web.HttpContext.Current.Request.MapPath(folderVirtualPath);
if (sourceFullPath.EndsWith(@"\"))
sourceFullPath = sourceFullPath.Substring(0, sourceFullPath.Length - 1);
if (folderFullPath.EndsWith(@"\"))
folderFullPath = folderFullPath.Substring(0, folderFullPath.Length - 1);
return sourceFullPath.Equals(folderFullPath, StringComparison.OrdinalIgnoreCase)
|| sourceFullPath.StartsWith(folderFullPath + @"\", StringComparison.OrdinalIgnoreCase);
}
}
}
To ensure uploaded temporary files are inaccessible to third parties, you should:
The following code sample saves temporary files using industry-accepted best practices:
@using(Html.BeginForm()) {
@Html.DevExpress().UploadControl(settings => {
settings.Name = "uploadControl";
settings.CallbackRouteValues = new {Controller = "UploadFiles", Action = "UploadFilesHandler"};
settings.ShowUploadButton = true;
settings.UploadMode = UploadControlUploadMode.Advanced;
settings.AdvancedModeSettings.EnableMultiSelect = true;
}).GetHtml()
}
public class UploadFilesController : Controller{
public ActionResult UploadFilesHandler([ModelBinder(typeof(UploadFilesBinder))] IEnumerable<UploadedFile> uploadControl) {
return null;
}
public class UploadFilesBinder : DevExpressEditorsBinder {
public UploadFilesBinder() {
UploadControlBinderSettings.FilesUploadCompleteHandler = uploadControl_FilesUploadComplete;
}
private void uploadControl_FilesUploadComplete(object sender, FilesUploadCompleteEventArgs e) {
var uploadedFiles = ((MVCxUploadControl)sender).UploadedFiles;
if (uploadedFiles != null && uploadedFiles.Length > 0) {
for (int i = 0; i < uploadedFiles.Length; i++) {
UploadedFile file = uploadedFiles[i];
string uploadFolder = "~/App_Data/UploadFiles/";
if (file.IsValid && file.FileName != "") {
string fileName = Path.Combine(System.Web.HttpContext.Current.Request.MapPath(uploadFolder), Path.GetRandomFileName() + ".tmp");
if (IsValidFullPath(fileName, uploadFolder)) {
file.SaveAs(fileName, true);
// Process the uploaded file here
}
}
}
}
}
bool IsValidFullPath(string sourceFullPath, string folderVirtualPath) {
var folderFullPath = System.Web.HttpContext.Current.Request.MapPath(folderVirtualPath);
if (sourceFullPath.EndsWith(@"\"))
sourceFullPath = sourceFullPath.Substring(0, sourceFullPath.Length - 1);
if (folderFullPath.EndsWith(@"\"))
folderFullPath = folderFullPath.Substring(0, folderFullPath.Length - 1);
return sourceFullPath.Equals(folderFullPath, StringComparison.OrdinalIgnoreCase)
|| sourceFullPath.StartsWith(folderFullPath + @"\", StringComparison.OrdinalIgnoreCase);
}
}
}
The DevExpress File Manager extension automatically creates content-based thumbnails and stores them in the ThumbnailFolder. Subfolder structure is based on the File Manager’s folder structure. Before the File Manager displays a thumbnail for the first time, the extension checks for an existing thumbnail with a corresponding path/name. If the thumbnail does not exist, the extension generates a new thumbnail file.
Consider the following when using the File Manager:
In multi-user applications or if you dynamically change the root folder, use the ThumbnailFolder property to specify the thumbnail folder dynamically based on the current user.
@model string
<script type="text/javascript">
function Submit() {
$("form").submit();
}
</script>
@using (Html.BeginForm()) {
@Html.DevExpress().ComboBox(settings => {
settings.Name = "fileManagerCurrentUser";
settings.Properties.Items.Add("Common files");
settings.Properties.Items.Add("User1");
settings.Properties.Items.Add("User2");
settings.Properties.Items.Add("User3");
settings.Properties.ClientSideEvents.SelectedIndexChanged = "function(s, e){ Submit(); }";
}).Bind(FileManagerHelper.FileManagerCurrentUser).GetHtml()
@Html.Partial("FileManagerPartial", Model)
}
@Html.DevExpress().FileManager(settings => {
settings.Name = "fileManager";
settings.DownloadRouteValues = new { Controller = "FileManager", Action = "DownloadFiles" };
settings.CallbackRouteValues = new { Controller = "FileManager", Action = "FileManagerPartial" };
settings.Settings.ThumbnailFolder = FileManagerHelper.myTumbnailsFolder;
// ...
}).BindToFolder(Model).GetHtml()
public class FileManagerController : Controller {
public ActionResult FileManager() {
return View("FileManager", (object)FileManagerHelper.myRootFolder);
}
[HttpPost]
public ActionResult FileManager(string fileManagerCurrentUser) {
FileManagerHelper.FileManagerCurrentUser = fileManagerCurrentUser;
return View("FileManager", (object)FileManagerHelper.myRootFolder);
}
public ActionResult FileManagerPartial() {
return PartialView("FileManagerPartial", (object)FileManagerHelper.myRootFolder);
}
public FileStreamResult DownloadFiles() {
return FileManagerExtension.DownloadFiles(
FileManagerHelper.CreateFileManagerDownloadSettings(),
(string)FileManagerHelper.myRootFolder
);
}
}
public class FileManagerHelper {
public static string currentUser = "Common files";
public static string myRootFolder = Path.Combine(@"Content\", "Common files");
public static string myTumbnailsFolder;
public static string FileManagerCurrentUser {
get {
return currentUser;
}
set {
if(string.IsNullOrEmpty(value))
return;
var rootFolderVirtPath = Path.Combine(@"Content\", value);
var thumbnailsFolderVirtPath = Path.Combine(@"Thumbs\", value);
if(IsValidVirtualPath(rootFolderVirtPath, @"Content\") && IsValidVirtualPath(thumbnailsFolderVirtPath, @"Thumbs\")) {
currentUser = value;
myRootFolder = rootFolderVirtPath;
myTumbnailsFolder = thumbnailsFolderVirtPath;
}
}
}
public static DevExpress.Web.Mvc.FileManagerSettings CreateFileManagerDownloadSettings() {
var settings = new DevExpress.Web.Mvc.FileManagerSettings();
var editingSettings = CreateFileManagerEditingSettings();
settings.SettingsEditing.Assign(editingSettings);
settings.Name = "FileManager";
return settings;
}
public static FileManagerSettingsEditing CreateFileManagerEditingSettings() {
return new FileManagerSettingsEditing(null) {
AllowCreate = true,
AllowMove = true,
AllowDelete = true,
AllowRename = true,
AllowCopy = true,
AllowDownload = true
};
}
static bool IsValidVirtualPath(string sourceVirtualPath, string folderVirtualPath) {
var sourceFullPath = System.Web.HttpContext.Current.Request.MapPath(sourceVirtualPath);
return IsValidFullPath(sourceFullPath, folderVirtualPath);
}
static bool IsValidFullPath(string sourceFullPath, string folderVirtualPath) {
var folderFullPath = System.Web.HttpContext.Current.Request.MapPath(folderVirtualPath);
if (sourceFullPath.EndsWith(@"\"))
sourceFullPath = sourceFullPath.Substring(0, sourceFullPath.Length - 1);
if (folderFullPath.EndsWith(@"\"))
folderFullPath = folderFullPath.Substring(0, folderFullPath.Length - 1);
return sourceFullPath.Equals(folderFullPath, StringComparison.OrdinalIgnoreCase)
|| sourceFullPath.StartsWith(folderFullPath + @"\", StringComparison.OrdinalIgnoreCase);
}
}
The File Manager extension allows you to specify access rules and security permissions for files/folders. A folder access rule affects the folder, its subfolders, and files. A file access rule affects all files whose path matches a specified pattern. Unlike access rules, each security permission affects an individual file or folder and allows you to implement complex user access logic.
Use the SettingsPermissions.AccessRules property to specify access rules and apply appropriate security permissions. Note the following:
AccessRules collection.The following example restricts editing operations for the entire file system:
@Html.DevExpress().FileManager(settings => {
settings.Name = "fileManager";
// ...
settings.SettingsPermissions.AccessRules.Add(new FileManagerFolderAccessRule { Edit = Rights.Deny });
settings.SettingsPermissions.AccessRules.Add(
new FileManagerFileAccessRule { PathPattern = "*", Download = Rights.Deny }
);
}).BindToFolder(Model).GetHtml()
Refer to the following topics for additional information/guidance: Access Rules and Permissions.
See Also