CSRF: It's not trivial

Tags: security, webdevelopment, websites.
By lucb1e on 2012-07-06 00:33:12 +0100

In the past few weeks I've found two websites with CSRF vulnerabilities. I wasn't really looking for it, but when they don't require me to enter my current password to change the password (or e-mail address, by which the password can be reset), it raises flags.

So what can you do with a CSRF vulnerability?
In one case, I could easily have gained myself admin permissions on a website with thousands of visitors a day.
The other, I'm not entirely sure what the extent was, but certainly get myself access to FTP accounts from websites.

CSRF stands for Cross-Site Request Forgery. It works like this:
Upon logging in to a website, pretty much any website, the website stores a cookie in your browser. One example would be PHPSESSID=eb3nvr68fdqmcrfks1raqkr467. This unique number is sent to the website every time you visit it. The website looks it up in some sort of database, and if it's found it knows you are logged in. Logging in is only one action, a cookie is needed to keep you logged in between pages (or often even after you closed the browser).

Now, websites can't simply tell the browser "Give me the cookie for facebook.com". If any website could, for example this website, I could save the cookie and pretend I'm you by using this cookie instead of my own. So they blocked that. However, what I can do is tell your browser "Send this data to facebook.com". For example, I could fake the password change form:
"Send password=thechosenone&confirm=thechosenone to facebook.com/passwdchange.php".
As I mentioned, the cookie is sent to the website along with every request, also this fake request originating from another website.

Now this won't work on Facebook or any decent website. Here's why:
First off, in the password change form you should be required to fill in your current password. Otherwise anyone using your computer can change the password without even knowing it; you set it to 'remember me', right? What if your phone or laptop got stolen? The website will remember you are logged in, and whoever uses it can change the password.

Secondly, Facebook includes this in every form:
<input type="hidden" name="fb_dtsg" value="7nC40gbJ" />
As you see, it's hidden. You won't see this, but it is sent along with every request made to the site; even to logout for example. Of course the value changes per user, and probably even from time to time, so an attacker can't guess this and fake a request.

Without this code, Facebook won't even consider processing the request (or, that's what the system relies on). When asking the browser to send data to Facebook, it will include the cookie, but there shouldn't be any way to obtain this hidden field. (Actually, this hidden field is called a token, rather similar to SecurID tokens or codes you receive on your phone for two-step authentication.)


So this all sounds pretty critical, but how could anyone abuse this? You'd have to track down users of the website, make sure they're logged in... Well there are usually easy fixes for that.
1. Google is your best friend: Search for "I use shiftedit"
2. Waarmaarraar.nl has an internal messaging system.

Yeah, Shiftedit and Waarmaarraar were the websites I found to be vulnerable. Big credits to them for the way they handled this though! Both fixed it sooner as I had expected, and neither banned me from the site or anything (not that I did any harm, but it wouldn't be the first time the whistleblower is portrayed the bad guy).

Anyway, technique number one is not perfect, but it gets the job done. You should be able to find contact info for most people. If the Google result is recent, good chance they're still logged in to the website. (Isn't it neat how Google allows you to only show results from the past month or so?) Make sure that whenever the attack is attempted, they're redirected to a Rickroll video or something. This makes them think you're a sucky teenager with no life, and they'll move on. Meanwhile you can check if it worked.

The second technique pretty much guarantees they're on the website; it's where your message arrives.

You should usually be able to find some text which makes them click, like "Sorry to bother you, but I've found a pretty nasty bug in [his website]. It's probably best explained through a screenshot: [yourdomain]/uploads/scrnshot391.png". Of course it's no png file, so make it send back a 'Content-type: text/html' header. Then either let the page appear to keep loading, or display a loading icon with the text "Loading your image...". Before they know it, it's done. The only problem is if they don't have Javascript enabled. Perhaps this helps:
<noscript>Please enable Javascript to load the image</noscript>


So that's how. Now how to fix this on your website:
Along with every form, you should include a hidden token. This token could be generated as follows:
<?php
    function getToken() {
        // This generates a token valid for as long as the user is logged in
        return hash("sha256", "your-random-salt" . userSesssionId());
    }
    echo "<input type='hidden' name='token' value='" . getToken() . "' />";


Then, to check the action:
<?php
    function validateToken() {
        // If we generate the token again, does it still match?
        return $_POST["token"] == getToken();
    }
    if (!validateToken())
        trigger_error("Invalid session token. Please confirm that you want to submit this message [or whatever action they are doing]. If you keep seeing this error, please try logging in again.");


Of course, trigger_error() should display the message along with the submitted data. The user should only have to click "Submit" or so again, and the requested action should be performed. This way the user will undoubtedly review what is being done; an attacker can't automate this (not through CSRF at least).

You could go much more advanced; storing sessions for a certain time and saving them in a database to make them one-time use only, etc. This is more secure against other kinds of attacks, but the above method wards off CSRF, is incredibly quick, and is easy to implement and revise. Other techniques are less secure or may produce bugs (like checking the HTTP referrer for every POST action), or require structural changes like database fields.


Found a CSRF vulnerability? You should notify the admin. Perhaps you should have a look at 'responsible disclosure' on Wikipedia. If you play the whitehat, you might get all the good stuff like get in the news or receive free service. (For the record, Shiftedit offered a year of free premium service, very nice of them!)


PS. One more thing: Don't announce the issue you found to a non-tech crowd. They won't be amused if you make their account friend you automatically ;)
lucb1e.com
Another post tagged 'security': To curl|bash or not to curl|bash

Look for more posts tagged security, webdevelopment or websites.

Previous post - Next post