Tuesday, 11 March 2008

File Denial

Let authorized users download files from your site - and keep unauthorized users out!

By Steve C. Orr

Introduction

A common web site requirement is the ability to share certain files with specific users. For example, customers who have purchased software or other digital assets from a web site should ideally be able to return to that site and re-download the file(s) any time they want. However, that user (and other users) must be prevented from downloading packages that they have not purchased.

This article will not attempt to cover ASP.NET user authentication techniques since that topic is already thoroughly covered elsewhere. Instead this article will focus on how (and how not) to provide access to a file once it has been determined the user is indeed authorized to download it.

How Not to Share Files

Many webmasters have a public folder underneath their web root somewhere that holds downloadable files. When appropriate, the user is provided with a hyperlink to a file within that folder which allows the user to download it. While this approach is common, it is also very insecure since anyone that knows that URL can download the file. The only security in place here is "security through obscurity", since it relies completely on users not knowing information; users that have not been given the URL will not know that they can download it. However, a user could easily give the URL to other people or post it on the Internet, or a hacker could intercept it by probing network traffic, etc. Before you know it everybody has the URL and can download at will.

Because of this insecurity, valuable files should never be placed in a public folder, unless perhaps it is an Intranet application (not available over the Internet) and is secured by Windows Authentication with the appropriate ACLs set on the files.

Private Parts

Instead, files should be stored in a secure location and managed via code to allow downloads only for select individuals. There are two main accepted techniques for storing files securely in an ASP.NET web site:

  1. Store the files in a private folder that is directly inaccessible to web users.

  2. Store the files in a database (such as SQL Server).

Both techniques have potential performance implications, depending primarily on the number and size of files. Other factors include the frequency of file updates & uploads, the number of concurrent users, hardware capacity, bandwidth, etc. So if top performance is a top priority then you should test both techniques to see which is better for your particular application.

Private Folders

When storing downloadable files on a server's file system, instead of storing them in a public folder (such as c:\inetpub\wwwroot\myapp\downloads) they should instead be stored in a folder outside the web application's directory hierarchy (such as d:\downloads).

If it's not possible to store the files outside the application's folder hierarchy (as the case may be if you're using a shared host) then the download folder should be secured so it cannot be directly accessed via the Internet. This can be done via IIS (by turning of all permissions off to that folder as shown in Figure 1) or via Windows ACL settings (as shown in Figure 2) by removing permissions. Alternately, many shared hosts have custom user interfaces that allow the configuration of such settings.


Figure 1: IIS can be configured to deny direct Internet access to specified folders.


Figure 2: The standard Windows security dialogs can be used to adjust ACLs and prevent access to unauthorized user accounts.

The Response.WriteFile method can be used to send a file to the user dynamically from a private folder once it has been determined they are authorized to download it. Here's an example:

Response.Clear()
Response.ContentType = "application/ms-excel"
Response.WriteFile("d:\downloads\Confidential.xls")
Response.AddHeader("Content-Disposition", "inline;filename=Confidential.xls")
Response.End()

Since this code is sending out a file instead of HTML, the first line clears the page's Response object to get rid of any HTML that may already be buffered in the output stream. Then the content type is set on the second line to inform the browser that the output is (in this case) an Excel file. The Response.Writefile method is then invoked on the 3rd line to grab the file from the web server's private folder and output its contents. On the fourth line of the above code sample, a header is added to the response object to let the browser know that it may open the Excel file embedded within the browser. (Replace "inline" with "attachment" to specify that it should be opened in an external Excel application window instead.) Finally, the Response is ended to ensure nothing else is written to the Response stream.

Note: In some cases you may need to use impersonation or grant access to the ASPNET user account (aka NetworkService) in order for ASP.NET to be able to access a private file share.

Database Storage

Applications that require large numbers of downloadable files - or allow users to upload files - may benefit from storing those files in a database instead of the web server's file system. By leveraging SQL Server's data management capabilities you can avoid writing reams of custom file management code that may otherwise be necessary.

SQL Server 2000 can store binary file data in a field of type image. SQL Server 2005 has improved binary storage capabilities in the form of the new varbinary data type.

The following block shows VB.NET code that retrieves binary data from a SQL Server query and sends the resulting file to the user:

Dim dr As DataReader = GetFileCommand.ExecuteReader
If dr.Read Then   
  Response.Clear()  
  Response.ContentType = dr("ContentType").ToString  
  Response.OutputStream.Write(CType(dr("FileData"), Byte()), 0, _  
  Convert.ToInt32(dr("FileSize")))  
  Response.AddHeader("Content-Disposition", "attachment;filename=" + _         
  dr("FileName").ToString())  
  Response.End()  
End If

Just as in the previous example, the Response object is first cleared of any buffered HTML since a file is being output instead. Next the content type that was stored along with the file is retrieved from the DataReader's current row and assigned to the Response's ContentType property.

The third line is the most important. The file data is written to the Response's output stream after the binary data is converted into a byte array. The file size that was also stored along with the file data is cast into an integer and passed as a parameter to the write method.

Finally the file's name is retrieved from the DataReader and provided to the browser via the Content-Disposition header, and the Response is ended to prevent anything else from slipping into the output.

Summary

You should now know how to provide ASP.NET file download capabilities to authorized users without compromising the security of those files. By storing files in a private folder or database, they can be easily retrieved with a just few lines of code - after you've determined the user is authorized to access the file.

References

For further reading on this subject you may wish to review these articles:

Easy Uploads

 

No comments: