The security implications of jQuery are not terribly exciting, but they are commonly misunderstood and can have a large impact. In many situations, we see this costing significant time and money for larger organizations; jQuery vulnerabilities are often raised with risk blown out of proportion and are a common source of disagreement between penetration testers and developers.
It’s worth noting that almost all of jQuery security issues surround functions which were so commonly misused that the jQuery team modified behavior to protect developers. Although the changes have been widely interpreted as bug fixes, it can be easily argued that vulnerabilities introduced by jQuery are nothing more than developer error. It is our hope that this article can be used for organization to better assess the risk of common jQuery security issues.
jQuery Basics – The $() Function
$() is identical to, and the most common written form of the jQuery() function, it returns a jQuery object: essentially chunk of content to be written to DOM.
In most use cases, a jQuery function will take a selector, element, or object as a parameter. The selector, denoted by a hash (#), is an identifier for existing html content in the current DOM. In the following example we will use the jQuery html() function to modify an element with the #myDivTag selector:
<script src="https://code.jquery.com/jquery-1.10.2.js"></script> <div id="myDivTag">My old div tag text!</div> <script> $( "#myDivTag" ).html("<b>My new div tag text!</b>"); </script>
Notice the “My old div tag text!” does not show. The jQuery modifies the DOM at runtime to replace the text of our div element:
This capability is not new. In the old world, the above code would be written similar to the example below:
<div id="myDivTag">My div tag text!</div> <script> document.getElementById("myDivTag").innerHTML = "My new div tag text!"; </script>
As you can see, the jQuery function is similar to the getElementById() function. But there is an important difference: jQuery accepts more than just a selector ID, including HTML and script content. Below shows an example of this:
Our ‘Hello world!’ is now bound to browser DOM and visible on screen.
jQuery and Application Pentesting
Application pentesters may already see the attack vectors in the examples above, however there’s a point we cannot stress enough: jQuery() and some element specific sub-functions are execution sinks. Penetration testers must evaluate the sources of data consumed by jQuery functions and determine if and how they are bound to DOM. For many applications this can be an extremely time consuming manual process. Fortunately, there is some help from tools like Burpsuite Professional’s passive scanner which will recognize simple occurrences of certain DOM properties placed within a jQuery function (Example: $(location.hash)). The unfortunate part is that more complex instances DOM XSS in cannot be reliably detected with automated methods.
jQuery’s “XSS Vulnerability”
If you arrived at this page today because a vulnerability titled “jQuery XSS Vulnerability” was raised on a pentest report, you’re not alone. At the time of this writing there are no known direct XSS vulnerabilities in the jQuery framework (not including jQuery plugins). Unfortunately, it is extremely common for the behavior changes to be interpreted as bug fixes.
Let’s take a closer look at the behavior change that has caused so many headaches. Below is an example of the most common vulnerable code:
<html><body> <script src="https://code.jquery.com/jquery-1.6.1.js"></script> <script> $(window.location.hash).appendTo("body"); </script> </body></html>
On the page below we can introduce arbitrary script directly into the browser DOM, this even bypasses Chrome’s XSS Auditor:
This XSS vector is so common that jQuery eventually changed the selector handling characteristics to prevent such attacks. A change was soon put in place to block HTML strings starting with a ‘#’. This requirement defeats XSS vectors from the window.location.hash property as content will always start with a hash.
In the following example using jQuery 1.6.1, an XSS bug is simulated. This passes script beginning with a # character as it would when being consumed from the location.hash property:
The code successfully executes.
In the example below we upgrade jQuery to 1.6.3 and run the same code:
The code no longer runs because the string starts with a # character. Not long after this change an additional behavior change was made to further fine tune the html detection of jQuery. In version 1.9.0b1 it became mandatory for html content to start with a < character. The discussion can be found here.
jQuery’s AJAX $.get() Response Handling Weakness
The jQuery ajax $.get() function (not to be confused with the .get() function) is used to make, as you might have guessed, ajax GET requests. It was found that in versions prior to 1.12.0 would automatically evaluate response content, potentially executing script if it were contained in a response.
Unlike the selector handling issue described above, we believe this behavior to be considered dangerous and potentially unexpected to even savvy developers. The important follow up to that statement is the scenarios in which this issue may manifest are far more unlikely than that of the previous issue.
This behavior may facilitate two potential vulnerabilities in an application.
- Applications making cross domain requests to untrusted domains may inadvertently execute script which may otherwise be perceived as safe content.
- Requests to trusted API endpoints may be leveraged in XSS attacks if script can be injected into data sources.
Like almost all modern software, jQuery aims to be powerful and versatile. There are countless safe and legitimate functions which can contribute to security vulnerabilities when misused. The jQuery issues described here were all a result of software which functioned as designed but was implemented improperly.