Skip to main content
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Mike Kingery and Tim Cunningham
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Mike Kingery Tim Cunningham

Using encodeForJavaScript() To Embed A JSON Payload As Configuration For A Single-Page Application in ColdFusion

By
Published in , Comments (3)

Five years ago, Sr. Security Engineer David Epler pointed out that my use of JsStringFormat() in conjunction with HtmlEditFormat() for encoding untrusted user-provided content could be more effectively done using the OWASP (Open Web Application Security Project) Encoder. At the time, I was only encoding a single field-value, which is how I've been using encodeForJavaScript() since. It's taken me another 5 years to realize that I can use the same technique to safely embed an entire JSON (JavaScript Object Notation) payload in a ColdFusion response for use as the configuration data in a Single-Page Application (SPA).

When loading a Single-Page Application (SPA), some amount of configuration data is often required (ex, public API keys, API end-points, etc). In some cases, this configuration data can be fetched via AJAX (Asynchronous JavaScript and JSON) as part of the bootstrapping process. And, in other cases, this configuration data can be embedded directly in the parent page response where it can be merged into the SPA's configuration.

In the latter approach, when embedding a JSON payload directly in the parent page response, you open yourself up to reflected Cross-Site Script (XSS) attacks. As such, it's critical that care be taken to escape potentially malicious data. In ColdFusion, we can use the built-in encodeForJavaScript() function, in conjunction with the serializeJson() function, to safely embed a JSON payload that contains untrusted user-provided data.

According to the DOM based XSS Prevention Cheat Sheet, embedding untrusted user content in a JavaScript context requires quoting and escaping the content:

GUIDELINE #2 - Always JavaScript encode and delimit untrusted data as quoted strings when entering the application when building templated Javascript

Since the content, in our case, represents a complex object, in order to apply quotes we need to perform server-side serialization followed by client-side deserialization. In ColdFusion, it would look something like this:

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

		var config = JSON.parse( "#encodeForJavaScript( serializeJson( data ) )#" );

	</script>
</cfoutput>

As you can see, we are taking our complex object and running it through serializeJson(). We then take this string value and run it through encodeForJavaScript() before embedding it as a quote-value in the client-side JavaScript content. Then, when the client-side code is executing, we taking that quoted-value and parse it as JSON, which results in a value that can be consumed by our Single-Page Application.

To see this in action, I've tried to embed a payload that has some potentially malicious data:

<cfscript>

	// Sample ColdFusion object data that contains untrusted context that will be
	// injected into a JavaScript context (as the initial configuration data for a
	// single-page application (SPA).
	data = {
		"testingQuotes": [
			"embedded 'single' quotes",
			'embedded "double" quotes',
			"embedded ""escaped"" quotes",
			"embedded "escaped" quotes"
		],
		"scriptTests": [
			"\"";alert('XSS');//",
			"</script><script>alert('XSS');</script>",
			"&lt;/script&gt;&lt;script&gt;alert('XSS');&lt;/script&gt;",
			"&amp;lt;/script&amp;gt;&amp;lt;script&amp;gt;alert('XSS');&amp;lt;/script&amp;gt;",
			"%3C/script%3E%3Cscript%3Ealert('XSS');%3C/script%3E",
			"<SCRIPT a="">"" SRC=""httx://xss.rocks/xss.js""></SCRIPT>"
		]
	};

</cfscript>

<cfcontent type="text/html; charset=utf-8" />
<cfoutput>

	<!doctype html>
	<html lang="en">
	<head>
		<meta charset="utf-8" />
		<script type="text/javascript">

			// UNTRUSTED data injected into a JavaScript context needs to be encoded
			// for JavaScript and DELIMITED as a QUOTED STRING.
			// --
			// Read More: https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet
			var config = JSON.parse( "#encodeForJavaScript( serializeJson( data ) )#" );

			console.log( config );

		</script>
	</head>
	<body>
		<h1>
			Using encodeForJavaScript() To Embed A JSON Payload As Configuration
			For A Single-Page Application in ColdFusion
		</h1>
	</body>
	</html>

</cfoutput>

As you can see, our data payload has some user-provided content that is attempting to escape the JavaScript content in order to execute a reflected XSS attack. However, when we run this code in ColdFusion 10, we get the following output:

Embedding JSON data in a JavaScript context as part of a ColdFusion page response.

As you can see, we safely embedded a JSON payload in the ColdFusion page response.

Now, to be clear, this safely transported untrusted user-provided content from the server to the client; but, it doesn't guarantee that the embedded values are safe to use within your Single-Page Application. Additional care must be taken when rendering user-provided content once it's in the SPA context. For example, if we then take the embedded configuration data and write it to the active document, bad things may happen:

<script type="text/javascript">

	// UNTRUSTED data injected into a JavaScript context needs to be encoded
	// for JavaScript and DELIMITED as a QUOTED STRING.
	// --
	// Read More: https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet
	var config = JSON.parse( "#encodeForJavaScript( serializeJson( data ) )#" );

	for ( var testValue of config.scriptTests ) {

		// CAUTION: Just because the JSON payload made it safely into the page
		// response, it DOES NOT MEAN that the embedded values can be blindly
		// applied to the execution context.
		// --
		// DO NOT DO THIS - DO NOT DO THIS - DO NOT DO THIS - DO NOT DO THIS
		document.writeln( testValue );
		// DO NOT DO THIS - DO NOT DO THIS - DO NOT DO THIS - DO NOT DO THIS

	}

</script>

Running this code does expose a vulnerability:

XSS attack via user-provided content.

Thankfully, all the modern JavaScript frameworks will automatically escape potentially malicious values when you render them as part of the framework's view templates (ex, "{{testValue}}" in Angular). So, using the encodeForJavaScript() can safely get the configuration data from the server into your client-side SPA; then, your JavaScript framework of choice, will help you safely render aspects of that configuration data at runtime.

Anyway, this was mostly a note-to-self. But, hopefully other developers may find it helpful. And, as a caveat, I am not a web security expert by any means. As such, trust but verify that what I'm saying here makes sense.

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

Reader Comments

15,902 Comments

@All,

Ironically enough, the demo code in this post has escaping issues. You will see that two of the lines in the script-testing key look the same. Well, they weren't the same - the second one used &lt; and &gt; style encoding. Which, somewhere along the path of my publishing pipeline, got unescaped. Just goes to show how tricky it is to deal with all this stuff.

449 Comments

So, Ben, would this kind of thing be useful for a website like StackOverflow, where developers are displaying solutions that might have embedded JavaScript?

An answer is submitted to SO's DB and then read to a web page. If SO was written in Coldfusion, you could use the steps you have outlined above to safely display the content?

I must say, this is pretty cool stuff. I have never really had to use this kind of thing before, because I usually only allow MarkDown to be submitted.

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