Application Security Testing: An Integral Part of DevOps
Working on the online dating service exactodate.com, I had to tackle the problem of permitting users to upload image files during the registration and user-management processes. Having worked with existing content of this nature in the past, I was surprised how much work is involved in something as simple as uploading, saving, and showing images in Web applications.
It involves many issues: permitting file uploads, saving or rendering the image, storing the image in a database, retrieving and re-binding an image at a later time, cleaning up images written to disk, and ensuring that an image contains something you'd actually permit to be on your Web site without human intervention. In truth, I haven't found a satisfactory answer for all of these problems. However, this article covers the mechanics of permitting image uploads, saving images, and rendering images, and it discusses problems with this whole process that don't seem to have solutions. Finally, it wraps up with a call to action.
Some of you may be experts on this subject and may know solutions that don't seem readily available. The call to action is your chance to show the rest of us how much you know. If you have a solution to the difficult challenges mentioned at the end of the article, send me an e-mail. I will post that information in a follow-up article.
Creating the User Interface
Creating the user interface for uploading images is comparatively straightforward. It requires the following three steps:
- Change the <form> tag to include an encoding type of "multipart/form-data."
- Add an HtmlInputFile control from the HTML toolbox tab.
- Change the Accept attribute to accept only image files.
1. Modifying the Form Tag
According to RFC (Request For Comment) 2388, the default encoding type is "application/x-www-form-urlencoded." This encoding type is inefficient for non-ASCII characters and large quantities of binary data. However, I have successfully uploaded images to Microsoft Internet Explorer (IE) with the default encoding type. To be safe, modify the <form> tag for the .aspx page to look something like this:
<form id="Form1" enctype="multipart/form-data" method="post" runat="server">
I found references to the Opera browser reporting an error when uploading files with the default encoding, but using the default encoding seemed to work okay with .jpg files in IE and Mozilla FireFox.
2. Adding the Input Control
A basic HTML input control with type=file will add a textbox and button pair that are pre-defined to display the Open Dialog and load the file when the button is clicked. The easiest way to add this control is to add the File Field control from the HTML tab of Visual Studio's toolbox (see Figure 1).
Figure 1: The Input Field (or HtmlInputFile) Control Shown in an HTML Table
3. Changing the Accept Attribute
To make sure this control is accessible from the code-behind, ensure that the runat="server" attribute is present, provide an IDin the example to follow, "File1"and set the accept attribute to accept images only. For example, "image/*" changes the file type default and is designed to permit uploading image files only. To be sure, you will add some code-behind checks to verify the file type and size.
Saving the Uploaded Image
Clicking Browse displays the open file dialog (or some equivalent), and selecting a file and clicking Open stores that data in a temporary cache on the client. The image will be posted back to the server along with the form's data. The posted file is accessible through the HtmlInputFile control's PostedFile property. However, before you save, render, or store the uploaded file, you may want to perform some sanity checking on it. Listing 1 demonstrates how to check the file size and content type using assertions, and how to save the uploaded file.
Listing 1: Check the File Size and Type, and Save the File to Disk
Debug.Assert(File1.PostedFile != null); Debug.Assert(File1.PostedFile.ContentType == "image/pjpeg"); Debug.Assert(File1.PostedFile.ContentLength < 15000, "file is too big"); File1.PostedFile.SaveAs(filename);
The checks are shown as assertions, and you would want to combine design time assertions with runtime conditional checks, generally performing identical checks. (Remember: Assertions are design-time checks and are for developers only.)
You can derive a file name any way you'd like. One technique I used was to use the Request.ApplicationPath concatenated to a fixed sub-path, the SessionID for a unique folder, and a GUID to try to ensure uniqueness between users.
In Listing 1, if the file is larger than approximately 15k or is not a .jpeg file, the image will not be uploaded and the assertion failure will be displayed in the Output window.