Thursday, November 8, 2012

DOMinatorPro Fuzzer finds a DOM XSS on Google.com

Introduction a.k.a. tl;dr


A quite simple DOM Based XSS was found on https://www.google.com/ context using DOMinatorPro.
What I think it's interesting here, is to show how DOMinatorPro works in this real world case.

In order to give a more detailed description, i recorded a video (HD) to show how DOMinatorPro can help in finding those particular kind of issues.




Some Details

DOMinatorPro is a runtime JavaScript DOM XSS analyzer, which means it can check for complex filters and help in finding debug and/or unreferenced parameters during JavaScript execution.

In order to do that, DOMinatorPro exposes a fuzzer button which fuzzes the URL query string and html input elements that have keyboard events attached to them as shown in the youtube video.
By using that feature I found that the code in
http://www.googleadservices.com/pagead/landing.js
uses unvalidated input to build the argument for two document.write call.

This javascript is used, among others, by:
http://www.google.com/toolbar/ie/index.html
and
https://www.google.com/toolbar/ie/index.html
which means that one more time a (almost) 3rd party script introduces a flaw in the context of an unaware domain.

Source Analysis

From http://www.googleadservices.com/pagead/landing.js (which has now been removed) the following lines do not escape user input:

Line 53:
    if (w.google_conversion_ad) {
      url = url + "&gad=" + w.google_conversion_ad;
    }
    if (w.google_conversion_key) {
      url = url + "&gkw=" + w.google_conversion_key;
    }
    if (w.google_conversion_mtc) {
      url = url + "&gmtc=" + w.google_conversion_mtc;
    }
    if (w.google_conversion_raw) {
      url = url + "&graw=" + w.google_conversion_raw;
    }
    if (w.google_conversion_domain) {
      url = url + "&dom=" + w.google_conversion_domain;
    }
   
And those values are taken by using :
function google_get_param(url, param) {
var i;
var val;
if ((i = url.indexOf("?" + param + "=")) > -1 ||
(i = url.indexOf("?" + param.toUpperCase() + "=")) > -1 ||
(i = url.indexOf("&" + param + "=")) > -1 ||
(i = url.indexOf("&" + param.toUpperCase() + "=")) > -1) {
val = url.substring(i + param.length + 2, url.length);
if ((i = val.indexOf("&")) > -1) {
val = val.substring(0, i);
}
}
return val;
}
...
google_conversion_ad = google_get_param(url, "gad");
if (window.google_conversion_ad) {
(google_conversion_key = google_get_param(url, "gkw")) ||
(google_conversion_key = google_get_param(url, "ovkey"));
google_conversion_mtc = google_get_param(url, "ovmtc");
google_conversion_raw = google_get_param(url, "ovraw");
}
}

After the previous code, the url variable is used on line 91:

document.write('' name="google_conversion_frame"' +
' width="' + width + '"' +
' height="' + height + '"' +
' src="' + url + '"' +
' frameborder="0"' +
' marginwidth="0"' +
' marginheight="0"' +
' vspace="0"' +
' hspace="0"' +
' allowtransparency="true"' +
' scrolling="no">');

and line 103:

document.write(''src="' + url + '&ifr' + 'ame=0"' +
' />');
so the offending url (which can be found with DOMinatorPro fuzzer), is:

http://www.google.com/toolbar/ie/index.html?&gad=bbbb&gkw=yyyy&ovkey=10101010&ovmtc=14141414&ovraw=18181818&

and on any of those parameters can be added an attack payload like:

AAAA"onload="alert(document.domain)"

Remediation

I suggested that the use of encodeURIComponent on user data
    if (w.google_conversion_ad) {
      url = url + "&gad=" + encodeURIComponent(w.google_conversion_ad);
    }
    if (w.google_conversion_key) {
      url = url + "&gkw=" +
encodeURIComponent(w.google_conversion_key);
    }
    if (w.google_conversion_mtc) {
      url = url + "&gmtc=" +
encodeURIComponent(w.google_conversion_mtc);
    }
    if (w.google_conversion_raw) {
      url = url + "&graw=" +
encodeURIComponent(w.google_conversion_raw);
    }
    if (w.google_conversion_domain) {
      url = url + "&dom=" +
encodeURIComponent(w.google_conversion_domain);
    }

would have solved the problem.

Anyway, Google fixed that by removing that script for good, which is a solution as well! :)

Conclusions 

As already said in my previous post, I still see DOM based XSS all around with little awareness and difficulties by all actors in SDLC in identifying them.
DOMinatorPro can really help in finding DOM based XSS and it does that by helping testers, developers or QA users by trying to give the information they need by adapting to the knowledge they have.

Give DOMinatorPro a Try

Do you still trust all those 3rd party JavaScripts embedded in your pages ?
Just browse your site with DOMinatorPro or fuzz your pages and see what happens. :)
In case you need help, check out our professional services or just ask for a contact.

Monday, November 5, 2012

DOM XSS on Google Plus One Button

Introduction

DOMinatorPro can be very useful to find DOM Based XSS on complex JavaScript web applications. This post will describe a Cross Origin Resource Sharing (CORS) abuse exploiting a flaw in the JavaScript Plus One code on plus.google.com.
Just to be clear, yes, it's the +1 button present on billions of pages around the internet.
The issue affected the context of https://plus.google.com which is the context of the social network.

Before going further with any details, a picture is worth a thousand words:



In order to better explain the issue and show how DOMinatorPro helped me in finding the problem, and since a video is worth a thousand pictures, I recorded a video on Youtube MindedSecurity channel.






In the video I deliberately chose to not show a single line of JavaScript in order to demonstrate what DOMinatorPro can do for a tester with little knowledge of JavaScript.

In this post, on the other hand, I'd like to discuss about how the input was treated and how Google fixed the issue with input validation.

Code Issue Details

The offending URL in a simplified version was:
https://plusone.google.com/_/+1/fastbutton?url=http://www.example.com&ic=1&jsh=m;/_/apps-static/_/js/gapi/__features__/rt=j/ver=ZZZZ/sv=1/am=!YYYY/d=1/rs=XXX

First of all, a throw "Bad URL " exception can be spotted on line 425, which actually controls for the presence of multiple callbacks (/cb=/) in 'l' variable and  for the presence of classic  /[@"'<>#\?&%]/ metacharacters in 'ga'. If some of those conditions are satisfied then an exception (Bad URL) is thrown. 
That is called data validation.
 
420 d = m.split(";");
421 d = (i = M[d.shift()]) &&  i(d);
422 if (!d) throw "Bad hint:" + m;
423 i = d = d[q]("__features__", T(r))[q](/\/$/, "") + 
                   (e[s] ? "/ed=1/exm=" + T(e) : "")
                   + ("/cb=gapi." + J);
424 l = i.match(ha); // "https://apis.google.com/TAINTED/cb=gapi.loaded_0".match(/\/cb=/g)
425 if (!l || !(1 === l[s] && 
                 ga[p](i) && !fa[p](i)))
                      throw "Bad URL " + d;
426 e[k].apply(e, r);
427 L("ml0", r, I);
428 c[R.f] || t.___gapisync ?
     (c = d, "loading" != u.readyState ? 
     W(c) :
     u.write("<" + S + ' src="' + encodeURI(c) + '"></' + S + ">")) :
     W(d, c, J) 
....

Line 428 is the call to the function that performs a XMLHttpRequest. By following the flow on Line 532 (beautified) the 'l' variable is tainted and it's the one that is traced by DOMinatorPro, originating by the location.href jsh parameter:

starting from: jsh=m;/_/apps-static/_/js/gapi/....

becomes "https://apis.google.com/_/apps-static/_/js/gapi/..../cb=gapi.loaded_0" and l[q] is the replace function :

function W(){
...
531 a = v.XMLHttpRequest,
532 l = l[q](/^https?:\/\/[^\/]+\//, "/"), 
533 m = new a;
534 m.open("GET", l, f)
...
}
So on line 532 https://apis.google.com/ is removed and 'l' becomes:

"/_/apps-static/_/js/gapi/..../cb=gapi.loaded_0"

The reason why there is execution is that the response is evaluated using the following code:

B=function(a,b,c){v.execScript?v.execScript(b,"JavaScript"):c?a.eval(b):
 (a=a.document,c=a.createElement("script"),c.defer=i,
 c.appendChild(a.createTextNode(b)...
Now, about the fix, I suggested Google to perform some input validation using A tag properties,
var aa=document.createElement("a");
aa.href=untrustedURL;

and then use aa.pathname to be sure it's the browser doing the parsing job but probably it does not work perfectly for all browsers.

In fact Google devs decided to add more data validation

if (!l || !(1 === l[v] && ha[q](d) && 
            ga[q](d) && h && 1 === h[v]))
  throw "Bad URL " + a;
that code changes one check and adds another condition, to the previous one we already discussed.
In particular:
 
ga[q](d) changes from /[@"'><#\?&%]/ (blacklist) 
                 to  /^[\/_a-zA-Z0-9,.\-!:=]+$/ (whitelist)
 
And  
1 === h[v] has been added and means if there is 
            only one "//"  (like http://  )
Which seems pretty solid to me, at least in the context of this specific issue; of course, bypasses are always around the corner, but I'm sure Google security guys took the best effort to be sure it's safe!

Conclusions

DOM Based XSS still remains quite untested, and that's because JavaScript is not easy to analyze in complex scenarios.
DOMinatorPro can really help in finding issue in the easy-to-hard-to-identify range of DOM Based XSS category, because DOMinatorPro is not as simple as you might think, it's a complex piece of software and with a large knowledge base in it.