Tips for Secure File Uploads with ColdFusion
June 24, 2009
Allowing someone to upload a file on to your web server is a common requirement, but also a very risky operation. So here are some tips to help make this process more secure.
Don't rely on
accept attribute gives a terrible false sense of security. Every time I present on CFML Security I ask the question: If I have this code is there any way I could upload a CFM file?.
The answer is YES, often to the surprise of most!
The cffile accept attribute uses the mime type that your browser sends to the server. Read that again... your browser tells cffile what the mime type is. It's very easy to spoof the mime type. For example:
<cfhttp url="http://target.example.com/upload" method="post">
For this reason you need to ensure that
cffile.serverFileExt is of an extension you allow, and one that your server will not execute.
Use a file extension whitelist rather than a blacklist, in other words you don't just check to make sure it is not a .cfm, make sure it is only one of: "jpg,png,gif". This way if someone installs PHP on your server, you don't have to update the code to block that file extension as well.
Validate that the File Extension matches File Format
You can use a Java API like JHOVE which reads the file contents to validate that it is of the asserted file format. It supports jpg, gif, pdf, tiff, and more.
In addition CF8 has the
IsPDFFile("path") functions you could use. If you do use
IsImageFile just make sure that you have upgraded your JVM to one that doesn't have the issue that can cause an image file to crash your server. See Mark Kruger's blog entry for details.
Always upload to a temp directory outside of the Web Root
Suppose I ran the same hack above with
cfhttp but you now have code in place to delete the file if the extension is incorrect. There is a slight chance that I could execute that file before you can delete it if you uploaded it into the web root (and I could predict where it would be placed).
Once you have validated the upload, you can move it to its desired location.
Keep uploaded files outside the web root
If possible keep uploaded files outside of the web root and serve them with
cfcontent. In some cases this is not possible, but seriously consider this as it does ease the risk significantly.
Remove execute permissions from upload directories
The reason for this should be obvious, but is something we often forget to do.
Upload to a static content server
If possible upload content to a server other than the application server, a server that only serves static content (for example Amazon S3).
Don't trust on the client supplied file name
The client supplied file name could possibly contain SQL Injection, cross site scripting, or CRLF Injection.
It's best to strip out non alpha numeric characters (perhaps with the exception of dash and underscore).
ColdFusion Administrator Settings
There are a few Administrator Settings that you should pay attention to related to file uploads (and large HTTP POST operations). They are under Server Settings » Settings under the heading Request Size Limits:
The first setting is the maximum size of a POST, and therefor also a file upload. The default 100mb is probably bigger than needed for most web apps, you can lower it to mitigate DOS potential. Chances are your web server is also capable of limiting the post size, on apache you can use the
LimitRequestBody directive to do this.
The next setting Request Throttle Threshold should probably be lowered to 1MB, this puts any request larger than 1mb into a throttle for synchronous processing.
The third setting Request Throttle Memory is the maximum size of request throttle queue. The default is kind of high, if you don't have a lot of large file uploads going on at the same time this should be lowered to say 50mb (it shouldn't be lower than the Maximum size of post data, or the Request Throttle Threshold, but it could be equal to the max size.). Consider that on a 32bit server, the max JVM size is typically not much bigger than 1GB, you could allow 1/5th of your server resources to be consumed by file uploads with the default setting.
If you are using the Enterprise edition of ColdFusion you can setup a sandbox for your file upload directory, and remove execute permission. This only applies to ColdFusion template execution (not PHP scripts for example).
Restrict using Web Server
Use you should limit your uploads directory to only allow static files to be requested. For example on IIS you can remove the handler mappings for CF, and then use Request Filtering to limit file extensions to a specific whitelist, so that IIS will reject any request under /images/ that is not a .gif, .png, or .jpg for example.
Do you have any additional tips?
Trackback Address: 701/EF20EBCFDE17E3640C4E91EBFA85B5FB
This may be a silly question, but if someone is uploading from a Mac, will it still be able to verify from the extension if there isn't one?
I think the browser may be able to send the appropriate mime type if there is no file extension (I would have to look into that further), but remember you can't trust what the browser sends anyways, it could be spoofed.
Pete, excellent tips. Disabling execute permissions on uploads directory is really nice.
Great set of tips; I'd also suggest that if you have Apache, watch out for any uploaded files that have multiple file extensions (e.g., file.php.html). By default, Apache will run the file with the PHP handler even though the last extension is something else. See http://www.mollerus.net/tom/blog/2009/02/watch_out_for_apache_handling_of_files_with_multip.html
@Tom Great tip, thanks for posting
I'm revisiting an app that allows customer file uploading, and one approach I'm considering is using CreatUUID() to generate a server side file name and stick the customer provided filename in a related database entry (going through cfqueryparam, of course).
Pete, great post. Thanks for the tips. I've been meaning to blog about this myself. You beat me to it. But you also covered quite a lot that I didn't know, so thank you for that. Very enlightening.
@jason olmsted - Make sure you sanitize the customer supplied file name before sticking it in the db, as they could potentially include something to trigger XSS, you can use this regex: ReReplace(fileNane, "[^a-zA-Z0-9_-]", "", "ALL"), and ofcourse still use cfqueryparam (always use it :)
@jason dean - glad you found it useful, feel free to post about this on your blog as well. The more people who read about it the better.
Great tips, thanks for sharing, Pete. I'm comforted by the fact that I tend to follow all suggestions you've made, with the exception of a static content server. I really do like that idea and intend to leverage Amazon S3 for static content whenever possible in the future.
It's worth noting that you could achieve similar security on your own server, if needed, by leveraging Apache and creating a static content virtual host. You can have that virtual host limit file extensions and/or mime types and rewrite to 403-forbidden any suspicious requests.
@Jamie thanks, yes that is worth noting. I didn't intend to suggest that S3, or some third party CDN was the only way.
Just so I'm clear: the problem isn't really that they can upload a CFML file, it's that they can predict the URL afterwards in order to execute it, right?
If so, placing an Application.cfc / Application.cfm file in your images directory that blocks requests would be sufficient to prevent execution?
Or am I missing something?
@Sean - They don't necessarily have to be able to predict it, the application may disclose it in an image tag, or link.
Adding Application.cfc/cfm is a good idea, but it can only block ColdFusion requests, I could upload a .jsp file instead, or some other file type that might be executed server side.
Extending the sandbox design:
We protect uploads from getting downloaded, without the application running more CFMX code to authorize:
Having Enterprise on *IX, we build a sandbox, where
* doc, a directory outside the Apache directories, allows read, write, delete for CFMX. OS permissions allow only j2ee to write, any can read.
* backend, another directory outside Apache, allows only read for CFMX. OS permissions allow only the project owner to write, any can read.
Now CFMX code can scan the backend directory and authorize what the user can see.
Meanwhile Apache can't leak the files on its own. Application code must decide whether to read from those directories, and decide what to send to who.
(And it's late, so I'm too tired to clean the grammar.)
Anyway, dittos on all the rest, and THANKS! Hope it helps.
(sorry forgot to subscribe, I'm way interested in feedback)
What about if you have CFIDE available for a shared server (for CFAJAX stuff) - are you then more vulnerable for these attacks that wreak havoc in the CF-community these days?
And how to defend yourself and your server (and hostingprovider)?
PS: My Gravatar is enabled via my Hotmail address - any chance you'll allow those mail-extensions in the future?
@Sean, You make an excellent point I haven't thought about. You can effectively disable CF from ever executing from that folder with the right application logic. However, it still leaves open the possibility that bad files can 'exist' on the server to be exploited outside of being executed by CF.
@Pete, How could I do a good file extension test before the file itself has been uploaded by CFFILE even using all the security mesures pointed here? Or the file will be uploaded first, then tested for extension and finally deleted by CFFILE DELETE? Thanks.
Check out Pete's "Always upload to a temp directory outside of the Web Root" section, above.
@Marllon Yes as Jamie mentioned you need to upload the file to a dir outside of the web root and then check the file extension. Otherwise the only way you could do this before calling cffile would be to use a Servlet Filter, or something else that runs before the CFML engine.
I'd just like to point out, in response to the first commenter, that Mac OS X files do indeed have file extensions. By default they are hidden to the user but upon sending a file out (as in this case) they do apply.
I just wanted to chime in to remind people that the same goes for emails which attachments that are downloaded by CFpop. I have a system which acts as a web client for accessing email ( <gasp> and saves the attachments into a pub directory</gasp> ), and I basically crapped myself when I realized that I was doing no checking, and someone could have simply emailed us a CFM file. My two faults here are A: serving attachments publicly and B: Not filtering attachments. Very old app, but Jeeze! Horrible.
Hi, I've seen comments about checking for a double file extensions. Does anybody have any code that would allow me to do this.
Very helpful tips. Thanks.
Does anyone have any suggestions for virus scanning on ColdFusion file uploads? We want to allow trusted sources to upload files to us, but we are worried about files with viruses being accidentally uploaded. Thoughts? Thanks.
Just to let you know your site looks a little bit strange in Opera on my laptop with Linux .
Joe Cairns / Hi, thanks for the tip on the file upaold technique. One problem will crop up if you are developing on a windows machine.The line:File.open(”/Users/db/_dev/rails/videos/swing.mov”, “w”)Should be changed to:File.open(”/Users/db/_dev/rails/videos/swing.mov”, “wb”)Windows, unlike linux, unix, and mac, does not assume a created file is a binary, it assumes a text file. By adding the b to the parameters you are forcing the file to be a binary. I don't know whether using the b option will cause a non-windows install problems or not though.-Joe