Is it possible to exploit XSS in the following code?
It seems to me that
window.location.hash always starts with a hash
#, and modern jQuery always interpretes this as an ID selector. Is this correct?
Information Security Stack Exchange is a question and answer site for information security professionals. It only takes a minute to sign up.Sign up to join this community
Yes, it's possible to do something malicious with
jQuery(window.location.hash). See this archived blog post which was mentioned in a comment to an answer in another SE question. A CSS timing attack can be done, as was described in the blog post:
If we execute the selector
*:has(:has(:has(*)) :has(*) :has(*)) input[name=authenticity_token][value^='x'], it will take a long time only if the authenticity_token starts with 'x'.
A malicious site can open a target site in an iframe and see whether the selector takes a long time to match. If
input[name=authenticity_token][value^='x'] is not matched, the selector will be short circuited and jQuery not check the more costly
As mentioned on the blog post, if this is running in a browser that runs both sites in the same process, the delay in a site that calls the victim site can be easily measured. (It seems to me that there may even be a timing attack possible even with separate processes, since it can slow the computer down generally even if the processes are scheduled separately. In general, it's likely asking for problems for any page to be written such that it may do a lot of processing based on whether something in the URL matches a secret string.)
As Goose’s answer mentions, it appears that there were some earlier versions of jQuery which were vulnerable to this.
Since the jQuery function can convert strings into DOM elements like this...
If your code had:
And an attacker caused you to access your page with a specially crafted URL that ended with a hash fragment like:
Then jQuery would interpret the hash as HTML, create the necessary DOM elements and in the process actually execute any JS handlers attached to them.
Future versions of jQuery appear to have mitigated this by prioritizing ID selectors over HTML, so anything starting with a hash is treated as a CSS selector, not as HTML.
Patches 9521 and 11290 several years ago attempt to mitigate this issue, although does not claim to completely remove the possibility for XSS. I've been running in jQuery 2.x some of the exploits they claim still exist, but they don't seem to be working, so I can't definitely confirm and deny if the code you provided is enough to exploit.
For what it's worth, it's also possible for this exploit to happen if you do something with the jQuery object. Let's say you allowed customers to submit HTML, and a customer submitted the following:
And for some crazy reason you had the following code on the page that displays this HTML:
A URL hash of
#xss would cause an alert to trigger.
So I can confirm this is possible, although it'd be very difficult to exploit this particular way without writing the code specifically to be vulnerable.
I'd recommend doing a validation check on the value of
window.location.hash or stripping out special characters before passing it in to
jQuery(), and as always be thoughtful of what you do with the jQuery object.
Why take a guess? Just sanitize it with \w first, it's so easy. It seems pretty terrifying if they have complete control over that jQuery line even if it must begin with a hash. In the words of the Goose's linked 9521,
I guess the question is whether this is such a common bone-headed move that we need to prioritize an id selector over HTML for this case. I could be convinced.
However, I am not sure how we can generally protect against this. The hash symbol is just text, and the string is valid HTML. Users should not be passing untrusted input to $() that could contain script.
So, according to at least one jQuery developer, this is not even considered a vulnerability at all - And he wants to leave it to the programmer to not do this. Even if you don't add it now, a later maintainer will probably notice this and add
window.location.hash.replace(/[^\w]/g, ""). (Or
[^\w-] if you use hyphens). Nothing is really saved by avoiding it. Sure they can mitigate it, but I wouldn't have this attitude being the leading force behind your web security.
My go-to for user input is a special JS file at the top that handles all sanitizing inputs, and stores things like window.location.hash in a "UserHash" variable that's already sanitized, with documented sanitization so others know what they can assume. I've found that not doing this will inevitably lead to vulnerabilities later on. Someone will figure "Oh, it's safe here so it'll be safe over here", leaving things on the very edge of semisafety falling into an actual vulnerability.