Bottom Line Up Front
During some security research for Counter Hack Challenges I found a Zero-day in Google Cloud.
This vulnerability revolves around the challenge and challenge-response authorization from the Google Chrome Enterprise Connectors.
The challenge-responses were never invalidated and could be generated across organizations.
That means if a threat actor gained access to a challenge-response, it would remain valid indefinitely.
A threat actor could also generate challenge-responses for your organization by only knowing the service-key name.
Nonsensitive information is suddenly sensitive.
This was disclosed to Google through their Google Bug Bounty program in November 2024.
Google responded that they have patched the vulnerability, but without access to Google Enterprise, I can not verify that all vulnerabilities have been mitigated.
This system is not intended for multi-factor authentication(MFA), but if you previously did use it as such, it is visible in network logs but not Google Cloud logs.
Search for multiple similar X-Verified-Access-Challenge-Response
requests to your server.
Due to errors and missing documentation in the v1
version of the API, I could not verify the vulnerabilities there.
Discovery
During my work with Counter Hack Challenges, we stumbled upon some Identity Provider (IdP) research. Part of the research had a Google Enterprise connector implemented as a method of authorization. The connectors main purpose is to ensure that the authenticating computer is authenticating through an enrolled browser or device. When we had an enrolled device we could generate challenge-responses when challenged by submitting the challenge-response to the endpoint https://verifiedaccess.googleapis.com/v2/challenge, but our research uncovered that the challenge-responses were never invalidated. Google documentation stated that the challenge-responses were only valid for a minute, but the challenge-responses were never invalidated. This sparked further investigation into what was going on.
Concept
The Google Chrome Enterprise Connectors are a way to ensure that the device is managed by an organization. It is not supposed to be a MFA system but as it is a way to ensure that the device is managed by an organization. However it is not unthinkable that someone would use it as a MFA method since it should ensure that only enrolled devices are able to access the system. To get a managed browser or device, you need to enroll the device in the Google Enterprise program. This involves having an enrollment key and allowing the attached company to manage the device by setting up policies. An example of an authentication flow is shown in image 1. A user supplies their username and password from a managed device to the company server, then the server accepts the request and redirects the user to a page defined within the Google Enterprise Connector settings. The managed device recognizes the URL as an authorized page and initiates the connection with a special header for proof of device trust. The server triggers a request to the Google API to generate a challenge, and forwards the challenge to the managed device. The managed device generates a challenge-response and sends it back to the server. The server then verifies the challenge-response with the Google API and asserts the user is authenticated.
Before diving into the vulnerabilities, I will explain how the lab was set up. If you do not want to know how the lab is set up, skip to the The vulnerabilities section.
Lab setup
To control the circumstances, I had to set up my own infrastructure, replicate and isolate as much as possible. I signed up for a trial period for Google Enterprise, that grants you 300 USD in Google cloud credits and 7 days of Enterprise access. The requirement for the trial is your own domain, hence the haphazard registration of capturethetgc.win.
After registering your domain in the enterprise program, you need to set up the cloud environment. In Google Cloud, set up a service account with a reasonable amount of access. Then go over to Google Cloud Enterprise, and edit the Chrome Enterprise connectors settings. Set the access to enable users to use enterprise connectors as shown in image 2.
Then go over to the devices page, navigate to Chrome (or Chromebook if you want to), and connectors.
Set up a new connector by clicking on NEW PROVIDER CONFIGURATION
.
This is shown in image 3.
It really does not matter what provider you choose. I tested them all and it made no difference for this vulnerability. Enter your service account that will be authorized to issue and verify the challenge and challenge-responses. In addition, you need to set the URL, which will add to the scope for challenge-responses. This is important for later, there is an interesting error here. In image 4 I set up an Okta connector.
After setting up your connector, generate an Enrollment Token
that will be used to enroll the browser or Chromebook.
I set up a Windows 10 VM to use as a managed device in order to isolate whatever happens with the machine and roll back if needed.
On the Windows machine, I enrolled Google Chrome using Googles own guide.
The Google managed browser is shown in image 5.
The managed browser or device will respond to the challenges and generate a challenge-response.
I did not have the time to go in depth about how the challenge-responses are generated, but that can be for future work.
With a device to respond to challenges and the connector configuration set up, we need to request and validate challenges.
I could not find any good documentation on how to set this up in the time frame given, therefore I opted to create this myself and mold it as a normal CTF challenge structure.
I created two Python scripts, pasted below in the Code section.
In short, one script is responsible for requesting challenges from https://verifiedaccess.googleapis.com/v2/challenge:generate, and the other script verifies the challengeResponse
at https://verifiedaccess.googleapis.com/v2/challenge:verify.
I will be explaining the Python scripts in the next section.
The vulnerabilities
Before describing the vulnerability I will briefly explain the requirements for the Google Enterprise challenge flow. The three components are:
- A backend that can request and submit challenges to Google APIs
- A managed browser or device that will respond to challenges
- A connector that will respond to our URL
When setting up the connector, we had to set the URL we wanted the device to verify its access against.
When the browser visits the URL, it automatically adds the header X-Device-Trust
.
The device trusts this URL and is now requesting a challenge.
The backend written in Flask, presented in the Code section reacts to all requests with that header by requesting a challenge with a Google API Token.
With all of this setup, I start up the server and request the page from the managed browser.
The browser recognizes the URL as an authorized page and initiates the connection with the X-Device-Trust
header.
The Python app then requests a challenge from googleapis.com and forwards it to the managed browser through an X-Verified-Access-Challenge
header.
The browser generates the response and replies with the challenge-responses in an X-Verified-Access-Challenge-Response
header.
The flask application will verify the challenge-response and return the JSON blob from the Google API verification.
The response from verifying a challenge-response at Google API is presented in image 6.
Vulnerability 1 - The never ending token
The obvious vulnerability that I was exploiting was the fact that the challenge-response was valid for more than one minute as stated in the documentation. The challenge-response was valid for days and was seemingly never invalidated, leading to us reusing the same challenge-response over the span of 56 hours. When checking the output from verifying a challenge-response at Google APIs, I could not find any timestamp stating when the associated challenge was requested.
Vulnerability 2 - Your name is your key
The last vulnerability is something we noticed during the setup of the connector. When setting up the connector, you need to set the URL that the device will trust and a service account that will validate the challenges. We attempted to add a service account of a different enterprise and generate challenges for that enterprise. Our backend could not validate challenge-response for that enterprise domain but could generate valid challenges for that domain. This means that the challenge-response can be generated across organizations and cross domains, without access to the service account credentials. The service account name is not sensitive information, with this vulnerability, it suddenly becomes the key to generate challenges for an organization. The attacker can not validate the challenge-response in their own environment as they do not have access to the service account credentials. This means that if a threat actor gains access to a service account name, they can generate challenge-responses for that organization indefinitely. The conceptual flow of this vulnerability is shown in image 7.
Vulnerability 3 - Scrambled Challenges? We do not care
There is an observable pattern in how the challenge-response is built.
The challenge always starts with the string CkEKFkVudGVycHJpc2VLZXlDaGFsbGVuZ2USI
, that roughly base64 decodes to EnterpriseKeyChallenge
with some extra bytes.
After the initial string in the challenge, the actual challenge is passed in the base64 encoded string.
The challenge-response is built in a similar way; the initial string is Cq8PCsYC
followed by the entire initial challenge string in base64 encoded format; giving it the following format; Cq8PCsYC
+ base64(Challenge)
+ base64(ChallengeResponse)
.
The byte offset for the challenge is 8 bytes, and the offset for the challenge-response is 443 bytes.
It turns out the first 91 characters of the challenge need to originate from a valid challenge, but the rest of the challenge is not validated.
The deconstructed challenge-response is shown in image 8.
Image 9 shows a different challenge injected in challenge-response from image 8. Image 10 shows a mangled challenge in the challenge-response from image 8. Both challenge-responses are verified as valid challenge-responses by the Google API.
This vulnerability is not the worst, it just allows an attacker to mangle challenge-responses, and avoid some detection. But this just shows that the challenge-response is not nested in a challenge. The conceptual flow of this vulnerability is shown in image 11.
Bonus Vulnerability 4 - Token replay attacks
As Vulnerability 1 states challenge-responses are never invalidated, and my mock backend also included a logic flaw.
The mock backend responds to requests based on the header, meaning it is not nesting challenge requests and responses.
This vulnerability is not directly associated with Google APIs as the API only responds if it is a valid challenge-response.
The vulnerability is affecting systems designed to blindly react to requests in a similar fashion.
An attacker with a valid response could issue the X-Verified-Access-Challenge-Response
as the initial request and verify their access without issuing a challenge to the attacker.
This can be classified as a token replay attack.
Furthermore, if the attacker initiates an authentication flow with the X-Device-Trust
header, my mock backend would not have nested challenge and challenge-responses.
Meaning if you request a challenge and respond with a different challenge-response, the backend would just ignore it and verify the challenge-response.
If you use Google Managed Devices as a form of authentication, I recommend you check your own server for this vulnerability.
A concept of the backend logic flaw is shown in image 12.
Disclosure
These vulnerabilities were disclosed to Google through their Google Bug Bounty program in November 2024.
They have verified the vulnerabilities and responded that they have patched the vulnerabilities.
No bug bounty was awarded for the vulnerabilities, but I was added to Google’s Honorable Mentions list.
Since the vulnerabilities were found in a trial period, I am unable to verify whether the vulnerabilities have been mitigated.
I have not been able to verify the vulnerabilities in the v1
version of the API due to errors and missing documentation but urged Google to look into it as well.
Final Thoughts
This was a fun research project that I did not expect to happen. The vulnerabilities exploit logic flaws and showcase the importance of session management and challenge-response invalidation. The vulnerabilities only affect Google Enterprise customers and are not accessible to the general public as the price to enter the program is quite high. I spent 300 dollars of Google Credits within 3 days just on challenge validation, which is the entire budget for the trial period. Setting up an isolated lab environment is crucial as it uncovered other vulnerabilities that I could not have found otherwise.
All of this was discovered accidentally and sparked an impromptu research session over the weekend. The research into Google Enterprise Connectors was a bit rushed and I did not have time to fully understand the API as I only had three days to finish the research.
Probably the most important lesson learned is to not compromise your Google API key in a bug bounty report. That would be awkward if anyone did that… 🤦🏽♂️
Code
Web Application and Verify Script Dependencies
|
|
In order to use the script, add a service key to a local file acc.json
and run Flask with the command python3 app.py
.
Python Web Application Frontend
|
|
Python Verification Script verify.py
|
|