diff --git a/cme/cli.py b/cme/cli.py index 9528489f4..997027f84 100755 --- a/cme/cli.py +++ b/cme/cli.py @@ -49,7 +49,8 @@ def gen_cli_args(): std_parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='database credential ID(s) to use for authentication') std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="username(s) or file(s) containing usernames") std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="password(s) or file(s) containing passwords") - std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)") + std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication") + std_parser.add_argument("--use-kcache", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)") std_parser.add_argument("--export", metavar="EXPORT", nargs='+', help="Export result into a file, probably buggy") std_parser.add_argument("--aesKey", metavar="AESKEY", nargs='+', help="AES key to use for Kerberos Authentication (128 or 256 bits)") std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter") diff --git a/cme/connection.py b/cme/connection.py index 60be67496..5fe879645 100755 --- a/cme/connection.py +++ b/cme/connection.py @@ -162,120 +162,150 @@ def over_fail_limit(self, username): return False def login(self): - if self.args.kerberos: - if self.kerberos_login(self.domain, self.aesKey, self.kdcHost): return True - 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 isfile(user): - with open(user, 'r') as user_file: - for usr in user_file: - if "\\" in usr: - tmp = usr - usr = tmp.split('\\')[1].strip() - self.domain = tmp.split('\\')[0] - if hasattr(self.args, 'hash') and self.args.hash: - with sem: - for ntlm_hash in self.args.hash: - if isfile(ntlm_hash): - with open(ntlm_hash, 'r') as ntlm_hash_file: - if self.args.no_bruteforce == False: - for f_hash in ntlm_hash_file: - if not self.over_fail_limit(usr.strip()): - if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True - elif self.args.no_bruteforce == True: - user_file.seek(0) # HACK: this should really not be in the usr for loop - for usr, f_hash in zip(user_file, ntlm_hash_file): - if not self.over_fail_limit(usr.strip()): - if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True - else: # ntlm_hash is a string - if not self.over_fail_limit(usr.strip()): - if self.hash_login(self.domain, usr.strip(), ntlm_hash.strip()): return True - - elif self.args.password: - with sem: - for password in self.args.password: - if isfile(password): - with open(password, 'r') as password_file: - if self.args.no_bruteforce == False: - for f_pass in password_file: - if not self.over_fail_limit(usr.strip()): - 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 - elif self.args.no_bruteforce == True: - user_file.seek(0) # HACK: this should really not be in the usr for loop - for usr, f_pass in zip(user_file, password_file): - if not self.over_fail_limit(usr.strip()): - 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 - else: # password is a string - if not self.over_fail_limit(usr.strip()): - 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 - - else: # user is a string - if hasattr(self.args, 'hash') and self.args.hash: - with sem: - for ntlm_hash in self.args.hash: - if isfile(ntlm_hash): - with open(ntlm_hash, 'r') as ntlm_hash_file: - for f_hash in ntlm_hash_file: - if not self.over_fail_limit(user): - if self.hash_login(self.domain, user, f_hash.strip()): return True - else: # ntlm_hash is a string - if not self.over_fail_limit(user): - if self.hash_login(self.domain, user, ntlm_hash.strip()): return True - - elif self.args.password: - with sem: - for password in self.args.password: - if isfile(password): - with open(password, 'r') as password_file: - for f_pass in password_file: - 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 - else: # password is a string - if not self.over_fail_limit(user): - if hasattr(self.args, 'domain'): - if self.plaintext_login(self.domain, user, password): return True - else: - if self.plaintext_login(user, password): return True + 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.args.kerberos: + if self.kerberos_login(domain, username, '', password, '', self.kdcHost, False): return True + elif self.hash_login(domain, username, password): return True + + elif credtype == 'plaintext' and not self.over_fail_limit(username): + if self.args.kerberos: + if self.kerberos_login(domain, username, password, '' , '', self.kdcHost, False): return True + elif self.plaintext_login(domain, username, password): return True + + except IndexError: + self.logger.error("Invalid database credential ID!") + if self.args.use_kcache: + with sem: + if self.kerberos_login(self.domain, '', '', '', '', self.kdcHost, True): return True + for user in self.args.username: + if isfile(user): + with open(user, 'r') as user_file: + for usr in user_file: + if "\\" in usr: + tmp = usr + usr = tmp.split('\\')[1].strip() + self.domain = tmp.split('\\')[0] + if hasattr(self.args, 'hash') and self.args.hash: + with sem: + for ntlm_hash in self.args.hash: + if isfile(ntlm_hash): + with open(ntlm_hash, 'r') as ntlm_hash_file: + if self.args.no_bruteforce == False: + for f_hash in ntlm_hash_file: + if not self.over_fail_limit(usr.strip()): + if self.args.kerberos: + if self.kerberos_login(self.domain, usr.strip(), '', f_hash.strip(), '', self.kdcHost, False): return True + elif self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True + elif self.args.no_bruteforce == True: + user_file.seek(0) # HACK: this should really not be in the usr for loop + for usr, f_hash in zip(user_file, ntlm_hash_file): + if not self.over_fail_limit(usr.strip()): + if self.args.kerberos: + if self.kerberos_login(self.domain, usr.strip(), '', f_hash.strip(), '', self.kdcHost, False): return True + elif self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True + else: # ntlm_hash is a string + if not self.over_fail_limit(usr.strip()): + if self.args.kerberos: + if self.kerberos_login(self.domain, usr.strip(), '', ntlm_hash.strip(), '', self.kdcHost, False): return True + elif self.hash_login(self.domain, usr.strip(), ntlm_hash.strip()): return True + + elif self.args.password: + with sem: + for password in self.args.password: + if isfile(password): + with open(password, 'r') as password_file: + if self.args.no_bruteforce == False: + for f_pass in password_file: + if not self.over_fail_limit(usr.strip()): + if hasattr(self.args, 'domain'): + if self.args.kerberos: + if self.kerberos_login(self.domain, usr.strip(), f_pass.strip(), '', '', self.kdcHost, False): return True + elif self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True + else: + if self.plaintext_login(usr.strip(), f_pass.strip()): return True + elif self.args.no_bruteforce == True: + user_file.seek(0) # HACK: this should really not be in the usr for loop + for usr, f_pass in zip(user_file, password_file): + if not self.over_fail_limit(usr.strip()): + if hasattr(self.args, 'domain'): + if self.args.kerberos: + if self.kerberos_login(self.domain, usr.strip(), f_pass.strip(), '', '', self.kdcHost, False): return True + elif self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True + else: + if self.plaintext_login(usr.strip(), f_pass.strip()): return True + else: # password is a string + if not self.over_fail_limit(usr.strip()): + if hasattr(self.args, 'domain'): + if self.args.kerberos: + if self.kerberos_login(self.domain, usr.strip(), password, '', '', self.kdcHost, False): return True + elif self.plaintext_login(self.domain, usr.strip(), password): return True + else: + if self.plaintext_login(usr.strip(), password): return True + + else: # user is a string + if hasattr(self.args, 'hash') and self.args.hash: + with sem: + for ntlm_hash in self.args.hash: + if isfile(ntlm_hash): + with open(ntlm_hash, 'r') as ntlm_hash_file: + for f_hash in ntlm_hash_file: + if not self.over_fail_limit(user): + if self.args.kerberos: + if self.kerberos_login(self.domain, user, '', ntlm_hash.strip(), '', self.kdcHost, False): return True + elif self.hash_login(self.domain, user, f_hash.strip()): return True + else: # ntlm_hash is a string + if not self.over_fail_limit(user): + if self.args.kerberos: + if self.kerberos_login(self.domain, user, '', ntlm_hash.strip(), '', self.kdcHost, False): return True + elif self.hash_login(self.domain, user, ntlm_hash.strip()): return True + + elif self.args.password: + with sem: + for password in self.args.password: + if isfile(password): + with open(password, 'r') as password_file: + for f_pass in password_file: + if not self.over_fail_limit(user): + if hasattr(self.args, 'domain'): + if self.args.kerberos: + if self.kerberos_login(self.domain, user, f_pass.strip(), '', '', self.kdcHost, False): return True + elif self.plaintext_login(self.domain, user, f_pass.strip()): return True + else: + if self.plaintext_login(user, f_pass.strip()): return True + else: # password is a string + if not self.over_fail_limit(user): + if hasattr(self.args, 'domain'): + if self.args.kerberos: + if self.kerberos_login(self.domain, user, password, '', '', self.kdcHost, False): return True + elif self.plaintext_login(self.domain, user, password): return True + else: + if self.plaintext_login(user, password): return True + + elif self.args.aesKey: + with sem: + for aesKey in self.args.aesKey: + if not self.over_fail_limit(user): + if self.kerberos_login(self.domain, user, '', '', aesKey.strip(), self.kdcHost, False): return True + + diff --git a/cme/protocols/ldap.py b/cme/protocols/ldap.py index 8168688d6..3e752ad7a 100644 --- a/cme/protocols/ldap.py +++ b/cme/protocols/ldap.py @@ -16,7 +16,7 @@ from impacket.smbconnection import SMBConnection, SessionError from impacket.smb import SMB_DIALECT from impacket.dcerpc.v5.samr import UF_ACCOUNTDISABLE, UF_DONT_REQUIRE_PREAUTH, UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION -from impacket.krb5.kerberosv5 import sendReceive, KerberosError, getKerberosTGT, getKerberosTGS +from impacket.krb5.kerberosv5 import sendReceive, KerberosError, getKerberosTGT, getKerberosTGS, SessionKeyDecryptionError from impacket.krb5.types import KerberosTime, Principal from impacket.ldap import ldap as ldap_impacket from impacket.krb5 import constants @@ -34,7 +34,9 @@ "532":"STATUS_PASSWORD_EXPIRED", "773":"STATUS_PASSWORD_MUST_CHANGE", "775":"USER_ACCOUNT_LOCKED", - "50":"LDAP_INSUFFICIENT_ACCESS" + "50":"LDAP_INSUFFICIENT_ACCESS", + "KDC_ERR_CLIENT_REVOKED":"KDC_ERR_CLIENT_REVOKED", + "KDC_ERR_PREAUTH_FAILED":"KDC_ERR_PREAUTH_FAILED" } @@ -237,26 +239,58 @@ def print_host_info(self): #self.logger.info(self.endpoint) return True - def kerberos_login(self, domain, aesKey, kdcHost): + def kerberos_login(self, domain, username, password = '', ntlm_hash = '', aesKey = '', kdcHost = '', useCache = False): + logging.getLogger("impacket").disabled = True + self.username = username + self.password = password + self.domain = domain # Get ldap info (target, targetDomain, baseDN) target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) + lmhash = '' + nthash = '' + self.username = username + #This checks to see if we didn't provide the LM Hash + if ntlm_hash.find(':') != -1: + lmhash, nthash = ntlm_hash.split(':') + self.hash = nthash + else: + nthash = ntlm_hash + self.hash = ntlm_hash + if lmhash: self.lmhash = lmhash + if nthash: self.nthash = nthash + + if self.password == '' and self.args.asreproast: + hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) + if hash_TGT: + self.logger.highlight(u'{}'.format(hash_TGT)) + with open(self.args.asreproast, 'a+') as hash_asreproast: + hash_asreproast.write(hash_TGT + '\n') + return False + + if not all('' == s for s in [self.nthash, password, aesKey]): + kerb_pass = next(s for s in [self.nthash, password, aesKey] if s) + else: + kerb_pass = '' + try: # Connect to LDAP proto = "ldaps" if self.args.gmsa else "ldap" self.ldapConnection = ldap_impacket.LDAPConnection(proto + '://%s' % target, self.baseDN) - self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, - self.aesKey, kdcHost=kdcHost) + self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, + aesKey, kdcHost=kdcHost, useCache=useCache) if self.username == '': self.username = self.get_ldap_username() self.check_if_admin() - # Prepare success credential text - out = u'{}\\{} {}'.format(domain, - self.username, - highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) + out = u'{}\\{}{} {}'.format(domain, + self.username, + # Show what was used between cleartext, nthash, aesKey and ccache + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" if not self.args.gmsa else "636" @@ -264,39 +298,98 @@ def kerberos_login(self, domain, aesKey, kdcHost): if not self.args.local_auth: add_user_bh(self.username, self.domain, self.logger, self.config) + if not self.args.continue_on_success: + return True + except SessionKeyDecryptionError: + # for PRE-AUTH account + self.logger.error(u'{}\\{}{} {}'.format(domain, + self.username, + " account vulnerable to asreproast attack", + ""), + color='yellow') + return False + except SessionError as e: + error, desc = e.getErrorString() + self.logger.error(u'{}\\{}{} {}'.format(self.domain, + self.username, + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + str(error)), + color='magenta' if error in ldap_error_status else 'red') + return False + except KeyError as e: + self.logger.error(u'{}\\{}{} {}'.format(self.domain, + self.username, + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + ''), + color='red') + return False except ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL - # Connect to LDAPS - self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN) - self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, - self.aesKey, kdcHost=kdcHost) - - if self.username == '': - self.username = self.get_ldap_username() + try: + # Connect to LDAPS + self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN) + self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, + aesKey, kdcHost=kdcHost, useCache=useCache) + + if self.username == '': + self.username = self.get_ldap_username() - self.check_if_admin() + self.check_if_admin() - # Prepare success credential text - out = u'{}\\{} {}'.format(domain, - self.username, - highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) + # Prepare success credential text + out = u'{}\\{}{} {}'.format(domain, + self.username, + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) + + if self.username == '': + self.username = self.get_ldap_username() + + self.check_if_admin() + + # Prepare success credential text + out = u'{}\\{} {}'.format(domain, + self.username, + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) + + self.logger.extra['protocol'] = "LDAPS" + self.logger.extra['port'] = "636" + self.logger.success(out) - self.logger.extra['protocol'] = "LDAPS" - self.logger.extra['port'] = "636" - self.logger.success(out) - - if not self.args.local_auth: - add_user_bh(self.username, self.domain, self.logger, self.config) + if not self.args.local_auth: + add_user_bh(self.username, self.domain, self.logger, self.config) + if not self.args.continue_on_success: + return True + except ldap_impacket.LDAPSessionError as e: + errorCode = str(e).split()[-2][:-1] + self.logger.error(u'{}\\{}:{} {}'.format(self.domain, + self.username, + self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, + ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), + color='magenta' if errorCode in ldap_error_status else 'red') + return False + except SessionError as e: + error, desc = e.getErrorString() + self.logger.error(u'{}\\{}{} {}'.format(self.domain, + self.username, + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + str(error)), + color='magenta' if error in ldap_error_status else 'red') + return False else: errorCode = str(e).split()[-2][:-1] - self.logger.error(u'{}\\{}:{} {}'.format(self.domain, + self.logger.error(u'{}\\{}{} {}'.format(self.domain, self.username, - self.password, + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), ldap_error_status[errorCode] if errorCode in ldap_error_status else ''), color='magenta' if errorCode in ldap_error_status else 'red') - - return True + return False def plaintext_login(self, domain, username, password): self.username = username @@ -326,6 +419,7 @@ def plaintext_login(self, domain, username, password): self.username, self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) + self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" if not self.args.gmsa else "636" self.logger.success(out) @@ -355,6 +449,8 @@ def plaintext_login(self, domain, username, password): if not self.args.local_auth: add_user_bh(self.username, self.domain, self.logger, self.config) + if not self.args.continue_on_success: + return True except ldap_impacket.LDAPSessionError as e: errorCode = str(e).split()[-2][:-1] self.logger.error(u'{}\\{}:{} {}'.format(self.domain, @@ -380,6 +476,8 @@ def plaintext_login(self, domain, username, password): def hash_login(self, domain, username, ntlm_hash): + self.logger.extra['protocol'] = "LDAP" + self.logger.extra['port'] = "389" lmhash = '' nthash = '' @@ -446,6 +544,8 @@ def hash_login(self, domain, username, ntlm_hash): if not self.args.local_auth: add_user_bh(self.username, self.domain, self.logger, self.config) + if not self.args.continue_on_success: + return True except ldap_impacket.LDAPSessionError as e: errorCode = str(e).split()[-2][:-1] self.logger.error(u'{}\\{}:{} {}'.format(self.domain, @@ -710,7 +810,6 @@ def kerberoasting(self): self.logger.highlight("No entries found!") elif resp: answers = [] - self.logger.info('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: @@ -761,6 +860,7 @@ def kerberoasting(self): pass if len(answers)>0: + self.logger.info('Total of records returned %d' % len(answers)) TGT = KerberosAttacks(self).getTGT_kerberoasting() dejavue = [] for SPN, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation in answers: diff --git a/cme/protocols/mssql.py b/cme/protocols/mssql.py index 44003cbda..603f50467 100755 --- a/cme/protocols/mssql.py +++ b/cme/protocols/mssql.py @@ -24,6 +24,7 @@ def __init__(self, args, db, host): self.server_os = None self.hash = None self.os_arch = None + self.nthash = '' connection.__init__(self, args, db, host) @@ -158,6 +159,60 @@ def check_if_admin(self): return True + def kerberos_login(self, domain, username, password = '', ntlm_hash = '', aesKey = '', kdcHost = '', useCache = False): + try: + self.conn.disconnect() + except: + pass + self.create_conn_obj() + logging.getLogger("impacket").disabled = True + + nthash = '' + hashes = None + if ntlm_hash != '': + if ntlm_hash.find(':') != -1: + hashes = ntlm_hash + nthash = ntlm_hash.split(':')[1] + else: + # only nt hash + hashes = ':%s' % ntlm_hash + nthash = ntlm_hash + + if not all('' == s for s in [self.nthash, password, aesKey]): + kerb_pass = next(s for s in [self.nthash, password, aesKey] if s) + else: + kerb_pass = '' + try: + res = self.conn.kerberosLogin(None, username, password, domain, hashes, aesKey, kdcHost=kdcHost, useCache=useCache) + if res is not True: + self.conn.printReplies() + return False + + self.password = password + self.username = username + self.domain = domain + self.check_if_admin() + + out = u'{}{}{} {}'.format('{}\\'.format(domain) if not self.args.local_auth else '', + username, + # Show what was used between cleartext, nthash, aesKey and ccache + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) + self.logger.success(out) + if not self.args.local_auth: + add_user_bh(self.username, self.domain, self.logger, self.config) + if not self.args.continue_on_success: + return True + except Exception as e: + self.logger.error(u'{}\\{}{} {}'.format('{}\\'.format(domain) if not self.args.local_auth else '', + username, + # Show what was used between cleartext, nthash, aesKey and ccache + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + e)) + return False + def plaintext_login(self, domain, username, password): try: self.conn.disconnect() diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index 1f25cf30c..ec2e500dc 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -11,14 +11,15 @@ from impacket.smb import SMB_DIALECT from impacket.examples.secretsdump import RemoteOperations, SAMHashes, LSASecrets, NTDSHashes from impacket.nmb import NetBIOSError, NetBIOSTimeout -from impacket.dcerpc.v5 import transport, lsat, lsad +from impacket.dcerpc.v5 import transport, lsat, lsad, scmr from impacket.dcerpc.v5.rpcrt import DCERPCException -from impacket.dcerpc.v5.transport import DCERPCTransportFactory +from impacket.dcerpc.v5.transport import DCERPCTransportFactory, SMBTransport from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP from impacket.dcerpc.v5.dcom.wmi import WBEM_FLAG_FORWARD_ONLY from impacket.dcerpc.v5.samr import SID_NAME_USE from impacket.dcerpc.v5.dtypes import MAXIMUM_ALLOWED +from impacket.krb5.kerberosv5 import SessionKeyDecryptionError from cme.connection import * from cme.logger import CMEAdapter from cme.servers.smb import CMESMBServer @@ -54,7 +55,9 @@ "STATUS_PASSWORD_EXPIRED", "STATUS_PASSWORD_MUST_CHANGE", "STATUS_ACCESS_DENIED", - "STATUS_NO_SUCH_FILE" + "STATUS_NO_SUCH_FILE", + "KDC_ERR_CLIENT_REVOKED", + "KDC_ERR_PREAUTH_FAILED" ] def get_error_string(exception): @@ -253,13 +256,16 @@ def enum_host_info(self): try: self.conn.login('' , '') - except: - #if "STATUS_ACCESS_DENIED" in e: + self.domain = self.conn.getServerDNSDomainName() + self.hostname = self.conn.getServerName() + self.server_os = self.conn.getServerOS() + except Exception as e: + if "STATUS_NOT_SUPPORTED" in str(e): + # no ntlm supported + self.domain = self.args.domain + self.hostname = self.host pass - self.domain = self.conn.getServerDNSDomainName() - self.hostname = self.conn.getServerName() - self.server_os = self.conn.getServerOS() try: self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning'] except: @@ -335,40 +341,75 @@ def print_host_info(self): return self.laps_search(self.args.username, self.args.password, self.args.hash, self.domain) return True - def kerberos_login(self, domain, aesKey, kdcHost): + def kerberos_login(self, domain, username, password = '', ntlm_hash = '', aesKey = '', kdcHost = '', useCache = False): + logging.getLogger("impacket").disabled = True #Re-connect since we logged off self.create_conn_obj() - # dirty code to check if user is admin but pywerview does not support kerberos auth ... - error = '' - try: - self.conn.kerberosLogin('', '', self.domain, self.lmhash, self.nthash, aesKey, kdcHost) - # self.check_if_admin() # currently pywerview does not support kerberos auth - except SessionError as e: - error = e + lmhash = '' + nthash = '' + if not all('' == s for s in [self.nthash, password, aesKey]): + kerb_pass = next(s for s in [self.nthash, password, aesKey] if s) + else: + kerb_pass = '' + try: - self.conn.connectTree("C$") - self.admin_privs = True - except SessionError as e: - pass - if not error: - out = u'{}\\{} {}'.format(self.domain, - self.conn.getCredentials()[0], + if not self.args.laps: + self.password = password + self.username = username + #This checks to see if we didn't provide the LM Hash + if ntlm_hash.find(':') != -1: + lmhash, nthash = ntlm_hash.split(':') + self.hash = nthash + else: + nthash = ntlm_hash + self.hash = ntlm_hash + if lmhash: self.lmhash = lmhash + if nthash: self.nthash = nthash + self.conn.kerberosLogin(username, password, domain, lmhash, nthash, aesKey, kdcHost, useCache=useCache) + self.check_if_admin() + + out = u'{}\\{}{} {}'.format(self.domain, + self.username, + # Show what was used between cleartext, nthash, aesKey and ccache + " from ccache" if useCache + else ":%s" % (kerb_pass if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.success(out) - return True - else: - self.logger.error(u'{} {} {}'.format(self.domain, - error, - '({})'.format(desc) if self.args.verbose else '')) + if not self.args.local_auth: + add_user_bh(self.username, domain, self.logger, self.config) + if not self.args.continue_on_success: + return True + elif self.signing: # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321 + try: + self.conn.logoff() + except: + pass + self.create_conn_obj() + except SessionKeyDecryptionError: + # for PRE-AUTH account + self.logger.error(u'{}\\{}{} {}'.format(domain, + self.username, + " account vulnerable to asreproast attack", + ""), + color='yellow') + return False + except FileNotFoundError as e: + self.logger.error('CCache Error: {}'.format(e)) + return False + except (SessionError, Exception) as e: + error, desc = e.getErrorString() + self.logger.error(u'{}\\{}{} {} {}'.format(domain, + self.username, + # Show what was used between cleartext, nthash, aesKey and ccache + " from ccache" if useCache + else ":%s" % (next(sub for sub in [nthash,password,aesKey] if (sub != '' and sub != None) or sub != None) if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8), + error, + '({})'.format(desc) if self.args.verbose else ''), + color='magenta' if error in smb_error_status else 'red') + if error not in smb_error_status: + self.inc_failed_login(username) + return False return False - - # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321 - if self.signing: - try: - self.conn.logoff() - except: - pass - self.create_conn_obj() def plaintext_login(self, domain, username, password): #Re-connect since we logged off @@ -414,8 +455,6 @@ def plaintext_login(self, domain, username, password): if error not in smb_error_status: self.inc_failed_login(username) return False - if not self.args.continue_on_success: - return True except (ConnectionResetError, NetBIOSTimeout, NetBIOSError) as e: self.logger.error('Connection Error: {}'.format(e)) return False @@ -479,8 +518,6 @@ def hash_login(self, domain, username, ntlm_hash): if error not in smb_error_status: self.inc_failed_login(self.username) return False - if not self.args.continue_on_success: - return True except (ConnectionResetError, NetBIOSTimeout, NetBIOSError) as e: self.logger.error('Connection Error: {}'.format(e)) return False @@ -522,15 +559,23 @@ def create_conn_obj(self): return False def check_if_admin(self): - lmhash = '' - nthash = '' - - if self.hash: - if self.hash.find(':') != -1: - lmhash, nthash = self.hash.split(':') - else: - nthash = self.hash - self.admin_privs = invoke_checklocaladminaccess(self.host, self.domain, self.username, self.password, lmhash, nthash) + rpctransport = SMBTransport(self.conn.getRemoteHost(), 445, r'\svcctl', smb_connection=self.conn) + dce = rpctransport.get_dce_rpc() + try: + dce.connect() + except: + pass + else: + dce.bind(scmr.MSRPC_UUID_SCMR) + try: + # 0xF003F - SC_MANAGER_ALL_ACCESS + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx + ans = scmr.hROpenSCManagerW(dce,'{}\x00'.format(self.host),'ServicesActive\x00', 0xF003F) + self.admin_privs = True + except scmr.DCERPCException as e: + self.admin_privs = False + pass + return def gen_relay_list(self): if self.server_os.lower().find('windows') != -1 and self.signing is False: