Thursday, July 28, 2011

jQuery is a Sink!

What's a Sink in Application Security?
A sink can be described as a function or method that is potentially dangerous when it's (unexpectedly) called or if one of its arguments, coming from an untrusted input, is not correctly escaped according to the layer the function is going to communicate to.

The jQuery Sink
Suppose we have the following code:
var aVar=location.hash;
jQuery(aVar);

By looking at the DOMinator output, you'll see something like the following:

That means the jQuery, or its alias '$', method is trying to understand if hash contains some tags. In particular part of the JavaScript stack trace will be similar to the following:

4 14:44:40.306 :[ http://host/jQueryTest.html#aaaaa ]
Target: [ exec(^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)) CallCount: 1 ]
Data:
+ #aaaaaa
http://host/jquery-1.4.3
,Line:19
,1: function (selector, context) {
2: var match, elem, ret, doc;
3: if (!selector) {
4: return this;
5: }
6: if (selector.nodeType) {
7: this.context = this[0] = selector;
8: this.length = 1;
9: return this;
10: }
11: if (selector === "body" && !context && document.body) {
12: this.context = document;
13: this[0] = document.body;
14: this.selector = "body";
15: this.length = 1;
16: return this;
17: }
18: if (typeof selector === "string") {
19: match = quickExpr.exec(selector);
20: if (match && (match[1] || !context)) {
21: if (match[1])
...


So, by trying to put #<img/src/onerror=alert(1)> in the hash part, we'll see the following alert on DOMinator:
12 14:44:40.364 :[ http://host/jQueryTest.html ]
Target: [ DIV.innerHTML CallCount: 1 ]
Data:
+ <img src="" onerror="alert(1)" />
+ Stack Trace
http://host/jquery-1.4.3
,Line:20
,1: function (elems, context, fragment, scripts) {
2: context = context || document;
3: if (typeof context.createElement === "undefined") {
4: context = context.ownerDocument ||
5: context[0] && context[0].ownerDocument || document;
6: }
7: var ret = [];
8: for (var i = 0, elem; (elem = elems[i]) != null; i++) {
9: if (typeof elem === "number") {
10: elem += "";
11: }
12: if (!elem) {
13: continue;
14: }
15: if (typeof elem === "string" && !rhtml.test(elem)) {
16: elem = context.createTextNode(elem);
17: } else if (typeof elem === "string") {
18: elem = elem.replace(rxhtmlTag, "<$1>");
19: var tag = (rtagName.exec(elem) || ["", ""])[1].toLowerCase(), wrap = wrapMap[tag] || wrapMap._default, depth = wrap[0], div = context.createElement("div");
20: div.innerHTML = wrap[1] + elem + wrap[2];
21: while (depth--) {
22: div = div.lastChild;
23: }
.....

Which means that if the arguments of jQuery contains some kind of tag it'll be rendered using innerHTML, resulting in a potential DOM Based Xss.

Countermeasures
If you have to use some untrusted input, Data Validation and Output encoding should be performed before using it as a jQuery argument.

8 comments :

  1. Agreed, see http://bugs.jquery.com/ticket/9521 which will fix the case you mentioned, but even that is no replacement for the app thoroughly validating user input it sends to jQuery. --dmethvin

    ReplyDelete
  2. I'm glad jQuery will be fixed..
    and I like the fix as well,

    if( $.safetyMode == true ) { // or more cute name
    // code for disable create html with $ function
    }

    $("<img>"); // throw error
    $.html("<img>"); // create Element

    this way user can set a "safetyMode" flag to true in order to disable the html insertion feature.

    ReplyDelete
  3. adding some documentation about it would be great as well, of course.

    ReplyDelete
  4. Just for sake of completeness, this jQuery behavior is a well known documented feature:
    http://api.jquery.com/jQuery/#jQuery2

    That's why I wrote such standard secure coding best practice in the countermeasures section.

    ReplyDelete
  5. Better fix: don't pass the hash to the jQuery function. It's not jQuery's job to sanitize all input.

    ReplyDelete
  6. http://blog.jquery.com/2011/09/01/jquery-1-6-3-released/

    ...Fix an XSS attack vector: User ma.la reported a common pattern that many sites are using to select elements using location.hash that allows someone to inject script into the page. This practice seemed widespread enough that we decided to modify the selector recognition to prevent script injection for the most common case. Any string passed to $() cannot contain HTML tags (and thus no script) if it has a “#” character preceding them. See the ticket linked above for more information and a test case.

    ReplyDelete
  7. @Stefano... you see how well that worked for PHP, right?

    --Robert

    ReplyDelete
  8. http://blog.jquery.com/2011/09/01/jquery-1-6-3-released/

    ReplyDelete