2017-03-30 00:03:04 +00:00
|
|
|
import logging
|
2019-11-10 23:12:35 +00:00
|
|
|
from os.path import isfile
|
2017-10-25 02:08:19 +00:00
|
|
|
# from traceback import format_exc
|
2017-03-27 21:09:36 +00:00
|
|
|
from gevent.lock import BoundedSemaphore
|
2017-04-14 21:32:39 +00:00
|
|
|
from gevent.socket import gethostbyname
|
2016-12-15 07:28:00 +00:00
|
|
|
from functools import wraps
|
2016-08-12 06:36:38 +00:00
|
|
|
from cme.logger import CMEAdapter
|
2020-05-10 18:16:34 +00:00
|
|
|
from cme.context import Context
|
2016-08-02 04:06:17 +00:00
|
|
|
|
|
|
|
sem = BoundedSemaphore(1)
|
|
|
|
global_failed_logins = 0
|
2016-08-02 14:49:30 +00:00
|
|
|
user_failed_logins = {}
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2017-10-25 02:08:19 +00:00
|
|
|
|
2016-08-12 06:36:38 +00:00
|
|
|
def requires_admin(func):
|
|
|
|
def _decorator(self, *args, **kwargs):
|
|
|
|
if self.admin_privs is False: return
|
|
|
|
return func(self, *args, **kwargs)
|
|
|
|
return wraps(func)(_decorator)
|
|
|
|
|
2017-10-25 02:08:19 +00:00
|
|
|
|
2017-03-27 21:09:36 +00:00
|
|
|
class connection(object):
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def __init__(self, args, db, host):
|
2016-05-16 23:48:31 +00:00
|
|
|
self.args = args
|
|
|
|
self.db = db
|
2017-04-30 18:54:35 +00:00
|
|
|
self.hostname = host
|
2016-08-12 06:36:38 +00:00
|
|
|
self.conn = None
|
2016-12-15 07:28:00 +00:00
|
|
|
self.admin_privs = False
|
2016-08-12 06:36:38 +00:00
|
|
|
self.logger = None
|
2020-05-03 18:30:41 +00:00
|
|
|
self.password = ''
|
|
|
|
self.username = ''
|
|
|
|
self.kerberos = True if self.args.kerberos else False
|
2020-05-04 17:22:10 +00:00
|
|
|
self.aesKey = None if not self.args.aesKey else self.args.aesKey
|
|
|
|
self.kdcHost = None if not self.args.kdcHost else self.args.kdcHost
|
2016-08-02 04:06:17 +00:00
|
|
|
self.failed_logins = 0
|
2016-12-15 07:28:00 +00:00
|
|
|
self.local_ip = None
|
2016-08-12 06:36:38 +00:00
|
|
|
|
2017-04-14 21:26:17 +00:00
|
|
|
try:
|
2017-05-03 00:52:16 +00:00
|
|
|
self.host = gethostbyname(self.hostname)
|
2020-05-03 18:30:41 +00:00
|
|
|
if self.args.kerberos:
|
|
|
|
self.host = self.hostname
|
2017-04-14 21:26:17 +00:00
|
|
|
except Exception as e:
|
2017-05-03 00:52:16 +00:00
|
|
|
logging.debug('Error resolving hostname {}: {}'.format(self.hostname, e))
|
2017-04-14 21:26:17 +00:00
|
|
|
return
|
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
self.proto_flow()
|
2016-08-12 06:36:38 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
@staticmethod
|
|
|
|
def proto_args(std_parser, module_parser):
|
|
|
|
return
|
2016-08-02 04:06:17 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def proto_logger(self):
|
|
|
|
pass
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def enum_host_info(self):
|
|
|
|
return
|
2016-08-12 06:36:38 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def print_host_info(info):
|
|
|
|
return
|
2016-08-12 06:36:38 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def create_conn_obj(self):
|
|
|
|
return
|
2016-08-12 06:36:38 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def check_if_admin(self):
|
|
|
|
return
|
2016-09-12 06:52:50 +00:00
|
|
|
|
2020-05-03 18:30:41 +00:00
|
|
|
def kerberos_login(self):
|
|
|
|
return
|
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def plaintext_login(self, domain, username, password):
|
|
|
|
return
|
2016-09-12 06:52:50 +00:00
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def hash_login(self, domain, username, ntlm_hash):
|
|
|
|
return
|
|
|
|
|
|
|
|
def proto_flow(self):
|
|
|
|
if self.create_conn_obj():
|
|
|
|
self.enum_host_info()
|
|
|
|
self.proto_logger()
|
|
|
|
self.print_host_info()
|
2020-08-11 11:39:16 +00:00
|
|
|
self.login()
|
|
|
|
if hasattr(self.args, 'module') and self.args.module:
|
|
|
|
self.call_modules()
|
|
|
|
else:
|
|
|
|
self.call_cmd_args()
|
2017-04-05 15:07:00 +00:00
|
|
|
|
|
|
|
def call_cmd_args(self):
|
2019-11-10 23:12:35 +00:00
|
|
|
for k, v in vars(self.args).items():
|
2017-04-05 15:07:00 +00:00
|
|
|
if hasattr(self, k) and hasattr(getattr(self, k), '__call__'):
|
|
|
|
if v is not False and v is not None:
|
|
|
|
logging.debug('Calling {}()'.format(k))
|
|
|
|
getattr(self, k)()
|
2016-05-16 23:48:31 +00:00
|
|
|
|
2017-05-03 00:52:16 +00:00
|
|
|
def call_modules(self):
|
|
|
|
module_logger = CMEAdapter(extra={
|
|
|
|
'module': self.module.name.upper(),
|
|
|
|
'host': self.host,
|
|
|
|
'port': self.args.port,
|
|
|
|
'hostname': self.hostname
|
|
|
|
})
|
|
|
|
|
|
|
|
context = Context(self.db, module_logger, self.args)
|
|
|
|
context.localip = self.local_ip
|
|
|
|
|
|
|
|
if hasattr(self.module, 'on_request') or hasattr(self.module, 'has_response'):
|
|
|
|
self.server.connection = self
|
|
|
|
self.server.context.localip = self.local_ip
|
|
|
|
|
|
|
|
if hasattr(self.module, 'on_login'):
|
|
|
|
self.module.on_login(context, self)
|
|
|
|
|
|
|
|
if self.admin_privs and hasattr(self.module, 'on_admin_login'):
|
|
|
|
self.module.on_admin_login(context, self)
|
|
|
|
|
|
|
|
if (not hasattr(self.module, 'on_request') and not hasattr(self.module, 'has_response')) and hasattr(self.module, 'on_shutdown'):
|
|
|
|
self.module.on_shutdown(context, self)
|
|
|
|
|
2016-12-15 07:28:00 +00:00
|
|
|
def inc_failed_login(self, username):
|
|
|
|
global global_failed_logins
|
|
|
|
global user_failed_logins
|
|
|
|
|
|
|
|
if username not in user_failed_logins.keys():
|
|
|
|
user_failed_logins[username] = 0
|
|
|
|
|
|
|
|
user_failed_logins[username] += 1
|
|
|
|
global_failed_logins += 1
|
|
|
|
self.failed_logins += 1
|
|
|
|
|
2016-08-02 14:49:30 +00:00
|
|
|
def over_fail_limit(self, username):
|
2016-08-02 04:06:17 +00:00
|
|
|
global global_failed_logins
|
2016-08-02 14:49:30 +00:00
|
|
|
global user_failed_logins
|
2016-08-02 04:06:17 +00:00
|
|
|
|
|
|
|
if global_failed_logins == self.args.gfail_limit: return True
|
2016-12-15 07:28:00 +00:00
|
|
|
|
2016-08-02 04:06:17 +00:00
|
|
|
if self.failed_logins == self.args.fail_limit: return True
|
2016-12-15 07:28:00 +00:00
|
|
|
|
2016-08-02 14:49:30 +00:00
|
|
|
if username in user_failed_logins.keys():
|
|
|
|
if self.args.ufail_limit == user_failed_logins[username]: return True
|
2016-08-02 04:06:17 +00:00
|
|
|
|
|
|
|
return False
|
|
|
|
|
2016-05-16 23:48:31 +00:00
|
|
|
def login(self):
|
2020-05-03 18:30:41 +00:00
|
|
|
if self.args.kerberos:
|
2020-05-04 17:22:10 +00:00
|
|
|
if self.kerberos_login(self.aesKey, self.kdcHost): return True
|
2020-05-03 18:30:41 +00:00
|
|
|
else:
|
|
|
|
for cred_id in self.args.cred_id:
|
|
|
|
with sem:
|
|
|
|
if cred_id.lower() == 'all':
|
|
|
|
creds = self.db.get_credentials()
|
|
|
|
else:
|
|
|
|
creds = self.db.get_credentials(filterTerm=int(cred_id))
|
|
|
|
|
|
|
|
for cred in creds:
|
|
|
|
logging.debug(cred)
|
|
|
|
try:
|
|
|
|
c_id, domain, username, password, credtype, pillaged_from = cred
|
|
|
|
|
|
|
|
if credtype and password:
|
|
|
|
|
|
|
|
if not domain: domain = self.domain
|
|
|
|
|
|
|
|
if self.args.local_auth:
|
|
|
|
domain = self.domain
|
|
|
|
elif self.args.domain:
|
|
|
|
domain = self.args.domain
|
|
|
|
|
|
|
|
if credtype == 'hash' and not self.over_fail_limit(username):
|
|
|
|
if self.hash_login(domain, username, password): return True
|
|
|
|
|
|
|
|
elif credtype == 'plaintext' and not self.over_fail_limit(username):
|
|
|
|
if self.plaintext_login(domain, username, password): return True
|
|
|
|
|
|
|
|
except IndexError:
|
|
|
|
self.logger.error("Invalid database credential ID!")
|
|
|
|
|
|
|
|
for user in self.args.username:
|
|
|
|
if not isinstance(user, str) and isfile(user.name):
|
|
|
|
for usr in user:
|
|
|
|
if "\\" in usr:
|
|
|
|
tmp = usr
|
|
|
|
usr = tmp.split('\\')[1].strip()
|
|
|
|
self.domain = tmp.split('\\')[0]
|
2020-05-09 11:59:53 +00:00
|
|
|
if hasattr(self.args, 'hash') and self.args.hash:
|
2020-05-03 18:30:41 +00:00
|
|
|
with sem:
|
|
|
|
for ntlm_hash in self.args.hash:
|
|
|
|
if isinstance(ntlm_hash, str):
|
2020-04-30 14:06:57 +00:00
|
|
|
if not self.over_fail_limit(usr.strip()):
|
2020-05-03 18:30:41 +00:00
|
|
|
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return True
|
|
|
|
|
|
|
|
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == False:
|
|
|
|
for f_hash in ntlm_hash:
|
|
|
|
if not self.over_fail_limit(usr.strip()):
|
|
|
|
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True
|
|
|
|
ntlm_hash.seek(0)
|
|
|
|
|
|
|
|
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == True:
|
|
|
|
user.seek(0)
|
2020-07-28 14:16:06 +00:00
|
|
|
for usr, f_hash in zip(user, ntlm_hash):
|
2020-05-03 18:30:41 +00:00
|
|
|
if not self.over_fail_limit(usr.strip()):
|
2020-07-28 14:16:06 +00:00
|
|
|
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True
|
2020-05-03 18:30:41 +00:00
|
|
|
|
|
|
|
elif self.args.password:
|
|
|
|
with sem:
|
|
|
|
for password in self.args.password:
|
|
|
|
if isinstance(password, str):
|
2020-04-30 14:06:57 +00:00
|
|
|
if not self.over_fail_limit(usr.strip()):
|
2020-05-09 11:59:53 +00:00
|
|
|
if hasattr(self.args, 'domain'):
|
|
|
|
if self.plaintext_login(self.domain, usr.strip(), password): return True
|
|
|
|
else:
|
|
|
|
if self.plaintext_login(usr.strip(), password): return True
|
2020-05-03 18:30:41 +00:00
|
|
|
|
|
|
|
elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == False:
|
|
|
|
for f_pass in password:
|
|
|
|
if not self.over_fail_limit(usr.strip()):
|
2020-05-09 11:59:53 +00:00
|
|
|
if hasattr(self.args, 'domain'):
|
|
|
|
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True
|
|
|
|
else:
|
|
|
|
if self.plaintext_login(usr.strip(), f_pass.strip()): return True
|
2020-05-03 18:30:41 +00:00
|
|
|
password.seek(0)
|
|
|
|
|
|
|
|
elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == True:
|
|
|
|
user.seek(0)
|
|
|
|
for usr, f_pass in zip(user, password):
|
|
|
|
if not self.over_fail_limit(usr.strip()):
|
2020-05-09 11:59:53 +00:00
|
|
|
if hasattr(self.args, 'domain'):
|
|
|
|
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True
|
|
|
|
else:
|
|
|
|
if self.plaintext_login(usr.strip(), f_pass.strip()): return True
|
2020-06-20 22:43:34 +00:00
|
|
|
user.seek(0) # added june 2020, may break everything but solve this issue cme smb file -u file -p file
|
2020-05-03 18:30:41 +00:00
|
|
|
elif isinstance(user, str):
|
|
|
|
if hasattr(self.args, 'hash') and self.args.hash:
|
|
|
|
with sem:
|
|
|
|
for ntlm_hash in self.args.hash:
|
|
|
|
if isinstance(ntlm_hash, str):
|
2016-08-02 14:49:30 +00:00
|
|
|
if not self.over_fail_limit(user):
|
2020-05-03 18:30:41 +00:00
|
|
|
if self.hash_login(self.domain, user, ntlm_hash): return True
|
|
|
|
|
|
|
|
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name):
|
|
|
|
for f_hash in ntlm_hash:
|
|
|
|
if not self.over_fail_limit(user):
|
|
|
|
if self.hash_login(self.domain, user, f_hash.strip()): return True
|
|
|
|
ntlm_hash.seek(0)
|
|
|
|
|
|
|
|
elif self.args.password:
|
|
|
|
with sem:
|
|
|
|
for password in self.args.password:
|
|
|
|
if isinstance(password, str):
|
2016-08-02 14:49:30 +00:00
|
|
|
if not self.over_fail_limit(user):
|
2017-07-10 05:44:58 +00:00
|
|
|
if hasattr(self.args, 'domain'):
|
2020-05-03 18:30:41 +00:00
|
|
|
if self.plaintext_login(self.domain, user, password): return True
|
2017-07-10 05:44:58 +00:00
|
|
|
else:
|
2020-05-03 18:30:41 +00:00
|
|
|
if self.plaintext_login(user, password): return True
|
|
|
|
|
|
|
|
elif not isinstance(password, str) and isfile(password.name):
|
|
|
|
for f_pass in password:
|
|
|
|
if not self.over_fail_limit(user):
|
|
|
|
if hasattr(self.args, 'domain'):
|
|
|
|
if self.plaintext_login(self.domain, user, f_pass.strip()): return True
|
|
|
|
else:
|
|
|
|
if self.plaintext_login(user, f_pass.strip()): return True
|
|
|
|
password.seek(0)
|