<<

Security

File Uploads

By David Epler (Bio)
gist

Accepting file uploads is another common requirement for web applications, but also pose a great risk to both the server and the users of the web application. If not handled correctly, an uploaded file can lead to a compromised server or spread a virus infected file to other users.

The default behavior of the file upload should be to delete the file if it does not pass a validation check. When the file has passed all the checks, move it to the proper location using a system generated file name.

The first and most important thing is that files should NEVER be uploaded to a web accessible directory. They should always be placed in a temporary location, generally the ColdFusion temporary directory from GetTempDirectory(). On UNIX systems should also restrict access to the uploaded file by specifying the mode attribute, preferably 600 so that only the ColdFusion process can read or write to the file.

The types of files accepted in the upload should always be limited through the ACCEPT attribute and not allow all file types. There were several changes to cffile action="upload" in ColdFusion 10 on how it handles what file types are allowed. In previous versions, the ACCEPT attribute only allowed for a list of mime types like "image/jpeg,image/png,application/pdf"; in ColdFusion 10 you can use a list of file extensions like "*.jpg,*.png,*.pdf". You should not mix the two in the attribute; use one or the other.

Also new in ColdFusion 10 is the strict attribute which defaults to true. In previous versions of ColdFusion, the mime type (content-type and content-subtype) were based upon what the client told ColdFusion the file is, not the actual contents. It was possible to check specific file types by using IsPDFFile(), IsImageFile(), or IsSpreadsheet(), but that left out a lot of other valid files that could not be checked. With strict set to true, the mime type of the file is checked when the file upload occurs; however, this means that ACCEPT must be a list of mime types and not file extensions. If ACCEPT has a list of file extensions and strict is set to true, ColdFusion will throw an error.

ColdFusion 10 introduced a new function, FileGetMimeType(), which can now return the mime type for any file.

<cfscript>
    variables.validMimeTypes =  {'application/pdf': {extension: 'pdf', application: 'Adobe Acrobat'}
                                ,'application/vnd.ms-powerpoint': {extension: 'ppt', application: 'PowerPoint (97-2003)'}
                                ,'application/vnd.openxmlformats-officedocument.presentationml.presentation': {extension: 'pptx', application: 'PowerPoint (2007)'}
                                ,'image/jpeg': {extension: 'jpg'}
                                ,'image/png': {extension: 'png'}};  
</cfscript>

<cftry>

    <cffile action="upload" filefield="uploadFile"
            destination="#GetTempDirectory()#" mode="600"
            accept="#StructKeyList(variables.validMimeTypes)#"
            strict="true"
            result="request.uploadResult"
            nameconflict="makeunique" />

<cfcatch type="any">
    <!--- file is not written to disk if error is thrown  --->
    <!--- prevent zero length files --->
    <cfif FindNoCase("No data was received in the uploaded", cfcatch.message)>
        <cfabort showerror="Zero length file" />

    <!--- prevent invalid file types --->
    <cfelseif FindNoCase("The MIME type or the Extension of the uploaded file", cfcatch.message)>
        <cfabort showerror="Invalid file type" />

    <!--- prevent empty form field --->
    <cfelseif FindNoCase("did not contain a file.", cfcatch.message)>
        <cfabort showerror="Empty form field" />

    <!---all other errors --->                           
    <cfelse>
        <cfabort showerror="Unhandled File Upload Error" />

    </cfif>
</cfcatch>
</cftry>           

<!--- get actual mime type --->
<cfset variables.actualMimeType = FileGetMimeType(request.uploadResult.ServerDirectory & '/' & request.uploadResult.ServerFile, true) />

<!--- redundant check with strict="true", does not hurt to double check Adobe --->  
<cfif NOT StructKeyExists(variables.validMimeTypes, variables.actualMimeType)>
    <cffile action="delete" file="#request.uploadResult.ServerDirectory#/#request.uploadResult.ServerFile#"  />
    <cfabort showerror="Invalid file type (checked)" />
</cfif>

<!--- generate unique filename for move to destination, DO NOT reuse filename sent by user --->
<cfset request.uploadFile.destination = ExpandPath("/") & "uploads/" & CreateUUID() & "." & variables.validMimeTypes[variables.actualMimeType]["extension"] />

<cffile action="move"
        source="#request.uploadResult.ServerDirectory#/#request.uploadResult.ServerFile#" 
        destination="#request.uploadFile.destination#" mode="644" />

Additional Measures

If possible limit file uploads to only authenticated users of the web application.

What is not shown through the code sample above is processing the upload through any type of virus scanner or any additional file size checks that could be done beyond the post limit size set in ColdFusion Administrator or through the web server configuration.

Also, additional restrictions could be put in place using OS level permissions and/or using Sandboxing within ColdFusion to limit where ColdFusion can read and write the file.