Skip to main content
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Luis Majano
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with: Luis Majano ( @lmajano )

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

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

15,663 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!

290 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.

6 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?

15,663 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.

15,663 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.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel