Calling CFFile-Upload Twice On The Same File For Security Purposes

Posted July 17, 2012 at 10:29 AM by Ben Nadel

Tags: ColdFusion

One of the things that I have always loved about ColdFusion's CFFile upload processing is the seamless way in which ColdFusion handles name conflicts. Simply set the "nameconflict" attribute to "makeunique" and you don't have to worry about anything else. Except maybe security. Luckily, I just discovered that ColdFusion allows us to call the CFFile-Upload action more than once on the same file within a single request. This allows us to upload a file to multiple destinations, taking independent advantage of both upload validation and ColdFusion's conflict resolution.


 
 
 

 
  
 
 
 

When we process file uploads, we typically use some variation on the following workflow:

  1. Save the upload to disk (on the server).
  2. Validate the file contents (or file extension).
  3. Integrate the file into our application.

Unfortunately, if the file validation is done in a publicly-accessible folder (ie. under the web root), it can expose a serious security threat. Even if the validation takes place milliseconds after the file has been written to disk, a load-tester can use hundreds of simultaneous requests in order to execute a malicious upload in the clock-ticks between step 1 and step 2 above.

To prevent this from happening, you have to upload files to an intermediary, non-public directory for validation before integrating the file into your app. However, you probably don't want your post-validation file processing to have to worry about moving the file around - this is just an unnecessary complication. Fortunately, you can keep these two steps fairly independent by calling the CFFile-Upload action twice on the same file.

In the following demo, notice that I am calling CFFile-upload once to get the file into a secure location for validation; then, once it is validated, I am calling the CFFile-Upload action a second time in order to complete the file integration.

  • <!--- Param the form fields for the submit. --->
  • <cfparam name="form.submitted" type="boolean" default="false" />
  • <cfparam name="form.file" type="string" default="" />
  •  
  • <!--- I will hold errors generated during the upload processing. --->
  • <cfset errorMessage = "" />
  •  
  • <!--- I will hold the name of the uploaded file (if successful). --->
  • <cfset imagePath = "" />
  •  
  •  
  • <!--- Set the path for the uploads folder. --->
  • <cfset uploadDirectory = expandPath( "./uploads/" ) />
  •  
  • <!---
  • Set the path for our temporary directory - this is the
  • intermediary directory where will upload files that need to be
  • evaluated for safety.
  •  
  • NOTE: This should NOT be a web-accessible directory (however in
  • our demo, it is).
  • --->
  • <cfset tempDirectory = expandPath( "./secure/" ) />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Check to see if the form has been submitted. --->
  • <cfif form.submitted>
  •  
  •  
  • <cftry>
  •  
  • <!--- Make sure a file was selected. --->
  • <cfif !len( form.file )>
  •  
  • <cfthrow
  • type="FileNotFound"
  • message="Please select a file to upload."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Make sure the file uploaded was actually an image. To do
  • this, we have to save the file to disk; however, we want
  • to make sure not to write to a place where an attacker
  • can stage a load-based attack on our file validation.
  • Let's use CFFILE to upload the file to the secure and
  • quarantined temp directory.
  • --->
  • <cffile
  • result="upload"
  • action="upload"
  • filefield="file"
  • destination="#tempDirectory#"
  • nameconflict="makeunique"
  • />
  •  
  • <!--- Check if its an image file supported by ColdFusion. --->
  • <cfset uploadIsNotImage = !isImageFile( "#tempDirectory##upload.serverFile#" ) />
  •  
  • <!--- Delete the file now that we've validated it. --->
  • <cfset fileDelete( "#tempDirectory##upload.serverFile#" ) />
  •  
  • <!---
  • Check to see if we should continue processing. If the
  • file is not an image, there's nothing more to do.
  • --->
  • <cfif uploadIsNotImage>
  •  
  • <!--- ALERT: Possible Attack!! --->
  • <cfthrow
  • type="InvalidImageFile"
  • message="Please select a valid image file."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • If we've made it this far, then we know the user has
  • selected a valid image file. Now, we want to move the
  • file into the uploads directory. Let's use the CFFile
  • tag AGAIN in order to let ColdFusion handle the file
  • conflict resolution.
  • --->
  • <cffile
  • result="upload"
  • action="upload"
  • filefield="file"
  • destination="#uploadDirectory#"
  • nameconflict="makeunique"
  • />
  •  
  • <!---
  • Set the path of the image so we can display it back
  • to the user.
  • --->
  • <cfset imagePath = "./uploads/#upload.serverFile#" />
  •  
  •  
  • <!--- Catch any upload processing errors. --->
  • <cfcatch>
  •  
  • <!--- Set the error message for the user. --->
  • <cfset errorMessage = cfcatch.message />
  •  
  • </cfcatch>
  •  
  • </cftry>
  •  
  •  
  • </cfif>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Reset the output buffer. --->
  • <cfcontent type="text/html; charset=utf-8" />
  •  
  • <cfoutput>
  •  
  • <!doctype html>
  • <html>
  • <head>
  • <title></title>
  • </head>
  • <body>
  •  
  • <h1>
  • Upload An Image
  • </h1>
  •  
  •  
  • <!--- Check to see if we have an error message to display. --->
  • <cfif len( errorMessage )>
  •  
  • <p>
  • <strong>Ooops:</strong> <em>#errorMessage#</em>
  • </p>
  •  
  • </cfif>
  •  
  •  
  • <form
  • method="post"
  • action="#cgi.script_name#"
  • enctype="multipart/form-data">
  •  
  • <input type="hidden" name="submitted" value="true" />
  •  
  • <p>
  • Please select an Image to upload:<br />
  • </p>
  •  
  • <p>
  • <input type="file" name="file" size="30" />
  • <input type="submit" value="Upload Image" />
  • </p>
  •  
  • </form>
  •  
  •  
  • <!---
  • Check to see if we have an uploaded image to display
  • back to the user.
  • --->
  • <cfif len( imagePath )>
  •  
  • <h3>
  • Your Upload:
  • </h3>
  •  
  • <p>
  • <img src="#imagePath#" width="400" />
  • </p>
  •  
  • </cfif>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

By using this kind of approach, we can add file-based validation without complicating the rest of the upload and processing. Notice that by using the CFFile-Upload action a second time, we can still leverage the "nameconflict" resolution (ie. "makeunique") feature provided by ColdFusion.

I used to think that when you called the CFFile-Upload action, ColdFusion would actually move the TMP file out of the server's temporary directory and into the upload destination. Only yesterday did I discover that this was not true. The temporary directory, from my understanding, is cleared out periodically; however, having the TMP file available for multiple CFFile-Upload calls within the same request has some really awesome benefits.




Reader Comments

Jul 17, 2012 at 11:24 AM // reply »
7 Comments

Ben,

In CF10, two attributes - accept and strict have been added that will allow you to restrict the type of file being uploaded. You can provide MIME types or file extensions as a value in the 'accept' attribute. If 'strict' is true then the content of the file is also validated.

I had posted this sometime back http://www.sagarganatra.com/2012/03/coldfusion-10-cffile-restricting-file.html

Let me know if you find this useful.

Sagar.


Jul 17, 2012 at 1:54 PM // reply »
11,314 Comments

@Sagar,

It's definitely going to be useful! Especially if the Accept attribute can contain (depending on mode) file extensions and/or mime-types. Very cool!


Jul 17, 2012 at 7:11 PM // reply »
273 Comments

@Ben,

Yes, cffile action="upload" is essentially a write operation, so you can do more than one.

Once upon a time, I wrote a multipart/form-data handler in C. (Not C++. Good ol' C.) So I know how it works at the HTTP level. The entire file is in memory at the time you decide to do something with it.

So that begs the question, why doesn't cffile action="write" have a nameConflict attribute? When you think about it, it kinda should.

Of course, in either case, upload or write, you could always do a FileExists call. I guess Allaire just went with the odds that you're more likely not to know whether the same file name already exists in the case of an upload.


Jul 18, 2012 at 6:37 AM // reply »
5 Comments

Why exactly would you need the file in a specific location? Since it is already in memory you can just call

  • isImageFile( form.file )

directly. Or are there functions you won't be able to call on it like this?


Jul 18, 2012 at 9:36 AM // reply »
11,314 Comments

@WebManWalking,

I would actually love to have name-conflict resolution in a File-Write call. It's one of those things that you don't often need; but, when you do, it would be sweet!

@Ralph,

Ah, awesome suggestion! In this case, that works perfectly. I'm working right now on factoring this out a bit to use file metaData instead of the file content - just a different take on it.


Jul 18, 2012 at 9:41 AM // reply »
11,314 Comments

@Ralph,

Actually, I just tried that and it seems to fail (at least in ColdFusion 8). I guess since the file has a ".tmp" extension at that point, ColdFusion doesn't see it as a valid image file extension. I guess, for performance reasons, ColdFusion doesn't actually look at the file content.


Jul 18, 2012 at 10:07 AM // reply »
11,314 Comments

@All,

I've refactored this concept out into its own utility function:

http://www.bennadel.com/blog/2399-Getting-The-MetaData-For-A-File-Upload-In-ColdFusion.htm

... this time, looking at a subset of the meta-data exposed by the natural CFFile-Upload action.


Jul 18, 2012 at 10:49 AM // reply »
5 Comments

@Ben,

Difference is probably that I'm using Railo than :)


Jul 31, 2012 at 9:30 AM // reply »
11,314 Comments

@Ralph,

Ha ha, Railo always trying to stay a few steps ahead in some cool areas!


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
seb
Jun 20, 2013 at 2:32 AM
Working With Inherited Collections In AngularJS
@mike, @ben, The best article about scope and prototypal prototypical inheritance in angularjs is http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical- ... read »
Jun 20, 2013 at 2:17 AM
ColdFusion NumberFormat() Exploration
Nice read thanks Ben, Is there a way to mask a negative number? Long story short in the finance sector when you go 'short' on a stock you want the price to fall this is a good thing because you are ... read »
Jun 20, 2013 at 1:09 AM
The Beauty Of The jQuery Each() Method
my html code : <html> <head> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="nss.js"> ... read »
Jun 19, 2013 at 11:31 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Ben, bunch to learn indeed, but thats fun part : ) ... read »
Jun 19, 2013 at 10:41 PM
Referencing ColdFusion Query Columns In A Loop Using Both Array And Dot Notation
Burdock-roots Are you going fat day by day? You need to be good for your family and make some money too. So we bring for you a best product that helps you to be more energetic every day. You will b ... read »
Jun 19, 2013 at 9:52 PM
Working With Inherited Collections In AngularJS
I recognize the applicability of your solution, and how easy it makes to share data across multiple views or even "submodules" of rather simple application. But it seems to me that it creat ... read »
Jun 19, 2013 at 9:38 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Alesei, Glad you like it. Even after working with AngularJS for months, I still get a bunch of unexpected, "$digest is already in progress". So hard to debug sometimes! ... read »
Jun 19, 2013 at 9:36 PM
Working With Inherited Collections In AngularJS
@Mike, The relationship of $scope values is definitely an interesting thing! But it's not simple - it really forces you to understand prototypal inheritance, which is not at all a simple topic! Gla ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools