Back in April I found three CSRF issues on Instagram, stemming from their Android/iOS App API (which is slightly different from their public API - it’s hosted on their main domain and doesn’t need an access token).

These issues were present in the following end-points:

  • accounts/remove_profile_pic - This is used to remove the profile picture from an account
  • accounts/set_private - This is used to mark a profile as private
  • accounts/set_public - This is used to mark a profile as public

Obviously the best one out of these is accounts/set_public. With a simple GET request we can reveal anyones profile and access their private pictures. Pretty cool.

Facebook patched the holes pretty quickly and I was awarded a decent bounty for it.

Once patched I checked to make sure that it was indeed fixed, and issuing a GET request returns a 405 Method Not Allowed response.

Round Two

I didn’t blog about the issue and completely forgot about it until recently. I decided to have another look at the Android App to see if there was any new end-points to play around with.

Pretty much all API requests within the app call a method named setSignedBody. This generates a hash of the parameters with a secret embedded in an .so file, meaning we can’t craft our own request on-the-fly and submit on the users behalf (without extracting the secret).

However, the three end-points I submitted still didn’t use setSignedBody (presumably because there are no parameters needed), and therefore no token is sent along. Because of this, we can submit a POST request and still perform the attack which was supposed to be fixed!

The use of setSignedBody without a CSRF token means that all end-points are vulnerable to a replay attack. You simply submit the request yourself, catch the request in Burp, and replay to the victim. Unfortunately, this is something I realised after the bug was fixed, so no screenshots available.

So the moral here is that you should double-double-check that an issue is fixed. If I’d been more thorough in testing the fix I would have spotted it sooner than four months, my bad.


This is now patched by requiring all requests to have a csrftoken parameter. Any request which is signed also requires a _uid parameter to prevent replay attacks (unless you extract the secret…).

The original proof-of-concept now returns a 400 error.

The response body is a JSON object showing the error message.

I’ve found a few bugs on various Facebook satellite/marketing domains (ones which are part of the Facebook brand, but not necessarily hosted/developed by them, and not under the * domain). Most of them aren’t that serious.

This one isn’t an exception, and I wouldn’t normally blog about it, but it’s an interesting use case as to why content types are important.

The bug is an XSS discovered on Facebook Studio. This is linked to by some Facebook marketing pages, and is used to showcase advertising campaigns on Facebook.

There is an area which allows you to submit work to the Gallery. This form conveniently has an option to scrape details from your Facebook page and fill in boxes for you (such as Company Name, Description).

This calls an AJAX end-point with your pages URL as a parameter.

If we set our pages description to something containing HTML/Javascript, it’s properly escaped. However, it’s escaped client-side. The end-point incorrectly sends a content-type header of text/html, when the response is actually JSON.

When browsed to directly (it doesn’t need any CSRF tokens to be viewed, despite the hash param), we see our script executed.

The cool thing about this bug is that whilst it’s not persistent (the payload is fetched when the page is visited), the code is not present in the request body, therefore avoiding Chrome’s XSS Auditor and IE’s XSS Filter.

Had the content type been set to application/json, the code would have not run (until you start to consider content sniffing…).


The content type is now set correctly.


  • 15th August 2013 - Issue Reported
  • 21st August 2013 - Acknowledgment of Report
  • 21st August 2013 - Issue Fixed

This is a quick post about a simple bug I found on Friendship Pages on Facebook. (Note: Not nearly as cool as a full account takeover, however!)

Friendship Pages show you how two users on Facebook are connected, with posts and photos they’re both tagged in, events they’ve both attended and common friends. On these pages, you’re given the option to upload a cover photo (like you would on your profile, or an event).

Removing A Cover

The cover photo on someones friendship page, we can remove from any account.

First, we need the friendship_id, which can be obtained with an AJAX call to /ajax/timeline/friendship_cover/selector, where profile_id is one user and friend_id is another.

Using this friendship_id we make an AJAX call to /ajax/timeline/friendship_cover/remove, placing the value into the profile_id parameter.

Refresh the page, and it’s disappeared.


Now, you can only remove your own cover.


  • 29th August 2013 - Reported
  • 2nd September 2013 - Acknowledgment of Report
  • 2nd September 2013 - Issue Fixed

This post will demonstrate a simple bug which will lead to a full takeover of any Facebook account, with no user interaction.

Facebook gives you the option of linking your mobile number with your account. This allows you to receive updates via SMS, and also means you can login using the number rather than your email address.

The flaw lies in the /ajax/settings/mobile/confirm_phone.php end-point. This takes various parameters, but the two main are code, which is the verification code received via your mobile, and profile_id, which is the account to link the number to.

The thing is, profile_id is set to your account (obviously), but changing it to your target’s doesn’t trigger an error.

To exploit this bug, we first send the letter F to 32665, which is Facebook’s SMS shortcode in the UK. We receive an 8 character verification code back.

We enter this code into the activation box (located here), and modify the profile_id element inside the fbMobileConfirmationForm form.

Submitting the request returns a 200. You can see the value of __user (which is sent with all AJAX requests) is different from the profile_id we modified.

Note: You may have to reauth after submitting the request, but the password required is yours, not the targets.

An SMS is then received with confirmation.

Now we can initate a password reset request against the user and get the code via SMS.

Another SMS is received with the reset code.

We enter this code into the form, choose a new password, and we’re done. The account is ours.


Facebook responsed by verifying that you have permission to modify the phone number on the profile denoted by profile_id.


  • 23rd May 2013 - Reported
  • 28th May 2013 - Acknowledgment of Report
  • 28th May 2013 - Issue Fixed


The bounty assigned to this bug was $20,000, clearly demonstrating the severity of the issue.

When you create a shop on Etsy, you can upload an image to be used as a banner.

The upload form in the administration section stops you changing the shop to one you don’t control, as expected.

There is, however, an AJAX end-point which can also be used to upload these images. This doesn’t check you’re the owner on upload.

We can easily upload any image we want onto any shop we want. This could be used to damage a business’s reputation, or like what happened on the Silk Road, upload a banner which prompts any prospective customers to send any orders and payments to an email address we control.


Etsy fixed this in a simple way - they now check you’re the owner on upload.