Site-Wide CSRF

Reading time ~1 minute

I originally wasn’t going to publish this, but @phwd wanted to hear about some of my recent bugs so this post is dedicated to him.

This issue was also found by @mazen160, who blogged about it back in June.

When launched back in April, I quickly had a look for any low-hanging fruit.

One of the first things to do is check end-points for Cross-Site Request Forgery issues. This is whereby an attacker can abuse the fact that cookies are implicity sent with a request (regardless of where the request is made from), and perform actions on another users behalf, such as sending a message or updating a status.

There are different ways of mitigating this (with varying results), but usually this is achieved by sending a non-guessable token with each request, and comparing it to a value stored server-side/decrypting it and verifying the contents.

The way I normally check for these is as follows:

  1. Perform the request without modifying the parameters, so we can see what the expected result is
  2. Remove the CSRF token completely (in this case, the fb_dtsg parameter)
  3. Modify one of the characters in the token (but keep the length the same)
  4. Remove the value of the token (but leave the parameter in place)
  5. Convert to a GET request

If any of the above steps produce the same result as #1 then we know that the end-point is likely to be vulnerable (there are some instances where you might get a successful response, but in fact no data has been modified and therefore the token hasn’t been checked).

Normally, on Faceboook, the response is one of two, depending on if the request is an AJAX request or not (indicated by the __a parameter).

Either a redirect to /common/invalid_request.php:

Or an error message:

I submitted the following request to change the sound_enabled setting, without fb_dtsg:

POST /settings/edit/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded


Which surprisingly gave me the following response:

HTTP/1.1 200 OK
Content-Type: application/x-javascript; charset=utf-8
Content-Length: 3559

for (;;);{"__ar":1,"payload":[],"jsmods":{"instances":[["m_a_0",["MarketingLogger"],[null,{"is_mobile":false,"controller_name":"XMessengerDotComSettingsEditController"

I tried another end-point, this time to remove a user from a group thread.

POST /chat/remove_participants/?uid=100...&tid=153... HTTP/1.1
Content-Type: application/x-www-form-urlencoded


Which also worked:

HTTP/1.1 200 OK
Content-Type: application/x-javascript; charset=utf-8
Content-Length: 136

for (;;);{"__ar":1,"payload":null,"domops":[["replace","^.fbProfileBrowserListItem",true,null]],"bootloadable":{},"ixData":{},"lid":"0"}

After trying one more I realised that the check was missing on every request.


Simple and quick fix - tokens are now properly checked on every request.

From Bug Bounty Hunter, to Engineer, and Beyond

A couple weeks ago I had my last day on Facebook's Product Security team. Abittersweet moment, but one which marks a "new chapter" in my ...… Continue reading