There have been some attacks recently against ColdFusion servers that allow users to upload files. This is a common feature on many sites – uploading your profile photo, classified ad pictures, etc. Here is my take on handling file uploads securely.

First, make sure you are not uploading files directly into your webroot. For example if you store your user’s profile photos in /images/profilePhotos, don’t upload your file right into there. You need to put it somewhere safe first and verify that it really is an image file. Adding accept=”image/jpeg” to your cffile tag doesn’t save you here either. There are attacks where the mime type is spoofed, making ColdFusion think an image file is being uploaded, when really its a .cfm file.

So, you need to place the file somewhere outside your webroot. A convenient place to use is your server’s temp directory. You can get the path to a temp directory by calling the built-in getTempDirectory() function. For example:
<cffile action="upload" filefield="userFile" destination="#GetTempDirectory()#" nameconflict="overwrite" result="uploadResult">

The next thing I do is make sure the file that was uploaded is an image. A nice feature is to allow the user to upload any type of image (that ColdFusion supports anyway), so instead of just checking if the file is a jpeg or gif or whatever, I use the isImageFile() function. If it thinks its an image, then ColdFusion supports reading it.

<cfif Not IsImageFile(getTempDirectory() & uploadResult.serverFile)>
    <cffile action="delete" file="#getTempDirectory()##uploadResult.serverFile#">
    <cfset doWhateverErrorHandlingYoudoNow()>
</cfif>

So now that we know the image is good, we can move it into our webroot where it should reside. This is where we do our image conversion, too. I only want jpgs on my site, but I want the user to be able to upload a .tiff or whatever. So instead of using cffile to move the file, we’ll use cfimage to read it in as-is and write it back out as a jpg.

<cfimage action="WRITE" source="#getTempDirectory()##uploadResult.serverFile#" destination="#application.imageDir#/#uploadResult.serverFileName#.jpg">

<cffile action="delete" file="#getTempDirectory()##uploadResult.serverFile#">

Note we use serverFileName here instead of serverFile. serverFileName omits the file extension so we can tack on .jpg ourself. The cfimage tag sees this and converts the file to a jpeg.

You might want to check the file extension first to see if its already a jpeg, and if so just move it with cffile. I like running it through cfimage regardless because it compresses it a little. I just ran a test with a file from my digital camera, the original file was 2.3MB but after running it through cfimage its down to 605kb and it still looks great. You can adjust the compression level by specifying a quality attribute to the cfimage tag. Valid values are 0-1, the default is .75.

If you are on Railo this all works great there too. But if you are on openBD there are some issues. First there is no isImageFile() function on blue dragon. I was able to write a basic one using some built in java libraries. But I’m having issues with using cfimage to convert the file, too. It seems cfimage doesn’t support anything other than png, gifs or jpgs.

Update: If you are running ColdFusion 9, the new ram disk feature would be another good place to store the image file temporarily before you copy it into the final destination under the webroot. There is nothing you need to setup, just write the file to “ram://”. So you would receive the uploaded file like this:
<cffile action="upload" filefield="userFile" destination="ram://" nameconflict="overwrite" result="uploadResult">

I tried uploading some huge files (400mb) just to see what would happen. I got an out of memory error on the page that was uploading the file. My other pages continued to be served just fine. There could be other issues of course. For one thing I’m guessing that if another process needed a lot of memory at the same time someone was uploading a huge file, the other process might not be able to get the amount of memory it needs. I don’t know what CF9 is doing to keep the ram:// area from running the main heap out of space.

6 Comments

  1. funandlearning says:

    Hi Ryan,

    This post is really helpful.

    Thanks.

  2. Prashant says:

    Good post! very helpful

  3. Mike Henke says:

    You are the MAN!!!

  4. Raffael says:

    Great post!
    How do you ensure that uploaded files have unique filenames? your solution could get an error when exact filename already exists in the destination="#application.imageDir#/#uploadResult.serverFileName#.jpg" directory (step cfimage/write). as a first step, you make nameconflict="overwrite" in the temp directory, but later on, the serverfilename could be already present in the "real" destination directory. bad though, cffile action="move", cffile action="copy" and cffile action="write" (and cfimage action="write") have no nameconflict="makeunique" attribute. I think we need a kind of cfdirectory check loop for that filename and append a number or so if already exists. or even make a whole new file with createuuid and check that file for existance before moving/writing.
    what do you guys think about this?

  5. tim says:

    @raffael – one solution I like is to rename the .jpg to the primary key of the record / profile it’s associated with. eg: 1.jpg, 2.jpg. or this could be some other unique identifier. I agree that doing a fileExists() would be a good addition before before writing with cfimage.

  6. anon says:

    ColdFusion has an attribute you can specify in cffile ‘nameconflict’, set this attribute’s value to “makeunique” and ColdFusion will automatically resolve name conflicts. Make sure you use the ‘serverFile’ field of the resulting struct to get the name as it was saved.