Today, I bring to you a tale of a simple mistake with huge potential repercussions for users. The issue today is Facebook's, and they handled the situation well, but it could easily have been any skilled developer. In this post, you'll see how one seemingly innocuous line of code could have allowed malicious Android apps to impersonate you on your acebook account.
Parse includes tools that make it easier to add Facebook support to your application. Developers can use Parse to present a Facebook-managed authentication flow and ask the user to grant specific permissions. To implement this feature, we use Facebook's Android SDK to get an authorized access token from Facebook.
With this access token it is possible to post messages to the user's wall, access the user's profile and friends lists, collect email addresses, and so on. These are all legitimate uses of an access token by an application with no malicious intent. The user has the ability to revoke an application's permissions at any time, and if the user chooses not to grant permissions to one application, it can still grant those permissions to another application that it trusts.
Another useful developer tool, provided by the Android platform, is logcat, which is essentially a device-wide diagnostic log to which applications and system processes can write. Applications can capture this log for debugging purposes, which when coupled with a bug or crash report can provide a huge amount of insight into the cause of the problem.
One of the permissions an Android app can request is the “Read Sensitive Log Data” extended permission, which allows an application to read from the system's various log files. This allows it to discover general information about what you are doing with the device, potentially including personal or private information. As a result, users should only grant this permission to apps they trust.
Most Android developers use logcat, and Facebook is no exception. But what happens when an SDK is just slightly too trusting of a built-in tool like logcat?
While working on adding Facebook integration to Parse's Android SDK, I discovered an odd behavior in my test application. Whenever I logged in through the Facebook SDK, a line like this would appear in my logcat viewer:
In plain text, I could see the entire access token that had just been granted after logging in, encoded into a URL.
It wasn't immediately apparent to me that this was a problem. After all, this was coming from code that my app was running. As long as I was the only one who could see it, no harm was done. But I knew that logcat was essentially a public diagnostic bulletin board for Android applications, so I tried running a few other apps that use the Facebook SDK - big apps from developers like Foursquare, Zynga, and Sony - and observed that they also printed this line after I logged in.
Still, this wasn't a serious issue if logcat was only visible when debugging was enabled for the device, or accessible only through Android's developer tools on a PC. However, I knew of a handy little android app called CatLog on the Android market, which is an on-device logcat viewer. My intuition was that CatLog would allow me to see the same information as I was getting while debugging my application, and that if CatLog could do it, I could write a simple app that uses a similar technique to scrape the output of the Facebook SDK in order to "steal" Facebook access tokens from the user of the device.
Sure enough, CatLog displayed the access token for the world to see, and alarm bells went off in my head. To verify that this was an exploitable security vulnerability, I checked that with only an access token, I could access Facebook's graph API and start pulling down data for that user. Facebook's own Graph API Explorer made testing this theory easy. The security hole was real.
This was a serious issue, and not just something that would keep me awake at night as a Facebook user. By using an unmodified version of the Facebook SDK with Parse, we were exposing our developers and their apps' users to the same vulnerability - something with potentially broad repercussions.
Reporting the issue
Before I reported this vulnerability to Facebook, I wanted to be absolutely sure that I had found a real issue. So the night after making my discovery, I sat down to write a quick proof-of-concept application that would exploit this, detecting (in real time), extracting, and using Facebook access tokens silently in the background on my Android phone.
I called my app "FacebookThief", and just an hour or so later, I had a working proof of concept. In less than 20 lines of non-boilerplate code, I could masquerade as any Facebook user that logged into any app on the device that used the Facebook SDK. It wouldn't even be clear that my app was the culprit. If my app posted to a user's profile using the stolen access token, it would show up as though it came from the application from which it was stolen!
The next morning I reported the vulnerability to Facebook, linking to FacebookThief and its source. Facebook responded like professionals. Within a few hours, a Facebook security expert contacted me to let me know they were working to verify the issue, and within 24 hours, they had corrected the issue in the Facebook SDK.
Of course, we immediately integrated the new version of the SDK into Parse.
The trouble with client software
All of this happened mid-February, and trust me, I would've loved to shout to the rooftops after making this discovery. But as the severity of the issue sank in, I really began to empathize with what Facebook had on their hands here.
The Facebook Android SDK is software that runs on the client and is built into other applications, so Facebook has no real way to push this update out to all of its developers. Each application developer that consumes this SDK would need to update their applications and push that update out to users. But that's hundreds or thousands of apps and developers, each with its own user base that needs to move off of the old, vulnerable versions of the apps. Each developer has to manually pull down the latest Facebook SDK, incorporate it into their applications, and push out updates (through various app stores and approval processes) before this problem can be eradicated from the ecosystem. This is something that could linger forever as apps with the vulnerability stop being supported.
Client SDK development poses some unique challenges. You don't really have control over updates to your own software, so every release has a permanence that is out of your control. At Parse, we're keenly aware of this, and take great pains to ensure that our SDK doesn't leave lasting holes in our developers' apps.
Facebook asked me to wait to disclose this issue until they had a chance to work with major application developers to get updates to their apps pushed out. They have informed me that all of the major developers that use the Android Facebook SDK have now shipped versions that correct this issue.
Fortunately, there has been no evidence that this has been exploited in the wild.
One line of code
Ultimately, this bug came down to one line of code in the Facebook SDK:
Log.d("Facebook-authorize", "Login Success! access_token=" + getAccessToken() + " expires=" + getAccessExpires());
It's a wolf in sheep's clothing. Logcat is not a safe place to put sensitive data, but it's easy to mistakenly assume that only developers will see this.
Any Android application that has the "READ SENSITIVE LOG DATA" permission has access to this data. There are legitimate reasons to request this permission, but users should be wary of any app that requests this permission unnecessarily.
The moral of the story
It's important to take situations like this seriously. It puts users at risk, and this is a problem that's not isolated to Facebook - it affects any Android developer that has chosen to use the Facebook SDK. So please, take heed:
Users: Make sure you keep your apps updated. When security fixes come out, don't wait -
- Developers: Please think carefully about any data that you make public. Logcat is not a safe place to put sensitive information, even if it makes debugging much more convenient. Protect yourself and your users - err on the side of sanitizing data that might be sensitive.
- Parse developers: We're looking out for you! Parse updated its SDK to fix this hole within a day of discovering it. We've contacted the developers at risk, but if you're on any version after 0.4.32, you're already protected.
Update: Users can protect themselves by downloading the latest version of their applications and uninstalling any untrustworthy apps. Fortunately, there has been no evidence that this has been exploited in the wild, but we remind all users to always exercise caution when granting apps permissions.