Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Kevin Staton
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Kevin Staton

Inspecting The Form Upload File Field Metadata In ColdFusion

By
Published in Comments (6)

When you upload a file in ColdFusion, the fileUpload() function and the CFFile[action=upload] tag aren't actually uploading the file to the server—at that point in the workflow, the file already exists on the server. The fileUpload() function is just moving the file from a temporary location to a permanent location of your choosing. And, when you're uploading files through a standard form post, the form field that represents your file upload contains the path to that temporary location. Which means you can therefore inspect a file in ColdFusion before you move it to its permanent location.

To see this in action, we're going to create a form that allows an image file to be uploaded. And, before we "upload" the file (vis-a-vis the fileUpload() function), we're going to get the size and dimensions of the given image while it's still in the server's temp directory.

Our form will only have a single field, imageFile; and, for the sake of simplicity, we're going to know that the form has been submitted by looking at the length of this field value. If it's populated, it means that it contains the path to the already-uploaded temporary file:

<cfscript>

	param name="form.imageFile" type="string" default="";

	// If the imageFile form field has a length, it contains the path to the file that has
	// already been uploaded into the server's temp directory. When you call fileUpload(),
	// or cffile[action=upload], you're not really uploading the file. More accurately,
	// you're just MOVING the file from a temporary location to a permanent location.
	if ( form.imageFile.len() ) {

		// Caution: All HTTP requests can be spoofed. Just because the form field has a
		// value in it, it doesn't mean that we can trust it. Make sure that the directory
		// that contains the temporary file is actually the expected directory.
		// --
		// Note: This is only meaningful when directly accessing the file in this type of
		// workflow. A fileUpload() workflow shouldn't require this type of check.
		if ( getDirectoryFromPath( getCanonicalPath( form.imageFile ) ) != getTempDirectory() ) {

			throw(
				type = "UnexpectedFileLocation",
				message = "Suspicious temporary file path."
			);

		}

		// Since we have access to the temporary file location, we can directly access the
		// file in order to gather metadata such as byte-size and, for images, dimensions.
		fileMetadata = {
			file: getFileInfo( form.imageFile ),
			image: imageInfo( imageNew( form.imageFile ) )
		};

	}

</cfscript>

<!doctype html>
<html lang="en">
<body>

	<h1>
		Upload An Image File
	</h1>

	<form method="post" enctype="multipart/form-data">
		<input
			type="file"
			name="imageFile"
			accept=".png, .jpg, .jpeg"
		/>
		<button type="submit">
			Upload Image
		</button>
	</form>

	<cfif ! isNull( fileMetadata )>
		<h2>
			File Metadata
		</h2>

		<cfdump
			var="#fileMetadata#"
			label="Uploaded File"
		/>
	</cfif>

</body>
</html>

Before I inspect the temporary file, I'm taking some security precautions to ensure that the given path exists in the server's temporary directory. Remember that every HTTP request can be spoofed by a malicious actor. Just because the form field contains a value, it doesn't inherently mean that it can be trusted. Normally, you wouldn't have to think about this check. But, since we're inspecting the file before we use ColdFusion's native upload functionality, it can't hurt to verify the location.

That said, you can see that I'm using the value of the form field to call the getFileInfo() and imageInfo() ColdFusion functions. And, when we run this Adobe ColdFusion page and look at the resultant data (for an uploaded image), we get the following output:

CFDump of the getFileInfo() and imageInfo() results in ColdFusion.

Notice that the path and source attributes in the getFileInfo() and imageInfo() results demonstrate that the file is being inspected while it's still residing within the server's temporary directory. This means that you can use this metadata to further restrict file uploads (such as blocking zero-length files) before even trying to call the fileUpload() function.

Now, to be clear, I still recommend that you ultimately use the fileUpload() function (or other related ColdFusion features) to move the uploaded file out of the temporary location. Those upload functions and tags apply additional security measures—such a blocking file extensions and validating mime-types—and provide information about the original file details.

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

Reader Comments

6 Comments

Great article! I'll add that getCanonicalPath() behaves a little differently on Lucee vs. ACF. On ACF, getCanonicalPath() will return an error if you pass in Virtual Filesystem paths, and not fetch the path. But Lucee will accept local filesystem paths or VFS paths, so you need to worry about Server Side Request Forgery attacks in the sample code. If the user passes something like http://some-url-here/ in form.imageFile, the server will request that URL. One way to avoid that could be to do something like this, so you're not passing user-controlled data into a function that will fetch paths and URLs before you validate the data:

my.newImageFile = (getTempDirectory() & GetFileFromPath(form.imageFile);
// more validation stuff here

I wrote about how various CFML tags and functions can be vulnerable to SSRF a few years ago - https://www.hoyahaxa.com/2021/04/ssrf-in-coldfusioncfml-tags-and.html

15,902 Comments

@Brian,

I can't believe that the server will request a URL if you're just trying to normalize the path. That's bananas! Feels like the Log4j issues we had a few years ago, where libraries become too flexible for their own good and then people figure out how use them maliciously.

I like you're idea of just getting the file from the path. In that case, I could have generated the expected file path and then checked to see if it existed before trying to read it. For the sake of other readers, building on your suggestion:

expectedPath = ( getTempDirectory() & getFileFromPath( form.imageFile ) );

if ( ! fileExists( expectedPath ) ) {
	throw(
		type = "MissingTempFile",
		message = "Temp file is not in the expected location - possible malicious action."
	);
}

fileMetadata = getFileInfo( expectedPath );
imageMetadata = imageInfo( imageNew( expectedPath ) );
// ... more processing ...

I had originally played around with using expandPath() on the form field. But, the leading / in the path was causing the field to be expanded into my web-root, so it was looking like:

/app/wwwroot/tmp/neoadklfjalkdjflaf.tmp

I could probably override the / mapping, but I was afraid that might break something else.

Anyway, thanks for the link - I'll take a closer look at your writing - sounds like some dragons to be uncovered.

6 Comments

@Ben - There are probably many ways to avoid this, and I have no idea which way is best. The perennial issue of needing to carefully validate user-supplied input! In Lucee you can do something like GetPageContext().formScope().getUploadResource(arguments.formField).getName() to get the filename of the multipart form data part -- which will be blank and return an error if you just pass in a URL or other path. This doesn't work in ACF, and I'm not sure if there's an equivalent function.

One of the functions I was surprised always worked this way in Lucee and ACF was fileGetMimeType(). It has a boolean parameter named "strict". If "strict" is set to false, it determines the file type by extension -- but will still fetch URLs!

15,902 Comments

WHOA WHOA! There's a function called fileGetMimeType() 😮 WTF!? And it's been there since CF10 - how am I just finding out about this! Good sir, you just blew my mind!

Post A Comment — I'd Love To Hear From You!

Post a Comment

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