You are on page 1of 10

THINGS THAT MATTER MOST

02 OCT 2014

jQuery and Cross-site Scripting


I ran into an interesting issue yesterday related to the use of
jQuery and a potential XSS (cross-site scripting) vulnerability. It
was an easy mistake to make, and one I unfortunately see (and
occasionally make myself) all too often.

So lets break things down and prevent this bug from coming up
again!

The Attack
A casual reader reported the attack. It seems they could append
some seemingly arbitrary text to a visible URL and cause the page
to trigger an alert. Digging deeper, they also pointed out that a
similar attack could redirect the page to any URL they wanted.

For example, https://eamann.com/#"><img src=M


onerror=alert('test');> would cause the page to post an

alert of test. 1 Similarly, https://eamann.com/#"><img


src=M

onerror=window.location.replace('http://facebook.com')
would force the browser to automatically redirect to Facebook.

On the surface, this seems like a non-issue an attacker could


display an alert or force a page to redirect. 2

The attack itself, though, goes much deeper and has much more
nefarious implications. Essentially, this vulnerability allows an
attacker to execute any arbitrary JavaScript they want, from the
users own context. An alert or redirect to Facebook is trivial
POSTing the current users cookies to a 3rd-party site, though, is
not.

If a site is vulnerable to this attack, I could craft a URL that would


grab the contents of the pages cookies and send them to anyone I
desire. Once I have your cookies, I can spoof your logged-in
session even if you logged in via HTTPS.

Doesnt seem like a non-issue any more, does it?

The Cause
It turns out, a 4-line script on the page was at fault. Some fancy tab

switching going on in an internal page required JavaScript to read


the current URL hash (the component after the # sign) and use it in
an element selector to help the page know which tab to load. Like
many developers, jQuery was used to build a quick proof-ofconcept of the feature and ended up being shipped to
production in finished code.

The prototype looked something like: if ( $( '.class .' +


window.location.hash + ' ul' ) ) { ...

On the surface, this looks just fine. Until, that is, you remember
what jQuery does behind the scenes with selectors. First, jQuery
will attempt to parse the selector as a selector the intended use
case. If the selector fails to validate, jQuery assumes the string
passed in is instead a block of HTML, and subsequently attempts
to parse it.

.class ."><img src=M onerror=alert('test');> ul


will force jQuery to attempt to create an image tag, with a broken
source attribute, and an error handler containing the attackers
desired script package. Since the source is broken (in this case just
an M), the error handler triggers immediately and executes
whatever script the attacker wants.

It could display an alert. It could redirect the page. It could grab

the browsers cookies (including your authenticated session


cookie) and send them to a remote party.

Its a pretty significant bug, and was created merely because


someone failed to recognize the security implications of a quick
proof-of-concept script and shipped it to production as-is.

The Solution
Instead of jQuery, the selector should be parsed using native DOM
methods. document.querySelectorAll() serves the same
purpose here. Unlike jQuery, it throws a syntax exception if you
attempt to pass the broken image tag used in the attack an
exception that is easily caught and discarded.

if ( 0 < document.querySelectorAll('.class .' +


window.location.hash + ' ul' ).length ) { ...
serves the same purpose in this conditional. Wrapped in a
try/catch block, it completely plugs the hole and keeps the sites
naive selector logic from opening a door to an attacker.

The original site hosting the vulnerable code has been patched,
and everyone involved learned a helpful lesson 3 about their code:
never trust any form of user input, even if its coming from an
allegedly trustworthy source. Referencing properties on the global

window object feels safe because its not coming directly from the
user; just always keep in mind where the values in those
properties come from.

The recent discovery of the bash-related Shellshock bug reminds


us just how important it is to always identify the source of
parameters and, even if theyre a trusted source, to be skeptical of
what data is passed in regardless.

Notes:

1. Note: Im using my domain in these examples, but this site


was not affected by this vulnerability.
2. Typically, redirections are a negative thing. But if youre
clicking on a link like this in the first place, you should be
aware that something is up.
3. I never fault anyone for making a mistake like this. After
seeing this bug, I went back through my own code and found
numerous examples that might present similar vulnerabilities.
Coding is a collaborative and iterative process, so the fact that
someone caught the bug and that it was fixed quickly speaks
volumes about the success of the development team involved.
I measure success not in the amount of bug-free code
produced, but in a teams overall ability to learn from and
correct their mistakes.

Share this:

Email

Print

Facebook

Twitter

Google

Filed Under: Technology


Tagged With: javascript, jQuery, XSS

Comments
Dave says
October 3, 2014 at 8:42 am

Just a quick note on this particular issue, it was fixed by jQuery in


version 1.7, released in November 2011. The string must now
start with HTML to be interpreted as HTML, otherwise it is
interpreted as a selector (and in this case it would throw an error).

I agree with the general point tho. Its a bad idea to send raw
unfiltered URL or user input into JavaScript APIs, you dont know
where its been. jQuery cant fix the general case because its a
feature to inject HTML via the jQuery API. Its only a bug when
you let untrusted data inject HTML.

Reply
Eric says
October 3, 2014 at 10:08 am

Ill have to dig a bit more into why exactly jQuery was behaving
this way, but I can confirm that v1.11.1 was running on the
affected site.

Reply
Eric says
October 3, 2014 at 10:33 am

I just did a quick spot test. It appears that jQuery 1.9 does not
exhibit this behavior (indicating that the bug was, in fact, fixed).
But jQuery 1.11.1 does. I verified against 3 different sites in
production, and one completely clean testbed.

It looks like this is a regression.

Reply
Dave says
October 3, 2014 at 10:55 am

This jsbin shows everything working okay:


http://jsbin.com/wupudu/1/edit If you switch that to 1.4.2 for
example the alert will show.

If youre using the jquery-migrate plugin it would put back the hole
because otherwise selectors and HTML interpretation work
differently and break many older pages.

Reply
Eric says
October 3, 2014 at 11:18 am

Nice catch. The common element among these sites was the
jquery-migrate plugin. I wonder if its worth fixing the issue in the
plugin, or if we should just focus instead on not needing the plugin
in the first place (I lean towards the latter option).

Reply
Matt Brundage says
December 17, 2014 at 5:52 pm

Dave, I believe that the selector injection issue was fixed in jQuery
1.9, not 1,7, as the following upgrade guide suggests:
http://jquery.com/upgrade-guide/1.9/

Reply

Samuel "Otto" Wood says


October 5, 2014 at 7:11 am

Im no jQuery or JS expert, but I believe the normal workaround to


prevent this is to use the .find() method instead of dropping the
hash straight into the selector.

So, instead of $( .class . + window.location.hash + ul ) you would


do something more like $( body).find( .class . +
window.location.hash + ul )

Reply

David Cervigni says


January 23, 2015 at 8:35 am

is this the same issue:


http://blog.mindedsecurity.com/2011/07/jquery-is-sink.html
?

Reply
Eric says
January 23, 2015 at 9:36 am

Yep. Though of note, the newer version of jQuery are much safer
in terms of not being a sink (they do some sanitization early to
prevent these kinds of errors). But certain features of jQuery
Migrate reintroduce the issue.

Reply

Leave a Reply

"

&

Search this website

Copyright 2016 Whitespace Pro Theme on Genesis Framework


WordPress Log in

You might also like