Skip to content
This repository was archived by the owner on Dec 6, 2023. It is now read-only.

Commit 6876761

Browse files
author
byt3bl33d3r
committed
Added the --ufail-limit flag to limit failed login attempts per username
1 parent 698f147 commit 6876761

File tree

4 files changed

+43
-21
lines changed

4 files changed

+43
-21
lines changed

cme/connection.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
sem = BoundedSemaphore(1)
1414
global_failed_logins = 0
15+
user_failed_logins = {}
1516

1617
class Connection:
1718

@@ -35,11 +36,14 @@ def __init__(self, args, db, target, server_name, domain, conn, logger, cmeserve
3536

3637
self.login()
3738

38-
def over_fail_limit(self):
39+
def over_fail_limit(self, username):
3940
global global_failed_logins
41+
global user_failed_logins
4042

4143
if global_failed_logins == self.args.gfail_limit: return True
4244
if self.failed_logins == self.args.fail_limit: return True
45+
if username in user_failed_logins.keys():
46+
if self.args.ufail_limit == user_failed_logins[username]: return True
4347

4448
return False
4549

@@ -124,15 +128,22 @@ def plaintext_login(self, domain, username, password):
124128
return True
125129
except SessionError as e:
126130
error, desc = e.getErrorString()
127-
if error == 'STATUS_LOGON_FAILURE':
128-
global global_failed_logins; global_failed_logins += 1
129-
self.failed_logins += 1
130-
131131
self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'),
132132
username.decode('utf-8'),
133133
password.decode('utf-8'),
134134
error,
135135
'({})'.format(desc) if self.args.verbose else ''))
136+
if error == 'STATUS_LOGON_FAILURE':
137+
global global_failed_logins
138+
global user_failed_logins
139+
140+
if username not in user_failed_logins.keys():
141+
user_failed_logins[username] = 0
142+
143+
user_failed_logins[username] += 1
144+
global_failed_logins += 1
145+
self.failed_logins += 1
146+
136147
return False
137148

138149
def hash_login(self, domain, username, ntlm_hash):
@@ -173,15 +184,22 @@ def hash_login(self, domain, username, ntlm_hash):
173184
return True
174185
except SessionError as e:
175186
error, desc = e.getErrorString()
176-
if error == 'STATUS_LOGON_FAILURE':
177-
global global_failed_logins; global_failed_logins += 1
178-
self.failed_logins += 1
179-
180187
self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'),
181188
username.decode('utf-8'),
182189
ntlm_hash,
183190
error,
184191
'({})'.format(desc) if self.args.verbose else ''))
192+
if error == 'STATUS_LOGON_FAILURE':
193+
global global_failed_logins
194+
global user_failed_logins
195+
196+
if username not in user_failed_logins.keys():
197+
user_failed_logins[username] = 0
198+
199+
user_failed_logins[username] += 1
200+
global_failed_logins += 1
201+
self.failed_logins += 1
202+
185203
return False
186204

187205
def login(self):
@@ -209,25 +227,25 @@ def login(self):
209227
with sem:
210228
for ntlm_hash in self.args.hash:
211229
if type(ntlm_hash) is not file:
212-
if not self.over_fail_limit():
230+
if not self.over_fail_limit(usr.strip()):
213231
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return
214232

215233
elif type(ntlm_hash) is file:
216234
for f_hash in ntlm_hash:
217-
if not self.over_fail_limit():
235+
if not self.over_fail_limit(usr.strip()):
218236
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return
219237
ntlm_hash.seek(0)
220238

221239
elif self.args.password:
222240
with sem:
223241
for password in self.args.password:
224242
if type(password) is not file:
225-
if not self.over_fail_limit():
243+
if not self.over_fail_limit(usr.strip()):
226244
if self.plaintext_login(self.domain, usr.strip(), password): return
227245

228246
elif type(password) is file:
229247
for f_pass in password:
230-
if not self.over_fail_limit():
248+
if not self.over_fail_limit(usr.strip()):
231249
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return
232250
password.seek(0)
233251

@@ -236,25 +254,25 @@ def login(self):
236254
with sem:
237255
for ntlm_hash in self.args.hash:
238256
if type(ntlm_hash) is not file:
239-
if not self.over_fail_limit():
257+
if not self.over_fail_limit(user):
240258
if self.hash_login(self.domain, user, ntlm_hash): return
241259

242260
elif type(ntlm_hash) is file:
243261
for f_hash in ntlm_hash:
244-
if not self.over_fail_limit():
262+
if not self.over_fail_limit(user):
245263
if self.hash_login(self.domain, user, f_hash.strip()): return
246264
ntlm_hash.seek(0)
247265

248266
elif self.args.password:
249267
with sem:
250268
for password in self.args.password:
251269
if type(password) is not file:
252-
if not self.over_fail_limit():
270+
if not self.over_fail_limit(user):
253271
if self.plaintext_login(self.domain, user, password): return
254272

255273
elif type(password) is file:
256274
for f_pass in password:
257-
if not self.over_fail_limit():
275+
if not self.over_fail_limit(user):
258276
if self.plaintext_login(self.domain, user, f_pass.strip()): return
259277
password.seek(0)
260278

cme/crackmapexec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ def main():
7575
parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)')
7676
parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output")
7777
fail_group = parser.add_mutually_exclusive_group()
78-
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='Max number of global failed login attemptes')
79-
fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='Max number of failed login attemptes per host')
78+
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='Max number of global failed login attempts')
79+
fail_group.add_argument("--ufail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per username')
80+
fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per host')
8081

8182
rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
8283
rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')

cme/enum/users.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ def enum(self):
5959
logging.debug('StringBinding %s'%stringbinding)
6060
rpctransport = transport.DCERPCTransportFactory(stringbinding)
6161
rpctransport.set_dport(self.__port)
62-
#rpctransport.setRemoteHost(self.__addr)
6362

63+
if hasattr(rpctransport, setRemoteHost):
64+
rpctransport.setRemoteHost(self.__addr)
6465
if hasattr(rpctransport, 'set_credentials'):
6566
# This method exists only for selected protocol sequences.
6667
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,

cme/execmethods/smbexec.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ def __init__(self, host, protocol, username = '', password = '', domain = '', ha
4242
logging.debug('StringBinding %s'%stringbinding)
4343
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
4444
self.__rpctransport.set_dport(self.__port)
45-
#self.__rpctransport.setRemoteHost(self.__host)
45+
46+
if hasattr(self.__rpctransport, 'setRemoteHost'):
47+
self.__rpctransport.setRemoteHost(self.__host)
4648
if hasattr(self.__rpctransport, 'set_credentials'):
4749
# This method exists only for selected protocol sequences.
4850
self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)

0 commit comments

Comments
 (0)