It seems to be obvious but apparently this idea still isn’t common knowledge — HTTP Referer header is unreliable, and it is especially unsuited for any security measures. The Referer header isn’t always present because of users going to a page directly (via bookmark and similar), using an “unusual” browser (most commonly download helper applications), using filtering firewalls (privacy protection) etc. The Referer header might be incorrect because of the same filtering firewalls (some prefer to advertise rather than remove the header entirely), special browser extensions to manipulate the Referer header etc.
And in addition to that we also have the fact that referring websites can remove the Referer header with tricks like META refresh. With slightly more advanced tricks these websites can even send POST requests without a Referer header. Sending a fake Referer header is harder but in the past it was possible via XMLHttpRequest or Flash — and it still might be possible somehow.
So, what does this all have to do with Cross-Site Request Forgery (CSRF)? With CSRF a malicious website can abuse the fact that its visitor has some special kind of access to another website (usually because he is logged in on that site). E.g. the malicious site can embed an HTML tag like this one:
This makes the browser of the site visitor send a request to good.site.example — with user’s IP address and with user’s authentication cookies. If good.site.example accepts the request because it comes from an authenticated user, the malicious site might do anything the user himself can do, like posting spam in a forum or changing user’s password.
Now to the solutions. A well-designed site should only accept GET requests to show some information, any actions that actually change something should always be sent as POST requests. This is a good first step, and will raise the bar for CSRF — it will no longer be possible to run a CSRF attack against your site by simply posting an image to a forum. However, if the attacker has full control over the HTML code of the attacking web page, he can still create a hidden form and submit it automatically, so CSRF isn’t limited to GET requests.
And here comes the wrong solution. As a cheap way to detect that the request actually originated on your site, some people use the Referer header. And a missing Referer header is also accepted because it would break too many users. This has the obvious problem that malicious sites can also send requests with an empty Referer header, as I mentioned above. Thankfully, addons.mozilla.org closed this vulnerability within days after I demonstrated how it can be exploited. The new solution is to send user’s session ID with all POST requests, if the received session ID doesn’t match the one in user’s cookie the request is ignored. Since the malicious site cannot know the session ID (by definition, otherwise it wouldn’t need the user to perform the request), this effectively prevents CSRF.
A more interesting case is the Fritz!Box router by AVM. After GNUCITIZEN reported on CSRF vulnerabilities in router web interfaces, I decided to have a look at my own Fritz!Box. After the initial impression that it didn’t have a CSRF protection, I discovered that the CSRF protection was there but of a very peculiar kind. While being based on Referer header, it didn’t simply compare the host name part of it with the valid values (“fritz.box” or the IP address of the router), it did a DNS request to resolve it. If the DNS request failed or the host name resolved to a local address, the request was accepted. No wonder that trying to do CSRF from localhost worked.
I reported that issue to AVM in January, sent exploit code (that would make the router dial a paid phone number) as well as a description of DNS rebinding as an additional (though more complicated) way to fool their protection. The new firmware came out half a month ago. What changed? There is now a recommendation in the user interface to set a configuration password (it doesn’t require you to set the password however meaning that the majority of users will stay vulnerable). Also, automatic dialing is now disabled unless you set a configuration password (great, that leaves only a few hundred other options in the user interface that can still be tweaked via CSRF). Oh, and the router can no longer be accessed as “http://fritz.box/”, you have to know the IP address — not sure whether this change is related but most people will use the default IP address anyway. To say that I am disappointed is an understatement.
There are already reports of first CSRF attacks against DSL routers. So, if you own a Fritz!Box and don’t want to become the next victim, I recommend you set a configuration password. And moving the router away from a guessable IP address should be a good idea as well. Oh, and feel free to contact firstname.lastname@example.org to complain about their routers still being vulnerable with the default configuration.
Update: Apparently, you can still access the router as “http://fritz.box/”, it only didn’t work for me because of VPN. So moving the router to a different IP address is pointless unless you also block the addresses “http://fritz.box/” and “http://fritz.fonwlan.box/” (e.g. with Adblock Plus or the hosts file).