def rpc_resolve_sids(self): binding = r'ncacn_np:%s[\PIPE\lsarpc]' % self.addr dce = self.dce_rpc_connect(binding, lsat.MSRPC_UUID_LSAT) if dce is None: logging.warning('Connection failed') return try: resp = lsat.hLsarOpenPolicy2(dce, lsat.POLICY_LOOKUP_NAMES | MAXIMUM_ALLOWED) except Exception as e: if str(e).find('Broken pipe') >= 0: return else: raise policyHandle = resp['PolicyHandle'] try: resp = lsat.hLsarLookupSids(dce, policyHandle, self.sids, lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta) except DCERPCException as e: if str(e).find('STATUS_NONE_MAPPED') >= 0: logging.warning('SID lookup failed, return status: STATUS_NONE_MAPPED') raise elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0: # Not all could be resolved, work with the ones that could resp = e.get_packet() else: raise domains = [] for entry in resp['ReferencedDomains']['Domains']: logging.debug('Found referenced domain: %s' % entry['Name']) domains.append(entry['Name']) i = 0 for entry in resp['TranslatedNames']['Names']: domain = domains[entry['DomainIndex']] domainEntry = self.ad.get_domain_by_name(domain) if domainEntry is not None: domain = ADUtils.ldap2domain(domainEntry['attributes']['distinguishedName']) if entry['Name'] != '': logging.debug('Resolved SID to name: %s@%s' % (entry['Name'], domain)) self.admins.append({'computer': self.hostname, 'name': unicode(entry['Name']), 'use': ADUtils.translateSidType(entry['Use']), 'domain': domain, 'sid': self.sids[i]}) i = i + 1 else: logging.warning('Resolved name is empty [%s]', entry) dce.disconnect()
def __init__(self, domain=None, auth=None, nameserver=None): self.domain = domain self.auth = auth self._dcs = [] self._kdcs = [] self.blacklist = [] self.whitelist = [] self.domains = {} self.nbdomains = {} self.groups = {} # Groups by DN self.groups_dnmap = {} # Group mapping from gid to DN self.computers = {} self.users = {} self.admins = [] # Create a resolver object self.resolver = dns.resolver.Resolver() if nameserver: self.resolver.nameservers = [nameserver] # Give it a cache to prevent duplicate lookups self.resolver.cache = dns.resolver.Cache() # Default timeout after 3 seconds if the DNS servers # do not come up with an answer self.resolver.lifetime = 3.0 # Also create a custom cache for both forward and backward lookups # this cache is thread-safe self.dnscache = DNSCache() if domain is not None: self.baseDN = ADUtils.domain2ldap(domain) else: self.baseDN = None
def try_connect(self): addr = None try: addr = self.ad.dnscache.get(self.hostname) except KeyError: try: q = self.ad.resolver.query(self.hostname, 'A') for r in q: addr = r.address if addr == None: return False # Do exit properly on keyboardinterrupts except KeyboardInterrupt: raise except Exception as e: logging.warning('Could not resolve: %s: %s' % (self.hostname, e)) return False logging.debug('Resolved: %s' % addr) self.ad.dnscache.put(self.hostname, addr) self.addr = addr logging.debug('Trying connecting to computer: %s' % self.hostname) # We ping the host here, this adds a small overhead for setting up an extra socket # but saves us from constructing RPC Objects for non-existing hosts. Also RPC over # SMB does not support setting a connection timeout, so we catch this here. if ADUtils.tcp_ping(addr, 445) is False: return False return True
def write_membership(self, resolved_entry, membership, out): if membership in self.ad.groups: parent = self.ad.groups[membership] pd = ADUtils.ldap2domain(membership) pr = self.resolve_ad_entry(parent) out.write(u'%s,%s,%s\n' % (pr['principal'], resolved_entry['principal'], resolved_entry['type'])) else: logging.warning('Warning: Unknown group %s', membership)
def process_computer(self, hostname, results_q): """ Processes a single computer, pushes the results of the computer to the given Queue. """ logging.debug('Querying computer: %s' % hostname) c = ADComputer(hostname=hostname, ad=self) if c.try_connect() == True: # Maybe try connection reuse? try: sessions = c.rpc_get_sessions() c.rpc_get_local_admins() c.rpc_resolve_sids() c.rpc_close() # c.rpc_get_domain_trusts() for admin in c.admins: # Put the result on the results queue. results_q.put(('admin',u'%s,%s@%s,%s\n' % (unicode(admin['computer']).upper(), unicode(admin['name']).upper(), admin['domain'].upper(), unicode(admin['use']).lower()))) if sessions is None: sessions = [] for ses in sessions: # Todo: properly resolve sAMAccounName in GC # currently only single-domain compatible domain = self.domain user = (u'%s@%s' % (ses['user'], domain)).upper() # Resolve the IP to obtain the host the session is from try: target = self.dnscache.get(ses['source']) except KeyError: target = ADUtils.ip2host(ses['source'], self.resolver) # Even if the result is the IP (aka could not resolve PTR) we still cache # it since this result is unlikely to change self.dnscache.put_single(ses['source'], target) if ':' in target: # IPv6 address, not very useful continue if not '.' in target: logging.debug('Resolved target does not look like an IP or domain. Assuming hostname: %s', target) target = '%s.%s' % (target, domain) # Put the result on the results queue. results_q.put(('session', u'%s,%s,%u\n' % (user, target, 2))) except DCERPCException: logging.warning('Querying sessions failed: %s' % hostname) except Exception as e: logging.error('Unhandled exception in computer processing: %s', str(e)) logging.info(traceback.format_exc())
def dns_resolve(self, domain=None, kerberos=True): logging.debug('Querying domain controller information from DNS') basequery = '_ldap._tcp.pdc._msdcs' if domain is not None: logging.debug('Using domain hint: %s' % str(domain)) query = '_ldap._tcp.pdc._msdcs.%s' % domain else: # Assume a DNS search domain is (correctly) configured on the host # in which case the resolver will autocomplete our request query = basequery try: q = self.resolver.query(query, 'SRV') if str(q.qname).lower().startswith('_ldap._tcp.pdc._msdcs'): ad_domain = str(q.qname).lower()[len(basequery):].strip('.') logging.info('Found AD domain: %s' % ad_domain) self.domain = ad_domain if self.auth.domain is None: self.auth.domain = ad_domain self.baseDN = ADUtils.domain2ldap(ad_domain) for r in q: dc = str(r.target).rstrip('.') logging.debug('Found primary DC: %s' % dc) if dc not in self._dcs: self._dcs.append(dc) except resolver.NXDOMAIN: pass if kerberos is True: try: q = self.resolver.query('_kerberos._tcp.dc._msdcs', 'SRV') for r in q: kdc = str(r.target).rstrip('.') logging.debug('Found KDC: %s' % str(r.target).rstrip('.')) if kdc not in self._kdcs: self._kdcs.append(kdc) self.auth.kdc = self._kdcs[0] except resolver.NXDOMAIN: pass return True
def resolve_ad_entry(self, entry): resolved = {} account = '' dn = '' domain = '' if entry['attributes']['sAMAccountName']: account = entry['attributes']['sAMAccountName'] if entry['attributes']['distinguishedName']: dn = entry['attributes']['distinguishedName'] domain = ADUtils.ldap2domain(dn) resolved['principal'] = unicode('%s@%s' % (account, domain)).upper() if not entry['attributes']['sAMAccountName']: # This doesn't make sense currently but neither does it in SharpHound. # TODO: figure out what the intended result is if 'ForeignSecurityPrincipals' in dn: resolved['principal'] = domain.upper() resolved['type'] = 'foreignsecurityprincipal' else: resolved['type'] = 'unknown' else: accountType = entry['attributes']['sAMAccountType'] if accountType in [268435456, 268435457, 536870912, 536870913]: resolved['type'] = 'group' elif accountType in [805306369]: resolved['type'] = 'computer' short_name = account.rstrip('$') resolved['principal'] = unicode('%s.%s' % (short_name, domain)).upper() elif accountType in [805306368]: resolved['type'] = 'user' elif accountType in [805306370]: resolved['type'] = 'trustaccount' else: resolved['type'] = 'domain' return resolved
def fromLDAP(identifier, sid=None): dns_name = ADUtils.ldap2domain(identifier) return ADDomain(name=dns_name, sid=sid, distinguishedname=identifier)