Account Recovery XSS

As part of the Google VRP, we receive quite a few reports related to account recovery. This post offers a look at one of the best account recovery reports we've seen recently.

Account recovery is complex. Many of the reports we receive are the result of small-scale testing, like creating a new account and then trying to recover it from the same machine, browser, and IP address. In these cases, our systems can assume with high fidelity that the recovery is attempted by the legitimate owner of the account — so a successful recovery attempt is unlikely to signify a real security bug.

A recent report from Ramzes goes beyond this sort of limited testing. The exploit illustrates how an attacker can initiate a recovery flow by executing JavaScript on the origin that serves the account recovery page, and then complete the flow using several other techniques along the way.

Step 1: XSS on

Ramzes found an XSS vulnerability in the API that many Google web applications use to display help articles inline without navigating to the Google Help Center. For example, Docs displays help articles in a popover:

The iframe (outlined in red) is responsible for displaying the help article. This "content frame", which is served from the domain, uses an API to retrieve the HTML code of the article to display. The API endpoint is on a different domain; the content frame communicates with it (via the postMessage() method) using an iframe (“communication frame”) served from that domain.

In this case, the source of the problem was that the URL of the communication frame is user-controllable, through a URL parameter of the content frame. In one of his previous reports, Ramzes noted that it was possible to specify a javascript: URL, which was then set as the communication frame URL, leading to direct JavaScript execution. This problem was fixed by limiting the URL scheme to https://, but at that point we didn’t realize that providing an arbitrary https:// URL is also dangerous. With an arbitrary URL, an attacker-controlled page in the communication frame could send a message (containing HTML) to the content frame, and the content frame would display that as a help article.

To summarize:

  1. The exploit opens the content frame URL in a new window (it can only be framed by same-origin pages, so a popup is necessary) with the communication frame URL parameter set to an attacker-controlled page.
  2. The attacker-controlled page sends a message to the newly opened page to make it initialize the communication frame.
  3. The rogue communication frame sends a message containing HTML to the parent page.
  4. The content frame treats the received code as the trusted HTML code of a help article and injects it into the DOM, leading to XSS.

The next stage of the payload can then run on, starting the recovery process by entering the target account email address:

Step 2:

The next step of the recovery process asks for the last known password. This form is served from a different domain:

After the form is submitted in the first step, several HTTP redirects occur, resulting in a final URL that contains a token. The form in step 2 can only be submitted via the URL that contains the token, so it acts as a CSRF token. The CSRF protection could be bypassed with an XSS attack on the domain. But the attack surface on that domain is quite small — only a handful of applications are hosted there, so finding XSS vulnerabilities is difficult. Instead, the exploit used a different solution.

To get the CSRF token, the exploit has to follow the redirect chain and read the final URL. However, as far as we know, it's not possible to read cross-origin redirect URLs in the browser under the circumstances required for this bug. That's because the same-origin policy prevents reading cross-origin redirect URLs via the XMLHttpRequest and fetch APIs. It could be done with a server-side component, but the intermediate steps of the redirect chain use HTTP-only cookies to identify the user, and those cookies cannot be read and transferred to the server.

But Ramzes discovered that the last HTTP request to the domain works even without cookies, and the final redirect target URL depends only on a state parameter in the URL, passed to this step from the previous redirect.

To summarize, the exploit does the following:

  1. Follow the redirect chain while it stays on
  2. Read the state parameter from the URL of the last request.
  3. Transfer the state parameter to a server-side script.
  4. Repeat the last HTTP request with the received state parameter, and read the redirect target URL’s CSRF token.
  5. Return the CSRF token to the client side.
  6. Submit the form with the known CSRF token.

This way, it’s possible to simulate a user clicking the second button (“I don’t know [any previous password]”) and submit the form without entering a password:

Step 3: Choosing the questionnaire

The account recovery process continues, back on the domain. Step 3 asks the user to choose from two options:

The exploit chooses the “Verify your identity” option, since this way, it’ll be possible to send the password reset link to the attacker’s email address (after completing a questionnaire).

Step 4: Answering the questionnaire using an information leak

Even if an attacker can get this far, a knowledge test must be passed before a password reset link can be sent to the attacker’s email address:

If the exact account creation date and last login date are known, this variant of the knowledge test can be short-circuited. But that information is very hard to guess, right? Unless you’re logged in and find a page where both of these dates are explicitly listed. As it turns out, there was indeed a page like that on the domain where the XSS payload was running, so the dates could easily be read:

Step 5: Win

Finally, after the responses to the questions are submitted, the account recovery process is complete and the password reset link is sent to the email address provided by the attacker.


After receiving this very useful report, we’ve fixed each problem separately in order to make similar exploits impossible in the future:

  • The help API now checks that the URLs of iframes used for cross-domain communication conform to a very strict whitelist.
  • We’ve added checks that prevent the CSRF token generation from being delegated to a server-side component.
  • We’ve fixed the information leak by removing the dates from the migration page.

In addition, for some time, we've been aiming to isolate some of the highest-value services in their own subdomains (e.g., or, so that they are not directly affected by XSS flaws discovered in unrelated components.

Final notes

The VRP panel decided to award $5,000 for the XSS portion of the report, and added another $7,500 as a Bug Chain Bonus. We’d like to thank Ramzes and all other VRP reporters for making Google a safer place for our users, and we look forward to receiving more high quality reports like this one.

Posted by Gábor Molnár, Information Security Engineer