Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: David Boon
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: David Boon@evadnoob )

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

By Ben Nadel on

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 &quot;escaped&quot; 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.



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

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
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.