-
Notifications
You must be signed in to change notification settings - Fork 1
/
calc.py
144 lines (109 loc) · 6.81 KB
/
calc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
BannedPasswords = ['TEST PASSWORD HERE'] #Azures password list is hidden but was dumped by Synacktiv
with open("eipp-global-bpl.txt") as file:
for line in file:
line = line.strip() #or some other preprocessing
BannedPasswords.append(line) #storing everything in memory!
with open("CustomList.txt") as file: # option to add a custom List here also
for line in file:
line = line.strip()
BannedPasswords.append(line) #storing everything in memory!
AllowedLowerCase = "abcdefghijklmnopqrstuvwxyz"
AllowedUpperCase = "ABCDEFGHIJKLMNOPQRSTUVXYWZ"
AllowedSymbols = " @#$%^&*-_!+=[]{}|\:',.?/`~();<>\""
AllowedNumbers = "0123456789"
AllowedChars = AllowedLowerCase + AllowedUpperCase + AllowedSymbols + AllowedNumbers
#https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sspr-policy#microsoft-entra-password-policies
def generate_edit_distance_one(s): # I could also use regex here
splits = [(s[:i], s[i:]) for i in range(len(s) + 1)]
deletes = [L + R[1:] for L, R in splits if R]
substitutes = [L + c + R[1:] for L, R in splits if R for c in (AllowedLowerCase + AllowedSymbols + AllowedNumbers)]
inserts = [L + c + R for L, R in splits for c in (AllowedLowerCase + AllowedSymbols + AllowedNumbers)]
return set(deletes + substitutes + inserts)
print("Substring matching is used on the normalized password to check for the user's first and last name as well as the tenant name. Tenant name matching isn't done when validating passwords on an AD DS domain controller for on-premises hybrid scenarios.")
FirstName = input("First Name?: ")
LastName = input("Last Name?: ")
TenantName = input("Tenant name? (Note: Other Domains associated to the tenant are allowed just not the one used for UPN): ")
#Error Handle
ContainsLowerCase = 0
ContainsUpperCase = 0
ContainsSymbol = 0
invalidflag = 0
while True:
inp = input("password to test: ")
#Password Requirement Check
if len(inp) <= 256 and len(inp) >= 8:
for i in inp:
if i in AllowedLowerCase:
ContainsLowerCase = 1
if i in AllowedUpperCase:
ContainsUpperCase = 1
if i in AllowedNumbers:
ContainsNumber = 1
if i in AllowedSymbols:
ContainsSymbol = 1
if i not in AllowedChars: #filter out if invalid characters AFTER length check
invalidflag = 1
if (ContainsNumber + ContainsLowerCase + ContainsUpperCase + ContainsSymbol) < 3 or invalidflag ==1:
print("invalid Password. Does not meet requirements.")
print("Contains Number: " + str(ContainsNumber))
print("Contains LowerCase: " + str(ContainsLowerCase)) #need to replace with fstring
print("Contains UpperCase: " + str(ContainsUpperCase))
print("Contains Symbol: " + str(ContainsSymbol))
print("invalid character: " + str(invalidflag))
break;
else:
#Password Protection Begins
#Step 1: Normalization
normalized_inp = inp.lower()
# Substitution/leetspeak, we have to do this AFTER checking the contain number check
#These are the substitutions listed in the Azure documentation
normalized_inp = normalized_inp.replace("0", "o")
normalized_inp = normalized_inp.replace("1","l")
normalized_inp = normalized_inp.replace("$","s")
normalized_inp = normalized_inp.replace("@","a")
#Provided by synacktiv.com https://www.synacktiv.com/publications/entra-id-banned-password-lists-password-spraying-optimizations-and-defenses
normalized_inp = normalized_inp.replace("5", "s")
normalized_inp = normalized_inp.replace("|","l")
normalized_inp = normalized_inp.replace("i","l")
normalized_inp = normalized_inp.replace("2","z")
normalized_inp = normalized_inp.replace("3","e")
normalized_inp = normalized_inp.replace("!","l")
#Step 2: Check if password is considered Banned (min length of 4)
#Fuzzy Match: edit distance of 1 comparison. We can do this is use Levenshtein Distance or create a fuzz set
temp = inp
score = 0
for word in BannedPasswords:
if len(word) >4:
SetToCheck = generate_edit_distance_one(word) #Only generate fuzzer if length is at least 4
#print(SetToCheck)
if (word in normalized_inp) or (word in inp):
#print("initial score: " + str(score))
score = score + temp.count(word) #add based of count of banned password
temp = temp.replace(word,'') #remove banned word to prevent further fuzzy match
#print("Temp: " + temp)
print("password directly contains banned phrase: " + word)
#Apply Normalisation to Temp
temp = temp.replace("0", "o").replace("1","l").replace("$","s").replace("@","a").replace("5", "s").lower()
for i in SetToCheck:
if i in temp:
print("password contains banned word " + word + " Fuzz Matcher: " + i)
# print("Temp: " + temp)
score = score + temp.count(i)
temp = temp.replace(i,'') #remove the banned word so we don't count it
#Count Each Remaining Character AFTER we exited loop. do not overlap character count from banned passwords
score = int(score) + int(len(temp))
# Substring Matching (On Specific Terms) https://learn.microsoft.com/en-us/entra/identity/authentication/concept-password-ban-bad#substring-matching-on-specific-terms
#TenantName/userName. The documentation/testing seems to suggest that distance is not affected here.
#This Check is applied AFTER Normalisation
if (FirstName in normalized_inp and len(FirstName)>=4) or (LastName in normalized_inp and len(LastName)>=4) or (TenantName in normalized_inp and len(TenantName)>=4):
#print(inp)
print("invalid Password, contains first/last/tenantName")
break; #Documentation Example suggests its rejected regardless of the score
if int(score) >= 5:
print("Valid Password")
else:
print("Invalid Password")
print("normalised Password: " + str(normalized_inp))
print("Score: " + str(score))
else:
print("Password Does not meet length requirement - Try again")