Inspecting The Form Upload File Field Metadata In ColdFusion
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:
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
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 likehttp://some-url-here/
inform.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: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
@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:
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.
@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!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!This is great - I did a little exploration of the
fileGetMimeType()
function:www.bennadel.com/blog/4727-using-filegetmimetype-to-determine-file-type-in-coldfusion.htm
Many thanks to Brian!
Thanks for the shoutout! Glad it was helpful!
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →