Google Sites: A Tale of Five Vulnerabilities


I'd like to use my last blog post of the year to sum up five vulnerabilities I found in Google Sites during the course of the year. Google Sites grew out of JotSpot, a site creation service bought by Google in 2006 and made free to use in 2008. A multi-tiered permission system and multitude of customisation options makes Sites an interesting target to investigate.

For these issues Google awarded a total of $13,034.80, making Sites the most rewarding Google property I've looked at so far. The first four vulnerabilities described here earned Google's celebrated $3133.7 bounty, the last $500.

The headerHeight vulnerability

Google Sites has many customisation options, allowing changes to settings such as colours, fonts, and element dimensions. One of these settings, the global page header height, manifests on the page as a CSS style rule added directly to the header element. I found the value to be included on the page with no escaping, which allowed me to inject my own CSS rules, leading to a cross-site scripting vulnerability in certain browsers.

With a quick POST to the settings URL, I was able to exploit the vulnerability in Opera and Internet Explorer:


Which resulted in the following:

<td id="sites-header-title" class="" style="height: 80;-o-link:javascript:alert(document.cookie);-o-link-source:current;x:expression(alert(document.cookie));px">

Interestingly, I was able to trigger the XSS even in recent versions of Internet Explorer by generating a rendering error on an unrelated page, forcing the rendering engine into Compatibility View; once triggered for one page, the entire domain falls back into compatibility mode.

Update: Nicolas Grégoire points out that IE compatibility mode is inherited from the parent document when content is included in an iframe, providing another route to exploitation. For more, see the Mozilla blog article on the topic.

The navigation link vulnerability

Navigation boxes are a key feature of Sites, allowing you to conveniently group internal links to other areas of your site, as well as provide links to external websites. When adding links the frontend prevents you from saving non-HTTP URIs, but on a hunch I tried altering the save request directly:

json={"properties":{"component/718572616/navigationTree":"[{\"title\":\"Surprise!\",\"id\":\"javascript:alert(document.cookie)\"}]", "component/718572616/navDynamicDepth":0, "component/718572616/showSiteMap":false, "component/718572616/showRecents":false, "component/718572616/title":"Navigation", "component/718572616/hideTitle":false}, "requestPath":"/site/somesite/"}

Success! An arbitrary link could be injected:

<ul jotid="navList">
    <li class="nav-first ">
        <div dir="ltr" style="padding-left: 5px;">
            <a href="javascript:alert(document.cookie)" class="sites-navigation-link">Surprise!</a>

Because of the nature of Sites, the X-Frame-Options header is not present, allowing this vulnerability to be further leveraged by way of a clickjacking attack; by placing the injected link in an iframe with 0 opacity on top of a tempting target (I used a kitten's nose for my proof-of-concept), the link would lie in wait, ready to be clicked by unsuspecting users wanting to make a kitty purr.

The navbarAlignment vulnerability

As mentioned in the headerHeight vulnerability, there are many customisation options available in Google Sites. In a vulnerability somewhat similar to the first (yet handled by different logic and scripts) I found it possible to inject arbitrary content into the main site CSS file. Interestingly a few dozen element customisations are provided, yet only navbarAlignment and navbarMargin were passed through almost unfiltered.

I say almost, because there were two tricks I had to pull off to get the result I wanted. Firstly, only relative URLs were allowed in CSS url() statements. I quickly found, however, that although this was true when using single quotes or no quotes, using double quotes allowed me to pass through any data I wanted. Strange, but true. Secondly, something was filtering out the word javascript. I submitted javajavascriptscript and javascript popped out - good old single-pass filters!

json={ "navbarAlignment":"center} .body { background: url(\"javajavascriptscript:alert(document.cookie)\") } /*<script>alert(document.cookie)</script>*/ {","navbarMargin":"10","requestPath": "/site/somesite/system/app/pages/admin/appearance/themesColorsAndFonts"}

Note that although I was able to inject HTML as well as CSS, the application sent the correct Content-Type header when serving the CSS file, blocking this route of exploitation. Here's the final result:

#sites-chrome-header .sites-header-nav {
    text-align: center} .body { background: url("javascript:alert(document.cookie)") } /*<script>alert(document.cookie)</script>*/ { !important;

I almost missed this vulnerability. For one, I almost didn't check every element style which could be updated as they all seemed to be properly filtered. For another, I didn't spot the injection vector straight away as any invalid CSS resulted in the bad block being subtly removed from the CSS, making it look as though filtering had taken place.

The uploaded stylesheet vulnerability

Google Sites comes with a set of pre-configured themes which can be changed by the site admin as they like. By capturing and manipulating the POST request which happened when changing themes, I found that an arbitrary theme name could be set, which had the effect of changing the path of a stylesheet include. Normally this would point to, however when an unrecognised theme name was set, the include instead pointed to a themes path under

One feature of Sites is that users can upload custom files, which get served served from the domain (with a redirected to a Google sandbox domain to prevent attacks). By saving a theme name containing dots and slashes, it was possible to navigate out of the theme directory and up the tree to a custom CSS file which had earlier been uploaded.

<link rel="stylesheet" type="text/css" href="/site/somesite/_/rsrc/2147483647/system/app/themes/../../../../../files/injected-css/standard-css-../../../../../files/injected-css-ltr-ltr.css" />

The sidebar text vulnerability

Text sidebar boxes allow text to be entered and displayed alongside the main content. When created, each box is given its own numeric node ID for internal reference. During testing I found a rather strange effect, in that manipulating the ID to contain a single quote led to a bypass of the normal text filtering mechanism, allowing HTML to be injected into the text box. It's not clear why a single quote had this effect while no other character did; I had suspected SQL injection, but I was unable to gather any evidence of such.

json={"properties":{"component/31337'/type":"/system/app/components/min-textbox","component/31337'/content":"Surprise! <script>alert(document.cookie)</script>","config/sidebarRef":"","component//items":["31337'"]},"requestPath":"/site/somesite/"}

Additional filtering prevents the injected HTML being displayed directly on the page, however when the box is edited no such filtering takes place and the XSS is triggered. This allowed targeted attacks against other users with edit privileges. Interestingly, it's possible to give any other Google user edit permission without their knowledge (there's a "send invitation" flag that can be set to false), allowing any Google user to be targeted in a way similar to the navigation link vulnerability.