I have an asp.net mvc app with a route that allows users to request files that are stored outside of the web application directory.
I'll simplify the scenario by just telling you that it's going to ultimately confine them to a safe directory to which they have full access.
For example:
If the user (whose ID is 100) requests:
http://mysite.com/Read/Image/Cool.png
then my app is going to append "Cool.png" to "C:\ImageRepository\Users\100\" and write those bytes to the response. The worker process has access to this path, but the anonymous user does not. I already have this working.
But will some malicious user be able to request something like:
http://mysite.com/Read/Image/..\101\Cool.png
and have it resolve to
"C:\ImageRepository\Customers\101\Cool.png"
(some other user's image?!)
Or something like that? Is there a way to make sure the path is clean, such that the user is constrained to their own directory?
How about
var fileName = System.IO.Path.GetFileName(userFileName);
var targetPath = System.IO.Path.Combine(userDirectory, fileName);
That should ensure you get a simple filename only.
Perhaps you should verify that the path starts with the user's directory path?
e.g. "C:\ImageRepository\Customers\100\"
You should also normalize the paths to uppercase letters when comparing them.
The safest way, if it is an option (you are using windows auth), is to make it a non-issue by using Active Directory rights on the folders so it doesn't matter if the user attempts to access a directory that is not valid.
Absent that, store the files so that the path is abstracted from the user. That is, use whatever name the user provides as a lookup in a table that has the REAL path to the file.
Cannolocalization protection is tricky business and it is dangerous to try and outthink a potential attacker.
Using the Request.MapPath overload is one way to check this:
try
{
string mappedPath = Request.MapPath( inputPath.Text, Request.ApplicationPath, false);
}
catch (HttpException)
{
// do exception handling
}
Also you could explode the string and delimit it by slashes, and check the username match also.
To also be able to include a subdirectory in the path you can use:
string SafeCombine(string basePath, string path)
{
string testPath = Path.GetFullPath(Path.Combine(basePath, path));
if (testPath.startsWith(basePath))
return testPath;
throw new InvalidOperationException();
}
Related
I will create an ASP.NET Web Application like this;
Users can login their own username and password.
Users can file upload their own folder (I define the folders manually), and also they can see all files in their own page and they can download them.
I think I can use an architecture like this;
I should use ASP.NET Login Controls, we all agree with that. But I also should use C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_regsql.exe and ASP.NET Web site Administration Tool? What does exactly aspnet_regsql.exe do? What does Administration Tool do? They are (Login controls + aspnet_regsql.exe + Administration Tool) only I need for creating a user login system?
For file upload I think to use File Upload control. Then, For example; if I have a user "A", folder should be like http://ourserver/A. I have no idea how can I upload file to specific folder. And how can I show to users their own uploaded files? (Also users should download them) Is there any asp.net control for doing this ?
EDIT: I prefer step by step explanation rather than giving some code. I want to also understand what these applications are doing ?
1) If you run the regsql tool, it will popup a screen asking for some sql information and then create a set of tables relative to membership etc. If you start a basic MVC 3 application, it comes with an account controller, login, logout, forgot password all wired up. This talks to that database. Your alternative is to create a custom membership provider, there are lots of tutorials out there for doing that. You can control more things with the custom membership provider, but it's a bit more work.
2) AmRans example code is perfect for saving the actual file. As far as the folder for the given user goes, then I would probably configure a base directory in your web.config, then;
if( Directory.Create( Path.Combine( baseDirectory, UserDirectory)).Exists) {
}
although if you're using .NET 2, then that might remain;
string savePath = Path.Combine( baseDirectory, UserDirectory);
Directory.Create( savePath );
if( Directory.Exists( savePath)) {
}
For viewing of those files, if the user is logged in, you can query the save directory + username and list out all the files in that.
See the code below , not the exact but might be help for you. Here UploadTypes is a custom enum and userId is the current user logged in , id (primary key of the table or any thing that is unique), with control name, passed from Front end.
/// <summary>
/// returns the virtual application path of uploaded file.
/// </summary>
/// <param name="fu"></param>
/// <param name="uploadType"></param>
/// <returns></returns>
public static string GetAppFileUploadPath(FileUpload fu, UploadTypes uploadType, int userId)
{
var path = string.Format("~/Images/no.gif");
if (fu.HasFile)
{
if (fu.FileContent.Length > 0)
{
var filename = Path.GetFileNameWithoutExtension(fu.PostedFile.FileName);
var extension = Path.GetExtension(fu.PostedFile.FileName);
switch (uploadType)
{
case UploadTypes.Images:
_validExtensions = new List<string>
{
".bmp", ".jpg",".jpeg",".gif",".png"
};
if (_validExtensions.Contains(extension.ToLower()))
{
var newFileName = string.Format("{0}_{1}_{2}{3}", filename, userId, Guid.NewGuid().ToString().Substring(0, 5), extension);
var serverUploadPath = string.Format("{0}/VirtualOffice/Uploads/ProductImage/{1}", HttpRuntime.AppDomainAppPath, newFileName);
path = string.Format("~/VirtualOffice/Uploads/ProductImage/{0}", newFileName);
fu.SaveAs(serverUploadPath);
}
else
{
Common.ShowMessage("Only image files allowed, bmp, jpg , gif or png.");
}
break;
case UploadTypes.Documents:
_validExtensions = new List<string>
{
".doc", ".rtf",".docx",".pdf",".txt"
};
if (_validExtensions.Contains(extension.ToLower()))
{
var newFileName = string.Format("{0}_{1}_{2}{3}", filename, userId, Guid.NewGuid().ToString().Substring(0, 5), extension);
var serverUploadPath = string.Format("{0}/VirtualOffice/Uploads/ProductImage/{1}", HttpRuntime.AppDomainAppPath, newFileName);
fu.SaveAs(serverUploadPath);
path = string.Format("~/VirtualOffice/Uploads/ProductImage/{0}", newFileName);
}
else
{
Common.ShowMessage("Only valid text files allowed, doc, docx ,rtf, pdf or txt.");
}
break;
}
}
}
return path;
}
This is my custom file upload code to allow either image or document as per scenario.
First of all, before jumping on to what controls to use, whether to create a Website project or ASP.Net MVC3 application, I think its crucial to understand what your core design should be. Implementing the GUI aspect of login is easy, what is important is the backend piece of it.
How do you authenticate users? What role should they have (read only, write only, read/write etc). What mode should you choose and how do you configure your site to accept one of these forms of authentication. If you have an understanding of these, you can skip this, otherwise I would suggest reading up here: ASP.NET authentication and authorization and this: ASP.NET 2.0 Membership, Roles, Forms Authentication, and Security Resources
Now the file upload and management part - well, while its fun to do things on your own from a learning perspective, I also believe in not reinventing the wheel (if it suits your purpose of course). There are good open source projects that you can benefit from like this:
http://www.codeproject.com/KB/aspnet/WebFileManager.aspx (created by Jeff Atwood and has a good explanantion)
http://www.filemanager.3ntar.net/
http://www.ajaxfilebrowser.com/
Hope this helps.
In File upload you can check user after login than check the folder is exist or not by using:
Directory.Exist("FolderPath")
If it is exist upload to that folder other wise you can create folder using
Directory.Create("Folder Path and Name");
Then upload file to that folder
Note that because of security purpose, you might have to provides writting rights to NETWORK SERVICE on you server for each folder you want to upload in. Because of that, I'll recommand you to create a folder in your root directory or anywhere you want and provide write access to this folder, say FolderA. So, any child folder will have same rights.
Also, there's plenty controls that can handle files from the web. Unfortunaly, most of them cost something (if you want one stable and who's working great). One we bought and work great for us, is this one (CuteWebUI.AjaxUploader). It allows you to specify many file at once or not, use Flash, IFrame or AJAX as you want it, save temporaly file automaticaly, etc.
Good luck!
Note : I talk french, so sorry for my english
For various reasons, in development I occasionally want to intercept a request for, say, ~/MyStyle.css
What I want to do is make the following snippet work:
string absFile = VirtualPathUtility.ToAbsolute(file);
return System.IO.File.ReadAllText(absFile);
This absolute path is absolute for the webserver though, it's not going to map to "C:\whatever". Is there an equivalent method to go to the file system? (Or a ReadFromVirtualPath etc.?)
Use Server.MapPath() to get the file system path for a requested application path.
string absFile = Server.MapPath(file);
or
string absFile = HttpContext.Current.Server.MapPath(file);
You can also use the OpenFile method on VirtualPathProvider to get a Stream pointing at your file
var stream = HostingEnvironment.VirtualPathProvider.OpenFile(file);
var text = new StreamReader(stream).ReadToEnd();
Generally this approach is preferable since you can now, at a later point implement a VirtualPathProvider where, lets say all your css files where located in a database.
I know the solid security recommendation of avoiding accepting user input that you then use to choose a path to read/write a file. However, assuming you have a base directory you want to keep within (such as the root of an ftp folder), how do you best ensure that a given user input keeps us within that folder?
For instance,
Path.Combine(_myRootFolder, _myUserInput)
could still take us outside of _myRootFolder. And this could also be dodgy
newPath = Path.Combine(_myRootFolder, _myUserInput)
if (newPath.StartsWith(_myRootFolder))
...
given something like "/back/to/myrootfolder/../../and/out/again" from the user. What are the strategies for this? Am I missing a blindingly obvious .NET method I can use?
Within ASP.NET applications you can use Server.MapPath(filename) which will throw an exception if the path generated goes outside of your application root.
If all you want is a safe file name and you just want all files in there it becomes simpler;
FileInfo file = new FileInfo(
Server.MapPath(
Path.Combine(#"c:\example\mydir", filename)));
If you're outside of ASP.NET like you indicate then you could use Path.GetFullPath.
string potentialPath = Path.Combine(#"c:\myroot\", fileName);
if (Path.GetFullPath(potentialPath) != potentialPath)
// Potential path transversal
Or you call Path.GetFullPath and then check the start of it matches the directory you want locked to.
I know, that this thread is quiet old, but to prevent following readers from writing code with potential security errors, I think I should point out, that using Path.Combine(arg1, arg2) isn't save when arg2 is directly based on user input.
When arg2 is for example "C:\Windows\System32\cmd.exe" the arg1 parameter will be completely ignored and you grant the users of your API or server application full access to the whole file system.
So please be very careful with using this method!
I came up with this solution that should (afaik) be secure:
public static string SecurePathCombine(params string[] paths)
{
string combinedPath = "";
foreach (string path in paths)
{
string newPath = Path.Combine(combinedPath, path);
if (!newPath.StartsWith(combinedPath))
return null;
combinedPath = newPath;
}
if (Path.GetFullPath(combinedPath) != combinedPath)
return null;
return combinedPath;
}
Edit: There is a new Path.Join() method now. Please use that one instead of the code above.
I believe Path.FullPath will do what you need (I didn't test this though):
string newPath = Path.Combine(_myRootFolder, _myUserInput);
string newPath = Path.FullPath(newPath);
if (newPath.StartsWith(_myRootFolder)) ...
Well, in your example of an FTP server, you should set the users home-directory, and permissions appropriately, such that they can't navigate out of the folder. Any reason you can't do that?
You can parse input string and cut ../ with regex.
1)how can i find out the Windows Installation drive in which the user is working.? I need this to navigate to the ApplicationData in DocumentsandSettings.
2)Also how can i get the user name too so that i can goto ApplicaitionData.? Eg: "D:\Documents and Settings\user\Application Data".
Look at combining Environment.GetFolderPath and Environment.SpecialFolder to do this.
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
Depending on what you are doing you might also want to look at
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
If the user is on a domain it will only be stored in their local AppData folder and not synced with their roaming profile.
Have a look at the Environment.SpecialFolders
Environment.SpecialFolder.ApplicationData;
Environment.SpecialFolder.System
that should get you round the username requirement as well.
Have a look at the System.Environment class and its properties and methods, e.g:
string systemDir = System.Environment.SystemDirectory;
string docs = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.MyDocuments));
string systemDrive = System.IO.Path.GetPathRoot(systemDir);
The first one returns "C:\Windows\system32" for example and the second one "C:\Documents and Settings\USERNAME\My Documents".
Try this:
string filePath = Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
1)how can i find out the Windows Installation drive in which the user
is working.?
var systemDrive = Environment.ExpandEnvironmentVariables("%systemdrive%");
I need this to navigate to the ApplicationData in
DocumentsandSettings.
You don't really require to fetch the value of either system drive or currently logged in user name to achieve this. There are predefined environment variables %localAppData% and %appData% which give you fully qualified path of these directories as shown in the code below:
var localApplicationData = Environment.ExpandEnvironmentVariables("%localappdata%");
//this gives C:\Users\<userName>\AppData\Local
var roamingApplicationData = Environment.ExpandEnvironmentVariables("%appdata%");
//this gives C:\Users\<userName>\AppData\Roaming
2)Also how can i get the user name too so that i can goto
ApplicaitionData.? Eg: "D:\Documents and Settings\user\Application
Data".
Again, you don't need user name to get the application data path as I've discussed above. Still, for the sake of knowledge you can fetch it from %username% environment variable as shown below:
var currentUserName = Environment.ExpandEnvironmentVariables("%username%");
I need to know if I can create a file in a specific folder, but there are too many things to check such as permissions, duplicate files, etc.
I'm looking for something like File.CanCreate(#"C:\myfolder\myfile.aaa"), but haven't found such a method.
The only thing I thought is to try to create a dummy file and check for exceptions but this is an ungly solution that also affects performance.
Do you know a better solution?
In reality, creating a dummy file isn't going to have a huge performance impact in most applications. Of course, if you have advanced permissions with create but not destroy it might get a bit hairy...
Guids are always handy for random names (to avoid conflicts) - something like:
string file = Path.Combine(dir, Guid.NewGuid().ToString() + ".tmp");
// perhaps check File.Exists(file), but it would be a long-shot...
bool canCreate;
try
{
using (File.Create(file)) { }
File.Delete(file);
canCreate = true;
}
catch
{
canCreate = false;
}
You can use CAS to verify that there are no .NET policies (caspol) restricting the creating and writing of a file on that location.
But this will not cover the windows policies. You'll have to manually check the NTFS policies. And even then there are processes that can decide you're not allowed to create a file (for instance a virus scanner).
The best and most complete way is to try it.