Script Keys

This is the second of my three ideas for the client-side mitigation of Cross-Site Scripting (XSS). It’s based on the same principle – that of the site owner specifying which scripts should be permitted and which not – but takes a slightly different approach.

Basically, the idea is that an HTTP header, Script-Key, defines a random string or “key” and only script labelled in some way with that string is allowed to execute. As the string will be different for every page load, injected script would not be correctly labelled and therefore would not be permitted to run.

The trick is trying to label the script in a way which allows the resulting markup to still validate. Having analysed the legal attributes of the <script> tag, here’s my current idea:

Content-Type: text/html
Script-Key: D3FC219A
<script type="text/javascript;key=D3FC219A">

Of course, that could also be a <script type=”…” src=”…”>.

That’s all very well, but how do you label e.g. script in event handler attributes like “onchange”? One idea would be just to say “you don’t”, and encourage use of proper DOM methods to add event handlers. Another would be to do something hacky like saying as long as the text of the script contained they key, it would be OK. So you could do:

<hr onclick="var foo = 'D3FC219A'; ..." />

Or, you could require the handler to set a navigator.scriptkey property to the correct key before you could do anything apart from assign variables. But that’s rather hacky too, and perhaps harder to implement, as you need to sandbox already-running script. (Either of these ideas would obsolete the content-type-parameter method.) Better suggestions welcomed. (We could define a new attribute, perhaps through the WHAT-WG. That might be cleaner, but it wouldn’t solve the event handler problem unless the attribute was made applicable to all tags.)

So how does this approach compare with Content-Restrictions? It’s a single layer idea – i.e. if it’s breached, and the attacker manages to e.g. inject script inside a block which is labelled as safe, then it’s game over. But it does reduce the attack surface. Content-Restrictions, on the other hand, is layered – if one restriction doesn’t prevent an attack, another one might. As a simple example, for “script=head,cookies=none”, even if you managed to inject script into the head, you couldn’t read cookies.

23 thoughts on “Script Keys

  1. I like that idea very much. It would pose quite some difficulty for someone to inject something into the HTTP header as well. Nice and elegant.

  2. Nice idea, perhaps even better than my idea (see comment at Gerv’s last post) of having script-free areas, as it doesn’t depend on proper nesting.

    For event handlers, what about using comments? E.g. <hr onclick=”someFunctionCall();//D3FC219A” /> Fully backward-compatible, and validates.

  3. minghong: because it would break too much of the web. Such a thing would need to be defined on a per-site basis, using something like, er, Content-Restrictions :-)

    jens.b: Good idea. Comments are even simpler, although they’d need to be /* */ as you’d need to close them for any script to be executed at all!

  4. Would it not be possible for the injected script to read the document, discover the key, and use it itself? It may not be able to read the HTTP headers, but it can read the DOM! Certainly if you used the navigator.scriptkey method it would be fairly easy for injected script to discover and use the key.

    If I’m right about this, this method wouldn’t give much protection for long.

  5. @Luke: Scripts won’t run w/o a valid key. So using a script to discover a key is moot, it wouldn’t run to begin with.

  6. How do I as a webdeveloper know which key is used in my HTTP header, so that I can label my scripts accordingly ?
    I would need access to the HTTP header when the page is generated, so doesn’t this exclude pure HTML pages ?

    Also what if the page is assembled from multiple servers. E.g. a webserver that fecthes some of the content from an “application server”. If what is fetched is a chunk of html including scripts how does the app server know which scriptKey to mark with ?

    Also, how does this work with cached javascrip files ?

  7. @VI – oh yeah, I was thinking of client side manipulation of scripts rather than server side injection (e.g. a script in a different tab in the browser), but that should be stopped by other mechanisms. Sorry for the confusion.

  8. Basically, as henrik says, this relies on the same system being used to set the HTTP header and generate the document and is only effective when the page is regenerated on every request. Even for a low traffic site that’s a big burden. For the kind of high volume site where caching is really important, this limitation seems to make the idea unworkable.

  9. jgraham: there’s the solution, then – the script key can be a user and session-specific value. So it’s always the same for a given user and session. So caching works, and different parts of the web app system can independently calculate the key if need be, so there’s less need for coordination between the parts.

    As there’s no restrictions on the format of the key, this should work fine.

  10. Just what I wanted to say. Webapps are perfectly capable of handling session. Inserting a random value in the header an a few times in the document isn’t that costly.

  11. Another method would be to have a “script security section” (SSS) in the document which contains a checksum/CRC/MD5/whatever for each of the scripts which are allowed to be run. The block could be specified as the first block in the , and would contain an array or list of the CRC for each individual script that could be run on the document. So, if there were 5 blocks and 3 scripted click events, there would be 8 entries in the SSS.

    Optionally, to take it fruther, the SSS could enumerate the allowed actions of the individual scripts. It could also list the elements which could use those scripts.

  12. the script key can be a user and session-specific value

    So presumably that limits the system to applications that have a definite concept of “user” and “session”. I’m not familar enough with web app development to know if that’s a big problem or not. Even within that framework, I would have thought that there was a use for serving static pages e.g. a webstore with static product listing pages and dynamically generated checkout pages.

  13. So presumably that limits the system to applications that have a definite concept of “user” and “session”.

    Not at all. I said it _can_ be a user and session-specific value, in response to issues about caching. Of course, if your application has no concept of user and session, then each request is unconnected to any other and so using a unique key each time isn’t a problem.

    Remember, the key system only needs to be used on pages which have script in them – and then, only really on pages which contain some sort of user-specified content or whose content depends on parameters which are user-controlled. Static pages are unlikely to be vulnerable to XSS attacks.

  14. Inventing browser site restrictions for cross-site scripting is a really interesting idea. Adding header informations to tell the browser to disable javascript or only allow them in special places is a simple and powerfull approach i really like.

    I wouldn’t go into too many details like disabling specific features (e.g. which type of objects can be manipulated in DOM) to avoid confusing people. XSS usually gets produced by two types of programmers: those who know the problem and don’t care, and those who lack skills to properly filter input or are just not aware of the problem.

    The people who don’t care won’t use browser side restrictions. And if the restriction system gets too complicated it won’t be used by unskilled developers.

    I am in favor of a few, easy to explain header commands like “disable javascript”, “disable inline scripts”, “disable javascript urls”, “disable cookie access”, etc.

    SQL injection and overwriting global variables was a huge problem in early web application (especially in PHP). magic_quotes_gpc and register_globals solved most of those problems with a simple and powerfull approach that is default today. It’s still possible to inject something if the programmers is too sloppy, but it solves the worst bugs. Let’s learn from this successful security feature.

    A simple approach make it also more likely that other other browsers and html editors will add the feature.

    A idea that just comes to my mind is that header tags would it also make really easy to add those tags using a proxy to all or specific sites. An interesting option for corporate users.


    P.S. 175 XSS flaws i found last december:

  15. @Gerv, about caching: if it is user or session specific then you would need to redownload it everytime the session times out, or you log into/outof the site.

    Also think load balancing/fail over scenarios, a backup node (a.k.a. server “B”) needs to be able to calculate the key so the site doesn’t break if the user suddenly hits another node.

    If the key is session based, the browser would also need to know to throw away a cached file if the session changes. That can be tricky to detect, because the servers in the load balancing are likely to have the same external ip.

    I could also easily see the scenario where static content (nearly pure html ) is served from “simple” webservers and dynamic content from “real” app servers. Both static and dynamic content could share the same cached scripts e.g. dynamic menus etc.

    Again all servers would need to be able to calculate the right key, and how to insert the key into the html from the static servers ?

    Lots of scenarios to look into..

    I like the idea, but I question the practical approach.

  16. That’s all very well, but how do you label e.g. script in event handler attributes like “onchange”?

    Perhaps I missed the point, but wouldn’t the following work?

    <meta http-equiv="Content-Script-Type"
  17. Racer: that’s really hard to do, as many scripts contain dynamically-generated data structures. I also don’t see what it buys you over the alternative approaches. If you want to use signed script, there are already mechanisms for doing that. It might be good to have a way of saying “run only signed script on this page”, though.

    mikx: thanks for your feedback. I’m not too worried about the complexity; people don’t have to use the more complex parts, they can stick with “script=none” if they like. (I assume you are talking about the other proposal here.)

    A idea that just comes to my mind is that header tags would it also make really easy to add those tags using a proxy to all or specific sites.

    Sounds like a good way to break large chunks of the web :-). This stuff is carefully tailored per-site, and needs to be added by the site, not some intermediary.

    Henrik: imagine the key is your jsessionid. Then you can see that almost all the issues you’ve just raised apply to web applications even without Script Keys.

    zcorpan: that sounds like a good way to enable all script on a page, which makes the proposal rather self-defeating!

  18. @Gerv, Even with the key being your jsession or asp-session, I don’t think it will solve the problems.

    In the case with one server doing static pages and another server doing dynamic pages, one might turn off sessions on the static pages alltogether.

    It also depends on how much validation of the cached js files is intended. If the “only” validation is the key in the script tag then things are a much easier then I might forst have assumed. However content aggregation and server farms still need to be looked into.

    e.g. A site using frames/iframes or similar where part of the website is from and another part is from Today those part can js-communicate by setting their domains to This scenario must still work.

    Another thing to consider is content managment systems, here the CMS system might intentionally insert stuff into the page when in “preview” mode. e.g. if they are using 1 window for the CMS controls and 1 for the preview content and they need to communicate, or the preview content could be wrapped in frames, having the CMS system in the “outer” frames.

    Im not saying it wont work, just that things like this should be considered.

  19. Henrik: you don’t have to serve Script-Key headers on the static pages; but then, you don’t get XSS protection on them.

    All of the issues you raise are not insuperable; but if they can’t be sorted completely with a certain CMS, then that CMS can’t take full advantage of this proposal. I’d be interested to hear of any modifications you have to the idea to make it more widely usable without decreasing the security but, even if you don’t have any, I don’t think we should drop it just because it can’t be used by everyone.

  20. Why is it that I can not get past the fact that this person really believes that a benevolent deity gives a flying shit about hacking? Already I am convinced his thinking is suspect. If this person were to keep his silly delusions to himself, then perhaps I would have ventured to read the rest of the post. And in any-case browser side scripting languages should be removed from the browser, that is how you will solve XSS – and that is not going to happen. Companies want full control over the browser os that they can pop over/under… and crash your machine with an out of memory error. The web bites ass, bring back gopher.

  21. WTF: God cares about everything we do. As for the title of this blog, it is inspired by “Whatever you do, do it all for the glory of God.” (1 Corinthians 10:31). I do what I do for his glory.