SYSSEC: August 2011 Archives

 

August 31, 2011

Today I had occasion to ask Web Security expert Adam Barth about avoiding XSS vulnerabilities. He was kind enough to help me out and (since he's presumably tired of answering this kind of question) also to write up some guidance for the rest of the world. His answer is below:

Folks often ask me how to build web applications without cross-site scripting (XSS) vulnerabilities, but I haven't really been able to find a reference that I'd be happy recommending. There are, of course, many different approaches you can use to build an XSS-free web application. This guide recommends a simplistic approach that works well for "single-document" web applications (like Gmail and Twitter) that use a single HTML document for the lifetime of the application.

Because complexity is the enemy of security, we approach the problem of eliminating XSS by simplifying how we handle untrusted data (by which we mean any information we retrieve from the network or from the DOM):

  1. Untrusted data MUST NOT be transmitted in the same HTTP responses as HTML or JavaScript. In particular, the main HTML document SHOULD be static (and therefore cacheable for a long time).

  2. When transmitted from the server to the client, untrusted data MUST be properly encoded in JSON format and the HTTP response MUST have a Content-Type of application/json.

  3. When introduced into the DOM, untrusted data MUST be introduced using one of the following APIs:
    • Node.textContent
    • document.createTextNode
    • Element.setAttribute (second parameter only)

That's it. If you follow those three rules, you stand a good chance of avoiding XSS. These rules are conservative. You can certainly build a secure web site that violates one or more of these rules. Conversely, these rules don't guarantee success. For example, they don't stop you from doing some dumb things:

// The textContent of a <script> element is active content.
var scriptElement = document.createElement('script');
scriptElement.textContent = userData.firstName;  // XSS!
document.body.appendChild(scriptElement);

// Some attributes (mostly event handlers) are active content:
var imageElement = document.createElement('img');
document.body.appendChild(imageElement);
imageElement.setAttribute('onload', userData.lastName);  // XSS!
imageElement.src = 'http://example.com/logo.png';

However, common sense should help you avoid those situations.