Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with:

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

By Ben Nadel on
Tags: ColdFusion

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.




Reader 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)>

Reply to this Comment

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.

Reply to this Comment

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.

Reply to this Comment

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?

Reply to this Comment

@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).

Reply to this Comment

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).

Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.