Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Josh Knutson and Scott Smith
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Josh Knutson Scott Smith

For Better Security Use HtmlEditFormat() In Conjunction With JSStringFormat() In ColdFusion

By
Published in Comments (9)

ColdFusion 9 (and earlier) provides several methods for escaping values in various contexts. ColdFusion 10 adds several more of these functions, with a nod to the OWASP security project. But, for the time-being, I wanted to talk about ColdFusion 9's jsStringFormat() and htmlEditFormat() functions and demonstrate why they should be used in conjunction inside of a JavaScript context.

The jsStringFormat() function will take a ColdFusion value and escape the characters within it that would break the encoding of a JavaScript string. This function allows you to safely translate ColdFusion values into JavaScript values:

var jsValue = "#jsStringFormat( cfValue )#";

This works perfectly well if the ColdFusion value is system-generated. Meaning, you have complete control over what is being output. If, on the other hand, the ColdFusion value has been user-provided, then jsStringFormat() is not sufficient. Even though it will escape JavaScript-related characters, it will leave-in HTML characters that can open you up to an XSS (Cross-Sit Scripting) attack.

To demonstrate, I have put together a ColdFusion script that allows a JavaScript alert() to be executed inside of a jsStringFormat() output:

<!---
	This is the malicious code that we want to run when our application
	converts a ColdFusion value into a JavaScript string value. We'll
	do this by executing the code as part of an image-error event.
--->
<cfsavecontent variable="code">

	eval( 'alert( "You just got jammed!" )' );

</cfsavecontent>

<!--- Get the malicious code as a character array. --->
<cfset chars = reMatch( ".", trim( code ) ) />

<!---
	Now, we want to encode the script as a bunch of HTML ASCII
	encodings. We can't render HTML **elements** like this; however,
	we can render HTML **attributes** like this.
--->
<cfloop index="i" from="1" to="#arrayLen( chars )#" step="1">

	<cfset chars[ i ] = ( "&##" & asc( chars[ i ] ) & ";" ) />

</cfloop>

<!--- Collapse the encoded character array down into a string. --->
<cfset encodedCode = arrayToList( chars, "" ) />

<!---
	When using jsStringFormat(), some characters get escaped, but not
	that many. Of particular note, the < and > characters do NOT get
	escaped; as such, we can sneak in a closing Script tag and the
	inject some HTML after it.
--->
<cfset value = "</script><img src=meh onerror=#encodedCode# />" />


<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->


<cfoutput>
	<script type="text/javascript">

		var value = "#jsStringFormat( value )#";

	</script>
</cfoutput>

When I run this code, I get a JavaScript alert modal saying:

You just got jammed!

The way this works may not be entirely obvious. In a web-document, you can define character data as a set of encoded ASCII numbers. For example, the tab character - ASCII character 9 - can be rendered using the following HTML:

&#9;

If you use this approach to render HTML elements, the browser won't interpret the elements, it will simply render the characters. As such, you can't get much malicious mileage out of encoding the "<" and ">" characters. Attributes, on the other hand, are a different beast. If you use this approach to encode attribute values, the browser will interpret them. As such, ASCII-encoded attribute values will work just like plain-text attribute values. And this is where jsStringFormat() falls short.

In this experiment, we are injecting the closing </script> tag into the "user-provided" value. This closes the current JavaScript tag context, no matter where it is found, even if it's inside of a string value. We then follow that with an HTML IMG tag that contains an on-error event handler attribute. This event handler contains the ASCII-encoded, malicious content - our alert() command.

The jsStringFormat() will attempt to replace the single and double quotes, which would normally limit the damage. But that won't matter here, since we've encoded our quotes as ASCII-values. What we need to do is pass the ColdFusion value through both the htmlEditFormat() and the jsStringFormat() functions:

<cfoutput>
	<script type="text/javascript">

		var value = "#jsStringFormat( htmlEditFormat( value ) )#";

	</script>
</cfoutput>

This way, all of the ASCII-encoded HTML constructs will be escaped as well.

When outputting user-provided content, you should pretty much always use htmlEditFormat(), unless you are completely sure that the content has been sanitized. And, if you need to use jsStringFormat(), don't be fooled into thinking that that is enough escaping. In those cases, you probably need to use both escaping methods for total safety.

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

Reader Comments

1 Comments

Wow - I had no idea you could break out of JSStringFormat(). That really has to be considered a CF bug, I think. Does this exploit work for CF10's EncodeForJavaScript()? I bet it doesn't because the OWASP people know what they're doing.
The CF9 equivalent would be

<cfset CreateObject("java", "org.owasp.esapi.ESAPI").encoder().encodeForJavaScript(evilString)>
3 Comments

I would echo what Andy says in using the OWASP ESAPI encoders instead of HTMLEditFormat() or JSStringFormat() (and XMLFormat(), URLDecode(), URLEncodedFormat()) since the ESAPI encoders/decoders are much better tested. Because of this there is a good chance that HTMLEditFormat (and other functions that have ESAPI encoders/decoders equivalents) will be deprecated in future releases of ColdFusion.

Also it is possible to get the ESAPI encoders/decoders in CF 9 (and 8 even) by using the line Andy shows, which is similar to what Pete has documented (http://www.petefreitag.com/item/788.cfm). It does require having the ESAPI library installed in CF, which if CF is patched correctly with APSB11-04 or higher it is available. Other choices, ESAPI for CF (https://github.com/damonmiller/esapi4cf), or CFBackPort (https://github.com/misterdai/cfbackport).

Even with the better encoders that ESAPI offers, one might still have to wrap output in multiple encoders depending upon what the context is. But in this case just a single EncodeForJavascript is sufficient in CF 10 or calling the ESAPI encoder as Andy suggests.

8 Comments

Have to agree with David and since the newer versions of CF and Railo have facades for the standard ESAPI methods you can simply run your source through a find&replace once you get rid of any legacy servers.

15,841 Comments

Groovy stuff. I didn't know that Enterprise Security stuff (ESAPI) was packaged inside of a JAR file. I've only really heard of OWASP in presentations - I haven't done too much R&D with it.

@David, I was just looking at the backports stuff and I see you wrote some "Decode" methods. I'm having trouble finding any info on what they do. Even the ESAPI docs don't really explain what decoding for HTML means?

Does that mean it will _unescape_ things like ampersands and quotes? And, if so, does it do so in some "secure" way? Or is it merely a few utility methods?

36 Comments

@Ben (just to confuse you, another David talking about the same backports project :P)

The DecodeForHTML method simply turns HTML entities back to the characters that they represent. Same goes for the DecodeFromURL which converts URL encoded characters (also supporting double encoded characters) back to the characters they represent.

http://owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/org/owasp/esapi/Encoder.html#decodeForHTML(java.lang.String)

I don't think it worries about doing anything special with security except for making sure everything is decoded (hence the support for double encoded URL characters).

8 Comments

Keep in mind that the library was added in one of the CF9 updates (and was at least somewhat integrated in CF10). If it fails on a CF9 dev machine be sure to check whether it's missing any updates (running the unofficial updater should take care of that).

15,841 Comments

@David B,

Ah, thanks for the insight. As far as the double-encoded URL characters, I assume you're saying that decode-for-html allows URLs to still work after decoding, since they were encoded twice?

@Michael,

Good tip. I'm on CF10 locally, I'll see if I can play around with the lib a bit.

61 Comments

Hmmm, and then around half a year ago the whole OWASP ESAPI project was denoted because of lack of support and updates. Now people around the ESAPI "tribe" are referring developers (CF'ers also) to the OWASP Java Encoder project: https://www.owasp.org/index.php/OWASP_Java_Encoder_Project

What are your thoughts on this Ben, especially since CF8+, CF9+, CF10 and CF11 and RAILO all include the ESAPI support, either directly via tags or via a Java object call. Life isn't easy 4 us developers ;-)

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