Week 6: Handling File Uploads
Online photo albums, document repositories, content management systems, all of these applications have one thing in common, they generally allow a user to upload files to a remote server. A server, that in most cases, hosts numerous other applications. A server that may store extremely sensitive customer information. A server, that if compromised, could result in the theft of customer records, confidential corporate information, or potentially compromise the entire corporate network. It's true, even the simplest thing of letting a user upload an image to your server, could result in the complete compromise of your entire corporate network. When's the last time you performed proper security testing on your upload functionality?
When allowing user's to upload files to your server, there are several areas we must pay particular attention to. These areas include proper input validation of valid file types, the location the uploaded files will be stored and the various permissions associated with the file storage. A simple mistake in anyone or all of these areas can result in a catastrophic compromise of your server. So it is absolutely essential when allowing a user to upload files to your server, you put your best security foot forward and enforce strict rules associated with this functionality.
Generally, when we allow a user to upload a file to our server we put a restriction on the acceptable file types we will allow them to upload. In some cases we may allow image files, such as .jpg, .gif, .png, etc. Whereas in other cases, such as that of a document repository, we may allow files such as .doc, .pdf, .xls, etc. In either case we want to restrict the types of files to a predefined list and disallow all other types of files. The only guaranteed manner we can enforce proper file type restrictions is through proper input validation. This validation must occur on both the client, as well as the server (input validation should always be done in both locations). The validation must check several things, including the file extension, the content-type and the file name itself.
function CheckFileExt(fileName, allowedFileTypes)
dot = fileName.split(".");
fileType = "." dot[dot.length-1];
if(allowedFileTypes.join(".").indexOf(fileType) != -1)
alert("Invalid File Type!");
In order to call the validation script above we will use the OnChange event of the file input control, as seen in Figure 2.
OnChange="return CheckFileExt(this.value, ['gif', 'jpg', 'png', 'jpeg']);"
The next area to consider for validation is the content-type and content itself. We can't solely trust a file by its extension alone, we must validate additional areas of it to ensure it is safe. It wouldn't be difficult for a malicious user to place the text seen in Figure 3 into a file and save it as a PDF document. This would allow the malicious user to bypass the file extension validation, assuming a PDF document was allowed, without the file actually being a valid PDF document.
Figure 3 The script tag below has been modified to render, rather than execute in your browser.
-%PDF < script>alert('Not A Valid PDF Document');< /script>
The text above placed into a file and saved as a PDF document, would not only bypass the file extension validation, but it would also bypass the content-type validation. Since the text contains a valid PDF signature ("-%PDF"), the content-type will assume it is a valid PDF document. The validation process must also validate the content of the file itself. By validating the actual content we would be able to make the determination that the file was not a valid PDF document.
The last area of input validation we need to look at is the actual file name. We would need to perform some validation to ensure the file name does not contain a characters that could be used maliciously. Characters such as < > /, etc. could be used maliciously to execute script or perform a directory traversal attack. Another cause for concern is a null byte(%00). Most servers interpret the null byte character as the end of a line or statement. Thus a file named "Test.asp%00.jpg", would bypass our file extension validation, and potentially our content-type validation. However, if the file were called directly in the browser by typing "http://mydomain.com/Test.asp%00.jpg". The server would interpret that everything after the should be dropped, thus the request would be changed to "http://mydomain.com/Test.asp". We'll see in the next section why this could be a major cause for concern.
Now that we have a better idea of how to perform proper validation on an uploaded file, we need determine where we are going to store the files a user has uploaded. Obviously a database is a bad place to store uploaded files. We would need to store the files in a server's file system. Most developers make the mistake of storing uploaded files directly with the web root. The primary issue with this is if a malicious user can determine the link to a file, they could possibly, depending on access permissions, directly request the file just by typing the URL into the address bar in their browser. This could potentially lead to the unnecessary disclosure of sensitive information. Another potential issue with storing uploaded files within the web root, is the possibility that the folder containing the files could allow the execution of scripts or executables.
In summary the primary concern is validation, validation, validation. We, as developers, need to ensure we validate every aspect of a file that is being uploaded. This includes the file extension, the file name, the content-type and lastly the content itself. Doing all of this still doesn't guarantee the file is 100% safe, but it will go a long way in preventing malicious files form being uploaded to our servers. We also need to ensure we aren't storing the files in the web root, because doing so could allow malicious users to directly request the files in their browser, thus potentially exposing sensitive information. Finally, if we must store the files in the web root we must ensure the folder permissions containing the files do not allow for the execution of script or executable code. Adding these preventive measures will go a long way in ensuring your user's and your server won't be compromised due to malicious file uploads.