Tuesday, September 29, 2009

Solving the XSS problem by signing <SCRIPT> tags

Last week I talked about JavaScript security at Virus Bulletin 2009. One of the security problems with JavaScript (probably the most insidious) is Cross-site Scripting (which is usually shortened to XSS).

The basic defense against XSS is to filter user input, but this has been repeatedly shown to be a nightmare. Just yesterday Reddit got hit by an XSS worm that created comments because of a bug in the implementation of markdown.

I believe the answer is for sites to sign the <SCRIPT> tags that they serve up. If they signed against a key that they control then injected JavaScript could be rejected by the browser because its signature would be missing or incorrect and the entire XSS problem would disappear.

For example, this site includes Google Analytics and here's the JavaScript:

<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost +
"google-analytics.com/ga.js'
type='text/javascript'%3E%3C/script%3E"));
</script>

<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-402747-4");
pageTracker._trackPageview();
} catch(err) {}</script>

Since I chose to include that JavaScript I could also sign it to say that I made that decision. So I could modify it to something like this:

<script type="text/javascript"
sig="068dd60b18b6130420fed77417aa628b">
var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost +
"google-analytics.com/ga.js'
type='text/javascript'%3E%3C/script%3E"));
</script>

<script type="text/javascript"
sig="17aa628b05b602e505b602e505b602e5">
try {
var pageTracker = _gat._getTracker("UA-402747-4");
pageTracker._trackPageview();
} catch(err) {}</script>

The browser could verify that everything between the <SCRIPT> and </SCRIPT> is correctly signed. To do that it would need access to some PK infrastructure. This could be achieved either by piggybacking on top of existing SSL for the site, or by a simple scheme similar to DKIM where a key would be looked up via a DNS query against the site serving the page.

For example, jgc.org could have a special TXT DNS entry for _scriptkey.jgc.org which would contain the key for signature verification.

To make this work correctly with externally sourced scripts it would be important to include the src attribute in the signature. Or alternatively an entirely new tag just used for signatures could be created to sign the HTML between the tags:

<sign sig="068dd60b18b6130420fed77417aa628b">
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost +
"google-analytics.com/ga.js'
type='text/javascript'%3E%3C/script%3E"));
</script>

<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-402747-4");
pageTracker._trackPageview();
} catch(err) {}</script>
</sign>

Either way this would mean that JavaScript blocks could be signed against the site serving the JavaScript completely eliminating XSS attacks.

Note that in the case of externally sourced scripts I am not proposing that their contents be signed, just that the site owner sign the decision to source a script from that URL. This means that an XSS attack isn't possible. Of course if the remotely sourced script itself is compromised there's still a problem, but it's a different problem.

Labels:

If you enjoyed this blog post, you might enjoy my travel book for people interested in science and technology: The Geek Atlas. Signed copies of The Geek Atlas are available.

5 Comments:

Blogger martijn said...

Isn't the new Firefox going to have something similar to SPF, where websites can say from which sources they would expect JS -- the idea, I believe, is to get rid of inline-JS and that JS code should only be loaded from external sources.

I like the idea of signing JS, but I wonder what happens if the webmaster (or one of the webmasters) of a smaller site unknowingly adds a space, removes a line break or simply saves the page under a different encoding.

10:31 AM  
OpenID sean.palmer said...

You'd have to be more careful about how you sign the tags, to that it's not vulnerable to the length-extension attack that was just found in Flickr's API. http://vnhacker.blogspot.com/2009/09/flickrs-api-signature-forgery.html

11:59 AM  
OpenID aaronla said...

I apologize for not having tried this before suggesting it, but couldn't the first script that runs on the page attach a hook that does this validation before other scripts on the page run? And, should the validation fail, remove the script before the browser executes it (say, by removing all attributes and emptying the body).

per martijn's comments about line breaks: any such scheme should consider normalization first. the negative side of normalization is that it increases overhead for signature verification. But you could normalize away empty lines, many form of whitespace, even names of local variables if you so desired -- anything that you can prove won't change the "meaning" of the text. In the case of a normal programming language, anything that won't change the parse tree or how symbol names will resolve (though in any program where eval can directly or indirectly be called, symbol names can affect program semantics, so you might want to think twice about normalizing locals in that case)

8:51 PM  
Blogger Andy said...

What happens if an already signed JavaScript is exploited?

For instance a script is dynamically created on the server side and is signed on the server; however some piece of unfiltered user input was placed in that generated script.

11:27 PM  
Blogger Kamil Szot said...

Better yet sanitize your output not the input. If you are using PHP then use htmlspecialchars() on any data before gluing it to string containing HTM).

Or perhaps there is some way of sneaking script tag into your site if you are converting all < to < before outputting them?

10:50 AM  

Post a Comment

Links to this post:

Create a Link

<< Home