JavaScript Is Harmless

In the preface to my “Mitigating…” talk I offer this Orwellian summation of the state of JavaScript as it relates to browser security:

War is peace.
Freedom is slavery.
Ignorance is strength.
JavaScript is harmless.

I then put forth arguments and examples for securing the client from JavaScript-related mishaps by adopting HTML5. The goal, to quote another paragon of English literature, is to render JavaScript “mostly harmless.”

The first approach to improving the security of JavaScript within your web app is adopting a well-written library like EXT JS, jQuery, or Prototype JS. The second step is adopting positive design patterns like "use strict" (more on that in a bit) and avoiding bad design patterns like string concatenation. Another poor design pattern is relying on intrinsic events — not event handlers in general, just the direct usage of event attributes within the DOM. The point about intrinsic event handlers might seem misguided to developers who love dynamic sites, but it’s important to understand their implications in terms of making code forward-compatible with security features like Content Security Policy.

To illustrate this issue, consider a very simple case of HTML injection. The following example is a pure client-side attack that leverages a URI’s fragment (a.k.a. “DOM-Based XSS”). Injection payloads in the fragment are particularly insidious because that portion of a URI is never sent to the server. It’s invisible to your server-side validation routines, application firewalls, or any other server-side mechanisms.

The following HTML uses the latest version of jQuery. However, the fundamental error is not within the library; it’s the developer’s use of string concatenation to build the selector and context arguments to the $() function. This kind of mistake differs from SQL injection only in the underlying technology. (Never the less, this vulnerability resembles a jQuery bug.)

<!DOCTYPE html>
<html>
<head>
  <title>DOM XSS</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22jquery-1.8.2.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
  <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%3E%0A%20%20%24(document).ready(function()%20%7B%0A%20%20%20%20var%20x%20%3D%20(window.location.hash.match(%2F%5E%23(%5B%5E%5C%2F%5D.%2B)%24%2F)%20%7C%7C%20%5B%5D)%5B1%5D%3B%0A%20%20%20%20var%20w%20%3D%20%24('a%5Bname%3D%22'%20%2B%20x%20%2B%20'%22%5D%2C%20%5Bid%3D%22'%20%2B%20x%20%2B%20'%22%5D')%3B%0A%20%20%20%7D)%3B%0A%20%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
</head>
<body>

<div id="main">
foo
</div>

</body>
</html>

You would exploit the vulnerability on this page with a URL fragment like the following payload:

https://web.site/page#<img/src=””onerror=alert(9)&gt;

The link’s fragment is assigned to variable x, which is passed into the $() function’s argument list as part of a string. Due to jQuery’s design, the function creates a new element out of the <img> tag. This produces an alert when the new element’s onerror event triggers.

One solution is to apply character encoding or filter the fragment before inserting it into the string. After all, the app should know that an image element isn’t going to be a valid selector. Unfortunately, trying to predict every possible bad input tends to be a losing battle. Another solution takes advantage of Content Security Policy’s restriction on JavaScript execution.

The first step to deploying CSP is restricting the origins from which content is loaded. In our example the JavaScript is assumed to be hosted on the same origin as the HTML. Hence, we can use the ‘self’ source expression to prevent any src attribute from loading non-origin content:

Content-Security-Policy: default-src 'self'

Browsers do not yet officially support CSP headers. (After all, it’s still in draft.) Those with experimental support use a custom header with an X- prefix to trigger this mode. You can generate headers with the following PHP code. (You can try setting the header via an http-equiv <meta> tag if you want a pure HTML example, but the browser might not interpret it.) Choose the appropriate header for your browser. All of the subsequent HTML examples assumes one of the following headers is present to restrict src attributes to the page’s Origin (i.e. 'self').

<?php header("X-Content-Security-Policy: default-src 'self'");   // Mozilla header("X-WebKit-CSP: default-src 'self'");   // WebKit (e.g. Safari) ?>

We have an immediate problem if we try to load the original HTML from a server that sets this CSP header: the browser will not execute the $(document).ready() function because it is an inline script. CSP has an 'unsafe-inline' directive that we could apply; but there’s no point in using it because our HTML injection payload has inline code in its onerror event — we’d have created a header that does nothing. If we make our code work that way, then we make the exploit work.

The solution is to move the jQuery function to a JavaScript file. Update the HTML to call main.js instead of the inline script:

<!DOCTYPE html>
<html>
<head>
  <title>DOM XSS</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22jquery-1.8.2.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
  <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22main.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
</head>
<body>

<div id="main">
foo
</div>

</body>
</html>

The main.js file should now contain the following code. Note that it’s still vulnerable to the injection attack. We haven’t changed the string concatenation. Our intent is to see how we can reinforce the browser against undetected vulnerabilities. Finding and fixing the injection points is a task for another day.

$(document).ready(function() {
  var x = (window.location.hash.match(/^#([^\/].+)$/) || [])[1];
  var w = $('a[name="' + x + '"], [id="' + x + '"]');
});

Try reloading the new page with the CSP header. No amount of trickery will force the browser into executing the JavaScript from your link:

https://web.site/page#<img/src=””onerror=alert(9)&gt;

The page remains vulnerable to the HTML injection attack, but is no longer exploitable because the payload’s onerror event is blocked as an unsafe-line script. This is the power of HTML5 — protecting your users from coding mistakes. Content Security Policy establishes a more granular control over content within the Same Origin Policy.

Getting to this point isn’t always so easy. This example merely required moving a script block into a .js file. However, more complex apps will have trouble, especially if they have intrinsic event handlers defined throughout the HTML.

Keep the following points in mind if you’re interested in migrating towards CSP headers and a more secure JavaScript experience for your users.

(1) Replace intrinsic events with their equivalent handlers from a JavaScript library like jQuery.

In other words, any time you’ve explicitly written something like onclick="..." you’ve created an intrinsic event handler. Replace it by using jQuery’s bind(), click(), or on() API on the element.

To demonstrate this, add these lines to the main.js file created earlier. Then load the HTML with its CSP header. You’ll see that it’s incorrect to use the attr() function. The other two functions give you identical “onclick” handling without impeding CSP:

$(document).ready(function() {
  var x = (window.location.hash.match(/^#([^\/].+)$/) || [])[1];
  var w = $('a[name="' + x + '"], [id="' + x + '"]');
// triggers unsafe-inline in CSP
// var click = $('#main').attr('onclick', 'alert(9)');

// doesn't trigger unsafe-inline
// var click = $('#main').click(function(e) { alert(9) });

// doesn't trigger unsafe-inline
// var click = $('#main').bind("click", function(e) { alert(9) });
});

Now you have two anti-patterns to excise from your code: intrinsic events in the HTML and any use of jQuery’s attr() API that creates an event handler. Fixing these problems allows you to apply CSP headers that aren’t weakened by an 'unsafe-inline' directive.

(2) Enable strict mode within each JavaScript code unit.

The preferable way to do this is within the scope of an anonymous function of each JavaScript file (i.e. the “code unit”). Doing so makes it easier to combine and minify files without having the strictness for one well-written piece of code to cause poorly written code to fail. (Of course, the poorly written code should be fixed.) This is as easy as:

(function(){
  "use strict";
  ...
})();

Strict mode helps you avoid the JavaScript problem of implicit globals. No longer will your script be able to casually place a variable into the global scope without explicitly defining it with var. This avoids errors and potential security problems due to shadowed variables. Strict mode also makes errors and warnings more prominent. Reducing such warnings leads to clearer code, which tends to lead to more secure code.

(3) Determine a deployment strategy for CSP headers

Replacing intrinsic events is a nice recommendation, but it’s infeasible to do so quickly for large web apps. You can start piecemeal, such as reviewing the login page or pages that take credit cards.

A smart design choice of CSP is that the headers provide a reporting mode, much like the monitor vs. enforce modes of a web app firewall. We’ll have to cover this in another article.

Another option is playing with different src directives. This example relied on default-src, which applies to all elements. If you know script sources are going to be problematic, start with img-src or frame-src directives.

(4) Encourage visitors to upgrade browsers

Obviously, CSP only helps browsers that understand and implement the directive. It protects a user from vulnerabilities in your code.

And finally, come back here for more details and updates as the CSP headers trundle closer to an official standard.

(Updated January 2013 to apply shortcode format to the HTML examples in order to improve their appearance.)