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.
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.