Don’t Accept Gifts From Strangers – Even Through HTML Form File Elements

comments

If you’re developing on the ASP.Net web stack you’ve probably used either the WebForms FileUpload control or the MVC HttpPostedFileBase model binding parameter many times before. On a badly configured website this can create a perfect storm of insecurity potentially exploited by anyone who uploads malicious files. As this very attack can be your website’s undoing let’s take a look at why it’s a problem and what you can do to fix it.

imageWhen many developers first start developing on the ASP.Net stack, they’ve either never developed web applications before, or they’ve come from another language such as PHP.

This very journey often hasn’t set them in good stead when it comes to building and configuring secure websites on the Microsoft web stack. Many many many insecure generalised blog posts and MSDN articles later, and they aren’t necessarily in any better position.

Basis for an Attack

When we use Request.Form.Files[x] or HttpPostedFileBase to accept uploads and their associated file name we are always making a few assumptions. These very assumptions are incorrect, as they assume that your visitors will always play nice by the RFC for Http.

We assume that an incoming Http file upload will have a valid file name. We also assume that it will only have a filename and not any untoward included.

To understand where this can go wrong, we have to take a look at an upload in action using Fiddler and inspect the request. You can see what the header for an upload looks like below.

image

Note that in the above, I used a file upload form field and simply submitted a file for upload.

The browser takes my file, and its filename, and adds them to the request as text.

The file contents become a binary encoded text blob, and the filename is added to the http request header as text.

And it is this text that your ASP.Net page uses when referring to Request.Files[x].FileName.

So if you take my not so great example code below:

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack && Request.Files.Count > 0  && Request.Files[0].ContentLength > 0)
    {
        Request.Files[0].SaveAs(Path.Combine(Server.MapPath("~/UserUploads/"), Request.Files[0].FileName));
    }
}

You can see that we are making these same assumptions.

  1. That the Request.Files[0].FileName property is clean and doesn’t contain anything untoward.
  2. That the Request.Files[0].FileName property contains just a filename, not a full path.

These assumptions, with the correct amount of defensive code, offsite user uploaded content storage, and a website  running with only a minimum security permissions often isn’t a problem.

This same example applies to ASP.Net MVC:

public ActionResult UserUpload(HttpPostedFileBase userFile)
{
    userFile.SaveAs(Path.Combine(Server.MapPath("~/UserUploads/"),userFile.FileName));

    return View();
}

When we take an incoming user file upload, the filename of the uploaded file shown above userFile.FileName (or Request.Files[0].FileName) actually comes from the browser not the file’s information.

If you had badly configured your application to place user uploaded files in a website directory, and no one was misusing your site it would look like this:

image

This is handed to our application as part of the HTTP POST request in the header.

Browsers should always keep this to just the filename of the uploaded file. We assume that this is always the case.

As they say; the word Assume makes an Ass out of You and Me.

It is very easy to tamper with this part of the HTTP header in the request, and make the filename anything we want.

If you have badly configured your application to store uploaded files inside the website’s root, and your security permissions were quite lax this creates problems.

For interests sake, lets just assume that we tamper with the Http request using something like Fiddler’s Compose feature and make the filename of our uploaded file “../MaliciousFile.txt”?

image 

image

The file wasn’t saved where we wanted it to be at all!

What if we weren’t checking for a file type and a user uploaded an *.aspx page - think of what they could do if they could then run this on your website?

What if someone uploaded and overwrote the default.aspx of your website?

What if the version they overwrote it with was a copy that was exactly the same, but contained some added malicious JavaScript?

This vulnerability is defined as an Unrestricted File Upload and can be a very dangerous security misconfiguration.

Best practice for building file upload functionality

When we talk about things that you should care about from a security standpoint when building applications that accept uploads from site visitors (even authenticated admin’ey ones… sometimes, especially authenticated ones), we often think of a number of things.

File type checks

This is quite a basic item, but if you are asking users to certain file types, check the file extension of the uploaded file to ensure you don’t save anything you don’t want to.

i.e. If you are accepting images check the file extension for JPG, JPEG, PNG, GIF etc

You can also check the first 4 bytes of a file against a file signature table like this one to ensure it really is the same type of file that the extension lets on.

 

File permissions

It sounds silly, but it’s so easy to be too relaxed when “just trying to make it work” during deployment if you just give you app pool user read/write access to your whole site. I’ve seen this often.

File permissions on your website are crucially important when it comes to maintaining its integrity – this is critical when it comes to web applications that accept user submitted uploads.

Ensure that your application pool user only has write access to the folders that you are uploading to – not your entire website, and definitely not other folders on your hard drive.

This is a reason to be avoid using Network Service or SYSTEM or any other user that shares access rights with other services the user that your application pool runs under.

 

User Upload File Location

Any content that a user uploads should not be saved to a folder that is accessible from the website’s URL tree. This means a lot to the security of your website. The double whammy of having something that has been uploaded by a user potentially being run on the website by a malicious user simply accessing a URL on your site is the equivalent of handing hackers a set of golden keys.

Ensure that the permissions that your application is running under has write access to only the folder that you save uploads to.

image

 

User Upload Filename Usage

As I've shown in my example saving a users file using the filename they specify is open to attacks, and it can sometimes be easier, simpler and more secure to not use user specified file names for storage (when possible). If someone can upload a file that has a name they know even if this is stored in an offsite area, and you then have some handler etc. that streams the file out again (i.e. download.aspx?filename=X) it can become easy to figure out the folder structure of your file store.

If you do save user supplied files to your file system, a way to lower risk is to use a proprietary file name and extension (i.e GUID.dat) and store the original filename and file type for later retrieval (database etc). You would then re-add this file name and extension when serving the file back through code later.

This makes it considerably harder to remotely execute files.

image

 

Framework Tips and Tricks

If you’re building with ASP.Net and have to use a user provided filename for storage, ensure that you implement your functionality using code that cleans the filename of anything untoward using whitelisting.

This usually means anything that isn’t considered a safe character (a-z 0-9) – this includes path information (“../” and “C:\badfolder\”) is removed from the filename.

Something similar to:

var fileName = Regex.Match(userFileName,
    @"[^\/|\\]+$",
    RegexOptions.IgnoreCase);

If using web forms, a handy piece of information is that the FileUpload control’s UserControl.FileName property already cleans and white lists the string returned from any extraneous path references to get you out of trouble.

Oh, how moving away from web forms has introduced its own problems…

If using MVC, ensure that you clean the file name from incoming Request.Files[x] using the above regex or a framework method like Path.GetFileName to get rid of anything unwanted from the file name.