Control Access to Server Files
- 6 minutes to read
To prevent unauthorized access to files/folders stored on a server, you should follow industry-accepted best practices including the following:
Validate File Paths
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);
}
}
}
Protect Temporary Files
To ensure uploaded temporary files are inaccessible to third parties, you should:
- Store temporary files in a folder inaccessible by URL.
- Use a dedicated file extension for temporary files.
- Call the GetRandomFileName method to assign random file names to uploaded files.
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);
}
}
}
Protect Thumbnails
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:
- A threat actor can access private thumbnails if these thumbnails are in a public folder.
- If you change the RootFolder property value dynamically, multiple thumbnails can use the same relative path and file name. In this instance, the extension may display incorrect thumbnails.
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);
}
}
Restrict Access to Files and Folders
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:
- Specify only one permission in an access rule object.
- Access rule order corresponds to rule order in the
AccessRules
collection. - An access rule with a higher index has a higher priority and can override a preceding rule.
- Security permissions are prioritized over access rules if they affect the same file or folder.
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.