Monday, October 24, 2011

Stripping Referrer for fun and profit

tldr: New methods for client side only (no server side script) referrer stripping in POST & GET requests. Code at the end.

Referer is that tiny bit of information that browser sends to servers while you click your way through interwebs, always carrying the URL of the webpage you've clicked the link at (more or less). It's useful for webdevelopers. For example, if they know you've reached their page from Google search results they can tailor the webpage especially for you. Of course, it's a privacy leak, so users can turn off referrer sending in current browsers. All in all, Referer is usually spoken in SEO circles, which is not my pair of shoes. However, at least one thing makes Referer very interesting from security point of view.

Are you me?

Sometimes it's used as an access control mechanism. It all began with hotliking. Spammy websites started republishing content (e.g. images) from other websites, simply using <img src="http://original-website.example.com/image.jpg" /> .  While the spammy page looked ok for the viewers, it was stealing bandwidth (and content) from the original website. So to prevent this, websites started checking the Referer header that was added by the browser to image requests. If the referrer URL was from the original website, server would return the image. If it was from a 3rd party (http://spammy-website.info), it would return 403 or something else, so the spammy websites stopped looking good anymore.

Do no evil!

Then came Cross Site Resource Forgery. Visiting a malicious website when being simultaneously logged in to some vulnerable application caused havoc. This malicious website could send dangerous requests to the original vulnerable app and e.g. delete your account, change your e-mail address or set up a mail filter.  Some web developers started adding simple defense mechanism: just check the referrer!
  • if it's our original application URL, process the request
  • in other cases, deny
There - case solved. Only pages from legitimate location could make successful requests. But this quickly gave some problems - what about users that disabled referrer sending? They wouldn't be able to use the application at all. For many websites, this was quickly corrected - for missing referrer, give the benefit of doubt and allow. 

Stripping for the client!

Of course, it's a very weak spot and attackers quickly tried to use it to their advantage. The goal was simple: Strip referrer, keep the cookies. There are many ways for attackers to lose referrer using some server side redirect etc. You can even fake that header. How? Just check how referer.us does it. But I wanted something different. I wanted to be able to make arbitrary cross origin POST / GET requests with stripping referrer header using no server-side script whatsoever. Only client-side techniques. I couldn't find any previous tries for this and only found old mentions about meta redirects. There of course are several known attempts of server-side techniques e.g. Jeremiah Grossman directed me to an example from 2006 of doing this with Flash.

Why client side only? Sometimes, as an attacker, you don't have control over the server at all, you can only inject some HTML (e.g. XSS) in a secondary host the victim user will be directed to. Client side only has fewer requirements to plant an attack.

Was it good?

Pretty good actually. Dealing with multiple browser quirks I was able to strip the referrer in every major browser for GET requests and in Firefox / WebKit for POST requests. Here's how:
function lose_in_webkit(url) {
 // chrome loses it in data uris
 location = "data:text/html,<script>location='" + url + '&_=' + Math.random() + "'</scr"+"ipt>";
 return false;
}

function lose_in_ie(url) {
 // ie loses referer in window.open()
 window.open(url + '&_='+Math.random());
}

function lose_in_ff(url) {
        // ff needs data:uri  AND meta refresh. Firefox, WebKit and Opera
 location = 'data:text/html,<html><meta http-equiv="refresh" content="0; url='+ url + '"></html>';
}

function post_and_lose(url) {
        // 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=f method=post action=\''+url+'\'></form><script>document.getElementById(\'f\').submit()</scri'+'pt>"></html>';
}
Demo and source.

Update:
@websterprodigy topped that with a nice way to lose the referrer in POST & GET in all browsers using <iframe src=about:blank>. Good job!

Why do I care?

Pentester: Let's imagine you encounter a CSRF flaw on a website, but this website does referrer checking. Now, without relying on server side techniques you can use these snippets to quickly prepare a CSRF proof of concept for your client. How often do websites rely on referrer? Pretty often, just wait for the next post (here it is) on a neat Alexa Top 50 example.

Developer: Now you know not to rely on referrer checking as CSRF protection. The only way is to use tokens!

11 comments:

Wayo said...

What about using an extra header to avoid CSRF?

kkotowicz said...

There's a problem with custom headers. In the past, browser plugins (Java, Flash, Sliverlight) could add arbitrary headers to requests (and cookies would be further added by browsers). I don't keep track of the things, but I suppose current versions of plugins disallow this. However, there probably are some obscure & old plugins out there with your users. It's not an old issue - the most recent vulnerability in Flash was patched this February - https://nealpoole.com/blog/2010/11/preventing-csrf-attacks-with-ajax-and-http-headers/#comment-1675 . There are probably more of those hidden in plugins code.

You need to have some token from the server. You can transfer it in HTTP header if you like, but it has to be unique per session. That way you'll be sure no weird plugin issue destroys your defense mechanism like it did for Django and Rails recently.

Your mom said...

Nice post!


In general relying on HTTP header requests is totally wrong.

And yes, using "nonce" is a better protection.

Soroush Dalili said...

Nice post. everything in one place!
I should also say "Gareth Heyes salty bypass" which was "Garethy Salty method" was written by me actually :D
http://www.google.co.uk/search?q=%22garethy%20salty%20method%22

kkotowicz said...

Aaah, thanks - corrected. Method name was a little tricky ;0

Phil P said...

Another way to strip the referer header is to have the CSRF request originate from an HTTPS site. HTTPS - > HTTP does not send a referer, but HTTP -> HTTP and HTTP -> HTTP does. This may vary browser to browser, but it certainly came in handy for one POC that I worked on.

Also, I think that referer checking can still be used for CSRF protection if the server validates that the referer is from the same domain and the expected page for the request. It seems dirty, but as far as I know, there isn't a way to spoof the referer hostname.

kkotowicz said...

yes, correct. I was looking at the situations where you don't have control over any server at all. AFAIK HTTPS => HTTP (or cross-dimain HTTPS for that matter) will strip referrer in any current browser.

As for strict referrer checking - correct too, but it's not used very often because developers can't rely on Referer header existence for vaious reasons (e.g. proxies or browser privacy settings).

pogue972 said...

Is it possible to translate this into an extension that would work in browsers to strip the referrer header from being sent? I hunted around for ways to do it in Chrome, but all I found was extensions that tacked on rel="noreferrer" to EVERY link in pages you visit.

Obviously, this would break a lot of functionality for certain websites, so some would need to be whitelisted, but I believe that would be a helpful privacy tool in some circumstances.

ballsofice said...

With Firefox you just need to goto about:config and set network.http.sendRefererHeader 0. For more advanced referer control (per website rules, referer spoofing etc) use the RefControl addon.

Morris Johns said...

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 url you have pushed (if the user bookmarks it or otherwise loads or copies the url).

Marseiller said...

what about alexa refferer? is that possible to make it for users?