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 accountaccounts/set_private
- This is used to mark a profile as privateaccounts/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.
Fix
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.
