How I stole the data in millions of people’s Google accounts
You don’t know me, but there’s a good chance I know you.
That’s because I have complete and total access to the private information in millions of peoples’ Google accounts. Emailed bank statements, medical records in Google Drive, Facebook chat records sent via Gmail, Google Voice voicemails, private pictures in Google Photos. The list goes on. None of them have any idea, and none of them ever will. Perhaps one of them is you.
So how did I do it? It all starts with an app I made.
For obvious reasons I won’t give away the name. It’s a pretty straightforward app, designed for fitness enthusiasts, with features like logging your pace during a run and guiding you through strength-building exercises. Like many apps, it requires the user to create an account before they can start using it. According to analytics, about 60% of users opt for the enticing ‘Sign up with Google’ button instead.
The basics should seem familiar to you: when a user clicks this button in my app, it opens the Google sign-in page in an in-app browser window.
This user has two-factor authentication enabled on their account, so after entering their email and password, a dialog pops up asking if it’s really them. The dialog has their correct device and location, so they click Yes.
… and that’s it. The user can now proceed to use the app as normal, but I have full, unfettered access to their account from my remote server. And they’ll never get an email about it, and if you were dedicated enough to examine the network traffic, you’d see that the only network requests the device made the entire time were to subdomains of google.com.
So how in the world is this possible? Well, let’s return to that Sign In with Google button. Just to get this out of the way: for the unaware, when this button is clicked, the app can do whatever it wants. It can prompt you to sign in with Google, play a trumpet noise, or show you a cat GIF. Not to say those things are likely, but one can dream.
In my case, when it is clicked, my app opens a dialog with a WebView, and sets the URL to https://accounts.google.com/EmbeddedSetup. This is a real Google sign in page, but it’s one purpose-built for setting up a new Android device. This will be important later, when it will helpfully give us the exact information we need in the form of a cookie.
Unfortunately, this page doesn’t look — or behave — exactly the same as a standard Google login page, at least by default:
So now the real fun begins. I use standard APIs built in to both iOS and Android to inject a carefully-formulated fragment of Javascript code into the page, which modifies the page to look and behave exactly alike the standard one.
If you’re smart, perhaps now you’re thinking “but wait — if I can inject JavaScript, can’t I just steal the email and password directly out of the text fields?” You certainly can— in fact, here’s the code to do so. But in this day and age having access to someone’s email and password just isn’t enough. Unless we’re lucky enough to have a server no more than a hundred or so miles away from the user, our sign-in attempt will be blocked, with a ‘suspicious account activity’ notification and email. And two-factor authentication throws another wrench into our plans.
So, let’s talk about something called the Google master token. It sure sounds ominous, and in reality, well… it’s even more ominous than it sounds.
When you sign in to an Android device for the first time, the device sends the token received from the aforementioned embedded sign-up webpage to a special endpoint. Here’s an example of a typical request:
POST /auth HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 349
Host: android.clients.google.com
Connection: Keep-Alive
User-Agent: GoogleLoginService/1.3 (a10 JZO54K);gzip
app: com.google.android.gmsapp=com.google.android.gms&client_sig=38918a453d07199354f8b19af05ec6562ced5788&callerPkg=com.google.android.gms&callerSig=38918a453d07199354f8b19af05ec6562ced5788&service=ac2dm&Token=oauth2_4%2F4AY0e-l5vPImYAf8XsnlrdshQQeNir3rSBx5uJ2oO9Tfl17LpsaBpGf1E2euc18UyOc8MnM&ACCESS_TOKEN=1&add_account=1&get_accountid=1&google_play_services_version=204713000
The token in this request is given by the sign-in page’s cookies, and everything else is publicly available information (thanks, microG!) Two-factor authentication is also handled by the sign-in webpage, with no additional effort on our part.
After this, the aforementioned endpoint sends it back: the master token. And how do I get access to it without making a suspicious network request? That part’s easy: log it to Google’s Firebase.
And boy is this token powerful.
The master token never expires, unless the user changes their password or two-factor settings. It’s not subject to any security checks based on location, IP, or activity, as best as I can tell. It never results an email or notification being sent to the user.
And with it, I now have access to every single Google service that was ever, at any point, accessible from a mobile device, as the target user’s account. A single POST call allows me to masquerade as an official Google app and retrieve an OAuth token for anything, private (and likely unreleased) APIs included. I can read all of their emails, browse their Google Drive, access backups of their phone, and look through their Google Photos, while checking their browser history and messaging their friends on Google Messenger. I even modified a version of microG so that I could use any of these user accounts with regular Google apps.
And remember, this is what the whole process looks like:
I urge you to ask yourself: would you be fooled?
The Reveal
As many of you may have suspected, this post is not entirely truthful. I have not released this fitness app onto the Play Store, nor have I collected millions of master tokens. Thanks to this post for inspiration. But yes, these methods do work. I absolutely could release such an app, and so could anyone else (and maybe they have).
FAQ
Q: It’s different than the normal Google sign in page! I would notice!
A: Not as much as you might think, and no you wouldn’t. While Google sign-in on Android typically has an ‘account picker’ style interface, this isn’t universally true, with web-based apps like those made with Ionic and Cordova, as well as most iOS apps, often opting for the web-based version that looks nearly identical to this. In addition, if you think you’d be tipped off by the lack of a ‘X app would like to access’ screen, that could be easily added with a few more hours of work.
Q: Does it work on iOS too?
A: I haven’t tried it, but there’s no reason to believe it wouldn’t.
Q: What should be done about it?
A: That’s actually a really tough question. Nothing I did would technically be considered an ‘exploit’ — but it’s still very dangerous. It would be a good idea, for starters, for Google to make their ‘sign in from new device’ notifications actually work. Although I do get them when signing in on my computer, I can’t say I ever saw them trigger when testing this app. Another good idea would be to update their guidelines for the Sign In with Google button, which right now say literally nothing about how it should be implemented. Perhaps they could venture far into the land of security through obscurity, which for all its pitfalls has so far worked wonders for maintaining Apple’s lock on iMessage.
Unfortunately, I can’t say I’m confident in the ability for a true technical solution to be made. As long as it is possible for the official Google app to do something, it will always be possible for a third-party to do the same with enough work. That said, they’ve got some smart people over there, so let’s see.
Q: Is this a problem with every third-party sign in system?
A: It certainly seems like it might be. I haven’t investigated clearly to see which ones send alerts and which don’t, et cetera, but even for those which do, those alerts aren’t necessarily clear. Sign in with Apple, for its part, at least has very strict guidelines which are actually enforced by its app store (presumably its main user base) — although it had its own issue which was bad enough to make this one look trivial by comparison.
The Real Story
While nowhere near millions, I have unfortunately actually collected a few master tokens from unknowing users— entirely by accident. The true story of this discovery starts with the creation of my now-defunct (and never widely released) music player app Carbon Player. It was designed to be a replacement for the Google Play Music app (remember when that still existed?) with a much better design. To access the user’s music library, I reimplemented Simon Weber’s gmusicapi in Java, but despite translating the code, I at first didn’t pay all too much notice to how the login process worked. All I really got was that it took the user’s email and password, which I prompted for in a standard no-frills dialog, and after a few requests, spat out some tokens I could use for retrieving music content.
Before releasing the first version of the app to my small group of testers, I went through and added logging everywhere, and also added a logging interceptor that would automatically upload all of the logs to Firebase. Of course, I wasn’t dumb enough to log the password, but I did end up mistakenly logging the three tokens that I got from my implementation of gmusicapi. Two were relatively harmless — providing access only to various parts of the music library. But the other one was the master token.
Now, the app only ever got perhaps 25 downloads, and I quickly gave up on it to focus on school. But before that happened, I did release maybe two major updates, one of which added a recreation of the snazzy, new (at the time) Google Play Music homepage, one of the only parts of the original app that managed to look good.
This ended up being a much bigger deal than I thought, and involved a strange amount of Protobuf reverse-engineering. More importantly, for whatever reason it required a completely different login token, one that wasn’t implemented in gmusicapi. So, to add it I ended up spending several hours actually deciphering the internals of how the login system worked, leading to a “holy, shit” moment when I realized I’d been logging possibly the most sensitive piece of data in existence. Suffice it to say, that logging was removed. To the 25 people who downloaded the app: I’m sorry! (And your tokens have been deleted from Firebase…)
Completely separately, I had been working at a startup that was creating a password manager app. One of the key tenets of this app was that it stored all the passwords only on your phone, but still let you log in on your computer via a JavaScript bookmarklet that would ‘bridge’ the devices through a QR code. In order to make this smoother, when you logged into a new site on your computer, the app would pull up the same site on the phone and inject a carefully-formulated piece of JavaScript to capture usernames, passwords, and anything else. Sound familiar?
Eventually, these two ideas became inseparable in my head. After a working prototype in Carbon Player and a few years of being too busy to work on it, I finally got around to building it out as its own demo. In the process, a lot changed — in fact, the method I described today in this article is quite different than what I originally prototyped three years ago due to Google changing their sign-in system. But the end result is the same, and it’s just as frightening as it’s ever been.
If you’d like, you can download the demo here to see it in action yourself, which I promise does not log anything to the cloud. Note that it’s pretty simple and very untested, so there’s a good chance it won’t work if your account is configured differently then mine.
Otherwise, thanks for reading, and I hope you enjoyed this quick reminder to always be skeptical. What may appear to be the most harmless of things on the surface can always turn out to be much worse on the inside. (Or much better, if said thing happens to be an ice cream cake.)