Copy link to clipboard
Copied
I recently ran into an issue when interacting with Mailgun's API via CFHTTP whenever we attempted to send a file attachment. The problem is that the recipient's email attachment file name always contains the full path of the file as passed to the cfhttparam file attribute.
For example, if my actual filename was "Sample_Adobe_PDF_Document.pdf", Mailgun would send the attachment as "c:myfilesdocumentsSample_Adobe_PDF_Document.pdf". Weird right?
I created a dummy API endpoint on beeceptor.com to see what CFHTTP was sending Mailgun, and this is the header that ColdFusion sends:
Content-Disposition: form-data; name="attachment"; filename="c:\myFiles\documents\Sample_Adobe_PDF_Document.pdf"
Content-Type: application/pdf
At first I figured this was a problem with Mailgun and that they weren't properly parsing the filename, from the POST data. However, when I look at the web specifications for Content-Disposition Filename, it says:
"Is followed by a string containing the original name of the file transmitted. The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. This parameter provides mostly indicative information. When used in combination with Content-Disposition: attachment, it is used as the default filename for an eventual "Save As" dialog presented to the user"
ColdFusion requires the full path of the file be used in cfhttparam, so I don't think this is a mistake from my end. It also doesn't make sense for ACF to reveal the full internal path of the file in a POST request. Some might argue that sending this data is a security concern.
I tested this in ACF 2016, ACF 2018, and ACF 2021. All versions include the full path of the file in the POST request.
What do you think? Let me know if you think this is a bug and I'll create a ticket.
Thanks for putting together the example, BKBK. The problem is that Adobe isn't following the spec when it generates the Content-Disposition Filename. It outputs the full path of the file like this:
Content-Disposition: form-data; name="attachment"; filename="C:\helloworld.txt"
When it should output like this:
Content-Disposition: form-data; name="attachment"; filename="helloworld.txt"
Spec Reference
There's no doubt you can extract just the filename if you're working with a system you control. Ho
Copy link to clipboard
Copied
That suggests you're passing the value
filename="c:\myFiles\documents\Sample_Adobe_PDF_Document.pdf"
in the cfhttpparam header tag. Well, you shouldn't. As you yourself point out, it could indeed be a security risk.
You could just pass the file's name, literally:
filename="Sample_Adobe_PDF_Document.pdf"
Copy link to clipboard
Copied
Thanks @BKBK. I wish I could do just that. However CFHTTPPARAM doesn't allow the `filename` attribute and the docs specify that the `file` attribute must be the full path to the actual file on disk:
"Applies to File type; ignored for all other types. The absolute path to the file that is sent in the request body."
cfhttpparam(
name="attachment",
type="file",
file="C:\myFiles\documents\Sample_Adobe_PDF_Document.pdf"
);
FYI Lucee handles files in cfhttparam properly and creates the right filename attribute in the request:
Content-Disposition: form-data; name="attachment"; filename="Sample_Adobe_PDF_Document.pdf"
Content-Type: application/pdf
Copy link to clipboard
Copied
I have a question or two.
The reason why I ask is this: it is unnecessary to pass the full file-path in the Content-Disposition header. That is because this header doesn't do the data transfer. It simply informs the receiver to expect the data as key-value pairs from a form. So a file-name, xyz.pdf, is sufficient.
I suspect Adobe ColdFusion and Lucee are doing it in essentially the same way. The way I see it, neither the ACF code nor the Lucee code you've shown will transfer a file! They're both headers, the announcers before the show. 🙂
I cannot remember sending files in this way. But I am guessing you could do so in one of two ways:
<cfset filename="Unleash the Ideavirus - Seth Godin.pdf">
<cfset filepath="C:\Users\BKBK\Desktop\Unleash the Ideavirus - Seth Godin.pdf">
<!--- Read file as binary data --->
<cffile action="readBinary" file="#filepath#" variable="attachedFile">
<!--- Convert binary to Base64 string so we can send it via a form --->
<!--- Alternative: convert to Hex instead of Base 64 --->
<cfset attachedFile=binaryEncode(attachedFile, "Base64")>
<cfhttp method="post">
<!--- Tells the recipient to expect the data via a form, as key-value pairs --->
<cfhttpparam type="header" name="Content-Disposition" value="form-data; name='attachment'; filename=#filename#">
<!--- Tells the recipient that content is PDF --->
<cfhttpparam type="header" name="Content-Type" value="application/pdf"/>
<!--- Sends the actual bytes of the file, as Base 64 string --->
<cfhttpparam type="formfield" name="attachment" value="#attachedFile#"/>
</cfhttp>
The recipient will receive the file as the value of form.attachment. Recipient will then have to convert from Base64 back to Binary.
<cfset filepath="C:\Users\BKBK\Desktop\Unleash the Ideavirus - Seth Godin.pdf">
<cfhttp method="post">
<!--- Tells the recipient to expect content of PDF type --->
<cfhttpparam type="header" name="Content-Type" value="application/pdf" >
<!--- Sends the file in the request body--->
<cfhttpparam type="file" file="#filepath#" name="attachment" mimetype="application/pdf">
</cfhttp>
Copy link to clipboard
Copied
@BKBK, my responses to your questions are below:
<cfscript>
cfhttp(
method="POST",
url="https://angrysam.free.beeceptor.com", // create your own endpoint on https://beeceptor.com/
result="result"
) {
cfhttpparam(
name="attachment",
type="file",
file="C:\helloworld.txt" // replace with a path to a real file on your system
);
}
writedump( result );
</cfscript>
If you look at the data Beeceptor receives, you'll see your server's full path was revealed like this:
Content-Disposition: form-data; name="attachment"; filename="C:\helloworld.txt"
Lucee follows the spec correctly and will use this header instead:
Content-Disposition: form-data; name="attachment"; filename="helloworld.txt"
Copy link to clipboard
Copied
Thanks for the information.
I am having another look.
Copy link to clipboard
Copied
Phew! Now, a bit of breathing space amid the Log4J zero-day troubles.
I have been able to create a test in which the file-path doesn't appear in the Content-Disposition header.
The test:
1) Create the directories
c:\filePost\senderFiles\
and
c:\filePost\receiverFiles\
2) Place a file, testFile.txt in c:\filePost\senderFiles\
3) Create the following directory (uder the web root): /wwwroot/filePost/
Place the following 2 CFM files in /wwwroot/filePost/
<!--- fileSender.cfm --->
<cfset filepath="C:\filePost\senderFiles\testFile.txt">
<cfhttp url="http://localhost:8500/filePost/fileRecipient.cfm" method="post" result="res" multipart="true">
<cfhttpparam type="file" file="#filepath#" name="attachment" mimetype="text/html">
</cfhttp>
<!--- Result of cfhttp post --->
<cfdump var="#res#" >
<!--- fileRecipient.cfm --->
<cftry>
<!--- Dump request data received from post --->
<cfdump var="#getHttpRequestData()#" format="html" output="C:\filePost\receiverFiles\getHttpRequestDataStruct.html" >
<!--- Dump form struct received --->
<cfdump var="#form#" format="html" output="C:\filePost\receiverFiles\formStruct.html">
<!--- Write the recieved file to disk --->
<cffile action="upload" destination="C:\filePost\receiverFiles\receivedFile.txt" filefield="attachment" nameconflict="overwrite" >
<cfcatch type="any" >
<cfdump var="#cfcatch#" format="html" output="C:\filePost\receiverFiles\error.html">
</cfcatch>
</cftry>
4) Launch fileSender.cfm in the browser. That is, something like, http://127.0.0.1:8500/filePost/fileSender.cfm
5) Examine the results. If all goes well, several things will happen:
- the text file will be posted.;
- the new files receivedFile.txt, getHttpRequestDataStruct.html and formStruct.html will be written in c:\filePost\receiverFiles\
If you examine getHttpRequestDataStruct.html you will see that it does not contain the path of the sent file. In addition, formStruct.html shows that the receiver automatically saves the incoming file in the system's temp directory
Copy link to clipboard
Copied
Thanks for putting together the example, BKBK. The problem is that Adobe isn't following the spec when it generates the Content-Disposition Filename. It outputs the full path of the file like this:
Content-Disposition: form-data; name="attachment"; filename="C:\helloworld.txt"
When it should output like this:
Content-Disposition: form-data; name="attachment"; filename="helloworld.txt"
Spec Reference
There's no doubt you can extract just the filename if you're working with a system you control. However, I have encountered issues with 3rd party APIs (e.g. Mailgun) that don't parse the filename and wind up including the path in the final file.
I contacted Adobe and I am happy to announce that they have a working hotfix for ACF 2018 that I have tested thoroughly on top of update 13 in development and production. Hopefully Adobe will release this fix in the next official update for 2018 and 2021, but in the meantime, if anyone else is struggling with the issue, please reach out to ColdFusion support and ask for hf201800-4212578
Copy link to clipboard
Copied
Thanks for sharing that.