Safely Binding Unescaped Content in Web Applications

"Snakecharmers," a chromolithograph by Alfred Brehm, c.1883
“Snakecharmers,” a chromolithograph by Alfred Brehm, c.1883

Binding unescaped HTML or CSS content on a web page is a scary proposition for most web developers. The idea conjures up black-hat hackers attacking your company’s infrastructure and high-visibility hacks. In fact, cross-site scripting vulnerabilities may exist on as many as 70% of all web sites.

In this post, I’ll explain what cross-site scripting vulnerability is, when it’s OK to silence warnings, and how to do so in two popular JavaScript frameworks: Angular and Ember.js.

What is an XSS Attack?

Cross-site scripting (XSS) attacks boil down to loading malicious, third-party code when initially trying to visit a trusted site. Web application security relies heavily on the concept of same-origin policy. Put formally:

A web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin.

XSS attacks occur when, in the process of loading the trusted website, a malicious third party’s code is inserted and loaded in the client’s browser. Cross-site scripting attacks can be especially malicious when they masquerade as the legitimate site the user was trying to access–for instance, a link leading to a site that looks exactly like a trusted bank login webpage.

One of the most common classes of XSS attacks occurs when a vulnerable page contains untrusted, user-created content–for example, a web search page that displays the user-created search phrase. In this scenario, the user can enter a search term that itself contains malicious code. Imagine a user searching for a term like:

<script>// <![CDATA[
alert("hello!")
// ]]></script>

On a compromised web page, the HTML for the search results page will have a script tag including JavaScript that unexpectedly executes code. In this case, an alert would pop up on the screen. It’s easy to imagine more malicious attacks, such as redirecting to a fake login page.

A more thorough explanation of XSS attacks can be found at the Open Web Application Security Project.

Unexpected Warnings in JavaScript Frameworks

Given the above scenario, JavaScript frameworks (such as Ember.js or Angular) will recognize and flag unsafe unescaped data bindings to bare HTML or CSS styles. In Ember.js, this will manifest with the following warning:

WARNING: Binding style attributes may introduce cross-site scripting vulnerabilities; please ensure that values being bound are properly escaped. For more information, including how to disable this warning, see http://emberjs.com/deprecations/v1.x/#toc_binding-style-attributes.

In Angular, you will see something like this:

Attempting to use an unsafe value in a safe context.

Both errors/warnings let the developer know that the value being bound is not safe. In other words, the string representing the data being bound has not been escaped by the framework to strip out CSS or HTML control phrases that could result in an XSS attack.

Bypassing Warnings Safely

Recently, I have been involved in development of an Ember.js project. While working on a feature that included DOM elements with dynamic width, code was written containing the following template:

.progress-bar style={{progressBarWidthStyle}}

The Component contained a computed property, like the following:

progressBarWidthStyle: Ember.computed 'width', -&gt;
  "width: #{@get('width')}%;"

The computed property will return a string representing the inline style, such as "width: 54.354%." Although this is a benign use of a bound-style attribute (no malicious code here!), the Ember framework cannot know for sure, so it generates a warning. To silence the warning, we must use Ember.String.htmlSafe:

progressBarWidthStyle: Ember.computed 'width', -&gt;
  Ember.String.htmlSafe("width: #{@get('width')}%;")

Angular’s Strict Contextual Escaping service provides similar functionality with the function trustAsHtml.

When Is It Safe?

So, when is it actually OK to disable warnings and flag unescaped content as trusted? Simply put, if the data being bound is not user-supplied. Binding data that you compute in your Ember or Angular controllers, such as the width example above, is perfectly fine.

Untrusted data, such as custom user-supplied CSS styles, must be carefully escaped or tested against a whitelist of valid business rules, length, etc. For more complex situations, you can check content sanitation libraries, such as AntiSamy and the Java HTML Sanitizer Project. While it’s impossible to anticipate every attack vector under the sun, XSS attacks are low-hanging fruit that we should all be watching for and preventing.