Exemplo n.º 1
0
    def get_computers(self, include_properties=False, acl=False):
        properties = ['samaccountname', 'userAccountControl', 'distinguishedname',
                      'dnshostname', 'samaccounttype', 'objectSid', 'primaryGroupID']
        if include_properties:
            properties += ['servicePrincipalName', 'msDS-AllowedToDelegateTo',
                           'lastLogon', 'lastLogonTimestamp', 'pwdLastSet', 'operatingSystem', 'description', 'operatingSystemServicePack']
            if 'msDS-AllowedToActOnBehalfOfOtherIdentity'.lower() in self.objecttype_guid_map:
                properties.append('msDS-AllowedToActOnBehalfOfOtherIdentity')
            if self.ad.has_laps:
                properties.append('ms-mcs-admpwdexpirationtime')
        if acl:
            # Also collect LAPS expiration time since this matters for reporting (no LAPS = no ACL reported)
            if self.ad.has_laps:
                properties += ['nTSecurityDescriptor', 'ms-mcs-admpwdexpirationtime']
            else:
                properties.append('nTSecurityDescriptor')
        entries = self.search('(&(sAMAccountType=805306369)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))',
                              properties,
                              generator=True,
                              query_sd=acl)

        entriesNum = 0
        for entry in entries:
            entriesNum += 1
            self.ad.computers[ADUtils.get_entry_property(entry, 'distinguishedName', '')] = entry
            self.ad.computersidcache.put(ADUtils.get_entry_property(entry, 'dNSHostname', '').lower(), entry['attributes']['objectSid'])

        logging.info('Found %u computers', entriesNum)

        return entries
Exemplo n.º 2
0
 def get_membership(self, member):
     # First assume it is a user
     try:
         resolved_entry = self.addomain.users[member]
     except KeyError:
         # Try if it is a group
         try:
             resolved_entry = self.addomain.groups[member]
         except KeyError:
             # Try if it is a computer
             try:
                 entry = self.addomain.computers[member]
                 # Computers are stored as raw entries
                 resolved_entry = ADUtils.resolve_ad_entry(entry)
             except KeyError:
                 use_gc = ADUtils.ldap2domain(
                     member) != self.addomain.domain
                 qobject = self.addomain.objectresolver.resolve_distinguishedname(
                     member, use_gc=use_gc)
                 if qobject is None:
                     return
                 resolved_entry = ADUtils.resolve_ad_entry(qobject)
                 # Store it in the cache
                 if resolved_entry['type'] == 'user':
                     self.addomain.users[member] = resolved_entry
                 if resolved_entry['type'] == 'group':
                     self.addomain.groups[member] = resolved_entry
                 # Computers are stored as raw entries
                 if resolved_entry['type'] == 'computer':
                     self.addomain.computers[member] = qobject
     return {
         "MemberName": resolved_entry['principal'],
         "MemberType": resolved_entry['type'].capitalize()
     }
Exemplo n.º 3
0
    def gc_sam_lookup(self, samname):
        """
        This function attempts to resolve the SAM name returned in session enumeration to
        a user/domain combination by querying the Global Catalog.
        SharpHound calls this GC Deconflictation.
        """
        output = []
        entries = self.resolve_samname(samname)
        # If an error occurs, return
        if entries is None:
            return
        if len(entries) > 1:
            # Awww multiple matches, unsure which is the valid one, add them with different weights
            for entry in entries:
                domain = ADUtils.ldap2domain(entry['dn'])
                principal = (u'%s@%s' % (entry['attributes']['sAMAccountName'], domain)).upper()
                # This is consistent with SharpHound
                if domain.lower() == self.addomain.domain.lower():
                    weight = 1
                else:
                    weight = 2
                output.append((principal, weight))
        else:
            if len(entries) == 0:
                # This shouldn't even happen, but let's default to the current domain
                principal = (u'%s@%s' % (samname, self.addomain.domain)).upper()
                output.append((principal, 2))
            else:
                # One match, best case
                entry = entries[0]
                domain = ADUtils.ldap2domain(entry['dn'])
                principal = (u'%s@%s' % (entry['attributes']['sAMAccountName'], domain)).upper()
                output.append((principal, 2))

        return output
Exemplo n.º 4
0
 def get_bloodhound_data(self, entry, collect):
     data = {
         'Name': self.hostname.upper(),
         'PrimaryGroup': self.primarygroup,
         'LocalAdmins': self.admins,
         'Properties': {
             'objectsid': self.objectsid,
             'domain': self.ad.domain,
             'highvalue': False
         },
         "RemoteDesktopUsers": [],
         "DcomUsers": [],
         "AllowedToDelegate": []
     }
     props = data['Properties']
     # via the TRUSTED_FOR_DELEGATION (0x00080000) flag in UAC
     props['unconstraineddelegation'] = ADUtils.get_entry_property(entry, 'userAccountControl', default=0) & 0x00080000 == 0x00080000
     if 'objectprops' in collect:
         props['enabled'] = ADUtils.get_entry_property(entry, 'userAccountControl', default=0) & 2 == 0
         props['lastlogon'] = ADUtils.win_timestamp_to_unix(
             ADUtils.get_entry_property(entry, 'lastLogon', default=0, raw=True)
         )
         props['pwdlastset'] = ADUtils.win_timestamp_to_unix(
             ADUtils.get_entry_property(entry, 'pwdLastSet', default=0, raw=True)
         )
         props['serviceprincipalnames'] = ADUtils.get_entry_property(entry, 'servicePrincipalName', [])
         props['description'] = ADUtils.get_entry_property(entry, 'description')
         props['operatingsystem'] = ADUtils.get_entry_property(entry, 'operatingSystem')
         # Add SP to OS if specified
         servicepack = ADUtils.get_entry_property(entry, 'operatingSystemServicePack')
         if servicepack:
             props['operatingsystem'] = '%s %s' % (props['operatingsystem'], servicepack)
         # TODO: AllowedToDelegate
     return data
Exemplo n.º 5
0
    def enumerate_memberships(self, filename='group_membership.csv'):
        entries = self.addc.get_memberships()

        try:
            logging.debug('Opening file for writing: %s' % filename)
            out = codecs.open(filename, 'w', 'utf-8')
        except:
            logging.warning('Could not write file: %s' % filename)
            return

        logging.debug('Writing group memberships to file: %s' % filename)

        out.write('GroupName,AccountName,AccountType\n')
        entriesNum = 0
        for entry in entries:
            entriesNum += 1
            resolved_entry = ADUtils.resolve_ad_entry(entry)
            try:
                for m in entry['attributes']['memberOf']:
                    self.write_membership(resolved_entry, m, out)
            except (KeyError, LDAPKeyError):
                logging.debug(traceback.format_exc())
            self.write_primary_membership(resolved_entry, entry, out)

        logging.info('Found %d memberships', entriesNum)
        logging.debug('Finished writing membership')
        out.close()
Exemplo n.º 6
0
 def get_primary_membership(self, entry):
     """
     Looks up the primary membership based on RID. Resolves it if needed
     """
     try:
         primarygroupid = int(entry['attributes']['primaryGroupID'])
     except (TypeError, KeyError):
         # Doesn't have a primarygroupid, means it is probably a Group instead of a user
         return
     try:
         group = self.addomain.groups[
             self.addomain.groups_dnmap[primarygroupid]]
         return group['principal']
     except KeyError:
         # Look it up
         # Construct group sid by taking the domain sid, removing the user rid and appending the group rid
         groupsid = '%s-%d' % ('-'.join(
             entry['attributes']['objectSid'].split('-')[:-1]),
                               primarygroupid)
         group = self.addomain.objectresolver.resolve_sid(groupsid,
                                                          use_gc=False)
         if group is None:
             logging.warning('Warning: Unknown primarygroupid %d',
                             primarygroupid)
             return None
         resolved_entry = ADUtils.resolve_ad_entry(group)
         self.addomain.groups[group['attributes']
                              ['distinguishedName']] = resolved_entry
         self.addomain.groups_dnmap[primarygroupid] = group['attributes'][
             'distinguishedName']
         return resolved_entry['principal']
Exemplo n.º 7
0
    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.dnsresolver.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

        try:
            q = self.dnsresolver.query(query.replace('pdc', 'gc'), 'SRV')
            for r in q:
                gc = str(r.target).rstrip('.')
                logging.debug('Found Global Catalog server: %s' % gc)
                if gc not in self._gcs:
                    self._gcs.append(gc)

        except resolver.NXDOMAIN:
            pass

        if kerberos is True:
            try:
                q = self.dnsresolver.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
Exemplo n.º 8
0
    def __init__(self,
                 domain=None,
                 auth=None,
                 nameserver=None,
                 dns_tcp=False,
                 dns_timeout=3.0):
        self.domain = domain
        # Object of type ADDomain, added later
        self.domain_object = None
        self.auth = auth
        # List of DCs for this domain. Contains just one DC since
        # we query for the primary DC specifically
        self._dcs = []
        # Kerberos servers
        self._kdcs = []
        # Global catalog servers
        self._gcs = []

        self.domains = {}
        self.nbdomains = {}
        self.groups = {}  # Groups by DN
        self.groups_dnmap = {}  # Group mapping from gid to DN
        self.computers = {}
        self.users = {}  # Users by DN

        # Create a resolver object
        self.dnsresolver = resolver.Resolver()
        if nameserver:
            self.dnsresolver.nameservers = [nameserver]
        # Resolve DNS over TCP?
        self.dns_tcp = dns_tcp
        # Set DNS timeout
        self.dns_timeout = dns_timeout
        # Give it a cache to prevent duplicate lookups
        self.dnsresolver.cache = resolver.Cache()
        # Default timeout after 3 seconds if the DNS servers
        # do not come up with an answer
        self.dnsresolver.lifetime = dns_timeout
        self.dnsresolver.timeout = dns_timeout
        # Also create a custom cache for both forward and backward lookups
        # this cache is thread-safe
        self.dnscache = DNSCache()
        # Create a thread-safe SID lookup cache
        self.sidcache = SidCache()
        # Create a thread-safe SAM lookup cache
        self.samcache = SamCache()
        # Create SID cache for computer accounts
        self.computersidcache = SidCache()
        # Object Resolver, initialized later
        self.objectresolver = None
        # Number of domains within the forest
        self.num_domains = 1
        # Does the schema have laps properties
        self.has_laps = False
        if domain is not None:
            self.baseDN = ADUtils.domain2ldap(domain)
        else:
            self.baseDN = None
Exemplo n.º 9
0
    def rpc_get_schtasks(self):
        """
        Query the scheduled tasks via RPC. Requires admin privileges.
        These credentials can be dumped with mimikatz via vault::cred
        """
        # Blacklisted folders (Default ones)
        blacklist = [u'Microsoft\x00']
        # Start with the root folder
        folders = ['\\']
        tasks = []
        schtaskusers = []
        binding = r'ncacn_np:%s[\PIPE\atsvc]' % self.addr
        try:
            dce = self.dce_rpc_connect(binding, tsch.MSRPC_UUID_TSCHS, True)
            if dce is None:
                return schtaskusers
            # Get root folder
            resp = tsch.hSchRpcEnumFolders(dce, '\\')
            for item in resp['pNames']:
                data = item['Data']
                if data not in blacklist:
                    folders.append('\\' + data)

            # Enumerate the folders we found
            # subfolders not supported yet
            for folder in folders:
                try:
                    resp = tsch.hSchRpcEnumTasks(dce, folder)
                    for item in resp['pNames']:
                        data = item['Data']
                        if folder != '\\':
                            # Make sure to strip the null byte
                            tasks.append(folder[:-1] + '\\' + data)
                        else:
                            tasks.append(folder + data)
                except DCERPCException as e:
                    logging.debug('Error enumerating task folder %s: %s',
                                  folder, e)
            for task in tasks:
                try:
                    resp = tsch.hSchRpcRetrieveTask(dce, task)
                    # This returns a tuple (sid, logontype) or None
                    userinfo = ADUtils.parse_task_xml(resp['pXml'])
                    if userinfo:
                        if userinfo[1] == u'Password':
                            # Convert to byte string because our cache format is in bytes
                            schtaskusers.append(str(userinfo[0]))
                            logging.info(
                                'Found scheduled task %s on %s with stored credentials for SID %s',
                                task, self.hostname, userinfo[0])
                except DCERPCException as e:
                    logging.debug('Error querying task %s: %s', task, e)
        except DCERPCException as e:
            logging.debug('Exception enumerating scheduled tasks: %s', e)

        dce.disconnect()
        return schtaskusers
Exemplo n.º 10
0
 def parse_gmsa(self, user, entry):
     """
     Parse GMSA DACL which states which users can read the password
     """
     _, aces = parse_binary_acl(user, 'user', ADUtils.get_entry_property(entry, 'msDS-GroupMSAMembership', raw=True), self.addc.objecttype_guid_map)
     processed_aces = self.aceresolver.resolve_aces(aces)
     for ace in processed_aces:
         if ace['RightName'] == 'Owner':
             continue
         ace['RightName'] = 'ReadGMSAPassword'
         user['Aces'].append(ace)
Exemplo n.º 11
0
    def write_membership(self, resolved_entry, membership, out):
        if membership in self.addomain.groups:
            parent = self.addomain.groups[membership]
            pd = ADUtils.ldap2domain(membership)
            pr = ADUtils.resolve_ad_entry(parent)

            out.write(u'%s,%s,%s\n' %
                      (pr['principal'], resolved_entry['principal'],
                       resolved_entry['type']))
        else:
            # This could be a group in a different domain
            parent = self.addomain.objectresolver.resolve_group(membership)
            if not parent:
                logging.warning('Warning: Unknown group %s', membership)
                return
            self.addomain.groups[membership] = parent
            pd = ADUtils.ldap2domain(membership)
            pr = ADUtils.resolve_ad_entry(parent)

            out.write(u'%s,%s,%s\n' %
                      (pr['principal'], resolved_entry['principal'],
                       resolved_entry['type']))
Exemplo n.º 12
0
    def enumerate_users(self):
        filename = 'users.json'

        # Should we include extra properties in the query?
        with_properties = 'objectprops' in self.collect
        acl = 'acl' in self.collect
        entries = self.addc.get_users(include_properties=with_properties,
                                      acl=acl)

        # If the logging level is DEBUG, we ident the objects
        if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
            indent_level = 1
        else:
            indent_level = None

        try:
            out = codecs.open(filename, 'w', 'utf-8')
        except:
            logging.warning('Could not write file: %s' % filename)
            return

        logging.debug('Writing users to file: %s' % filename)

        # Initialize json header
        out.write('{"users":[')

        num_entries = 0
        for entry in entries:
            resolved_entry = ADUtils.resolve_ad_entry(entry)
            user = {
                "Name": resolved_entry['principal'],
                "PrimaryGroup": self.get_primary_membership(entry),
                "Properties": {
                    "domain": self.addomain.domain,
                    "objectsid": entry['attributes']['objectSid'],
                    "highvalue": False
                }
            }
            if with_properties:
                MembershipEnumerator.add_user_properties(user, entry)
            self.addomain.users[entry['dn']] = resolved_entry
            if num_entries != 0:
                out.write(',')
            json.dump(user, out, indent=indent_level)
            num_entries += 1

        logging.info('Found %d users', num_entries)
        out.write('],"meta":{"type":"users","count":%d}}' % num_entries)

        logging.debug('Finished writing users')
        out.close()
Exemplo n.º 13
0
 def get_membership(self, member):
     """
     Attempt to resolve the membership (DN) of a group to an object
     """
     # First assume it is a user
     try:
         resolved_entry = self.addomain.users[member]
     except KeyError:
         # Try if it is a group
         try:
             resolved_entry = self.addomain.groups[member]
         except KeyError:
             # Try if it is a computer
             try:
                 entry = self.addomain.computers[member]
                 # Computers are stored as raw entries
                 resolved_entry = ADUtils.resolve_ad_entry(entry)
             except KeyError:
                 use_gc = ADUtils.ldap2domain(
                     member) != self.addomain.domain
                 qobject = self.addomain.objectresolver.resolve_distinguishedname(
                     member, use_gc=use_gc)
                 if qobject is None:
                     return None
                 resolved_entry = ADUtils.resolve_ad_entry(qobject)
                 # Store it in the cache
                 if resolved_entry['type'] == 'User':
                     self.addomain.users[member] = resolved_entry
                 if resolved_entry['type'] == 'Group':
                     self.addomain.groups[member] = resolved_entry
                 # Computers are stored as raw entries
                 if resolved_entry['type'] == 'Computer':
                     self.addomain.computers[member] = qobject
     return {
         "ObjectIdentifier": resolved_entry['objectid'],
         "ObjectType": resolved_entry['type'].capitalize()
     }
Exemplo n.º 14
0
 def write_primary_membership(self, resolved_entry, entry, out):
     try:
         primarygroupid = int(entry['attributes']['primaryGroupID'])
     except (TypeError, KeyError):
         # Doesn't have a primarygroupid, means it is probably a Group instead of a user
         return
     try:
         group = self.addomain.groups[
             self.addomain.groups_dnmap[primarygroupid]]
         pr = ADUtils.resolve_ad_entry(group)
         out.write('%s,%s,%s\n' %
                   (pr['principal'], resolved_entry['principal'],
                    resolved_entry['type']))
     except KeyError:
         logging.warning('Warning: Unknown primarygroupid %d',
                         primarygroupid)
Exemplo n.º 15
0
    def rpc_get_loggedon(self):
        """
        Query logged on users via RPC.
        Requires admin privs
        """
        binding = r'ncacn_np:%s[\PIPE\wkssvc]' % self.addr
        loggedonusers = set()
        dce = self.dce_rpc_connect(binding, wkst.MSRPC_UUID_WKST)
        if dce is None:
            logging.warning('Connection failed: %s', binding)
            return
        try:
            # 1 means more detail, including the domain
            resp = wkst.hNetrWkstaUserEnum(dce, 1)
            for record in resp['UserInfo']['WkstaUserInfo']['Level1'][
                    'Buffer']:
                # Skip computer accounts
                if record['wkui1_username'][-2] == '$':
                    continue
                # Skip sessions for local accounts
                if record['wkui1_logon_domain'][:-1].upper(
                ) == self.samname.upper():
                    continue
                domain = record['wkui1_logon_domain'][:-1].upper()
                domain_entry = self.ad.get_domain_by_name(domain)
                if domain_entry is not None:
                    domain = ADUtils.ldap2domain(
                        domain_entry['attributes']['distinguishedName'])
                logging.debug(
                    'Found logged on user at %s: %s@%s' %
                    (self.hostname, record['wkui1_username'][:-1], domain))
                loggedonusers.add((record['wkui1_username'][:-1], domain))
        except DCERPCException as e:
            if 'rpc_s_access_denied' in str(e):
                logging.debug(
                    'Access denied while enumerating LoggedOn on %s, probably no admin privs',
                    self.hostname)
            else:
                logging.debug('Exception connecting to RPC: %s', e)
        except Exception as e:
            if 'connection reset' in str(e):
                logging.debug('Connection was reset: %s', e)
            else:
                raise e

        dce.disconnect()
        return list(loggedonusers)
Exemplo n.º 16
0
    def try_connect(self):
        addr = None
        try:
            addr = self.ad.dnscache.get(self.hostname)
        except KeyError:
            try:
                q = self.ad.dnsresolver.query(self.hostname,
                                              'A',
                                              tcp=self.ad.dns_tcp)
                for r in q:
                    addr = r.address

                if addr == None:
                    return False
            # Do exit properly on keyboardinterrupts
            except KeyboardInterrupt:
                raise
            except Exception as e:
                # Doesn't exist
                if "None of DNS query names exist" in str(e):
                    logging.info(
                        'Skipping enumeration for %s since it could not be resolved.',
                        self.hostname)
                else:
                    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
Exemplo n.º 17
0
    def enumerate_gpos(self):
        gpos = []
        resolver = AceResolver(self.addomain, self.addomain.objectresolver)
        entries = self.addc.get_gpos()
        for entry in entries:
            gpo = {
                    "Properties": {
                        "highvalue": ADUtils.get_entry_property(entry, 'isCriticalSystemObject', default=False),
                        "name": ADUtils.get_entry_property(entry, 'displayName'),
                        "domain": '.'.join(str(ADUtils.get_entry_property(entry, 'distinguishedName')).split('DC')[1:]).translate({ord(c):'' for c in '=,'}),
                        "objectid": str(ADUtils.get_entry_property(entry, 'objectGUID')).translate({ord(c):'' for c in '}{'}),
                        "distinguishedname": ADUtils.get_entry_property(entry, 'distinguishedName'),
                        "description": None,
                        "gpcpath": ADUtils.get_entry_property(entry, 'gPCFileSysPath')
                    },
                    "ObjectIdentifier": str(ADUtils.get_entry_property(entry, 'objectGUID')).translate({ord(c):'' for c in '}{'}),
                    "Aces": []
            }

            _, aces = parse_binary_acl(gpo, 'gpo', ADUtils.get_entry_property(entry, 'nTSecurityDescriptor'), self.addc.objecttype_guid_map)
            gpo['Aces'] = resolver.resolve_aces(aces)
            gpos.append(gpo)
           
        self.dump_gpos(gpos)
Exemplo n.º 18
0
 def fromLDAP(identifier, sid=None):
     dns_name = ADUtils.ldap2domain(identifier)
     return ADDomain(name=dns_name, sid=sid, distinguishedname=identifier)
Exemplo n.º 19
0
    def dns_resolve(self, domain=None, kerberos=True, options=None):
        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.dnsresolver.query(query, 'SRV', tcp=self.dns_tcp)

            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

        try:
            q = self.dnsresolver.query(query.replace('pdc', 'gc'),
                                       'SRV',
                                       tcp=self.dns_tcp)
            for r in q:
                gc = str(r.target).rstrip('.')
                logging.debug('Found Global Catalog server: %s' % gc)
                if gc not in self._gcs:
                    self._gcs.append(gc)

        except resolver.NXDOMAIN:
            # Only show warning if we don't already have a GC specified manually
            if options and not options.global_catalog:
                if not options.disable_autogc:
                    logging.warning(
                        'Could not find a global catalog server, assuming the primary DC has this role\n'
                        'If this gives errors, either specify a hostname with -gc or disable gc resolution with --disable-autogc'
                    )
                    self._gcs = self._dcs
                else:
                    logging.warning(
                        'Could not find a global catalog server. Please specify one with -gc'
                    )

        if kerberos is True:
            try:
                q = self.dnsresolver.query('_kerberos._tcp.dc._msdcs',
                                           'SRV',
                                           tcp=self.dns_tcp)
                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
Exemplo n.º 20
0
 def get_root_domain(self):
     return ADUtils.ldap2domain(
         self.ldap.server.info.other['configurationNamingContext'][0])
Exemplo n.º 21
0
    def write_default_groups(self):
        """
        Put default groups in the groups.json file
        """

        # Domain controllers
        rootdomain = self.addc.get_root_domain().upper()
        entries = self.addc.get_domain_controllers()

        group = {
            "IsDeleted": False,
            "IsACLProtected": False,
            "ObjectIdentifier": "%s-S-1-5-9" % rootdomain,
            "Properties": {
                "domain": rootdomain.upper(),
                "name": "ENTERPRISE DOMAIN CONTROLLERS@%s" % rootdomain,
            },
            "Members": [],
            "Aces": []
        }
        for entry in entries:
            resolved_entry = ADUtils.resolve_ad_entry(entry)
            memberdata = {
                "ObjectIdentifier": resolved_entry['objectid'],
                "ObjectType": resolved_entry['type'].capitalize()
            }
            group["Members"].append(memberdata)
        self.result_q.put(group)

        domainsid = self.addomain.domain_object.sid
        domainname = self.addomain.domain.upper()

        # Everyone
        evgroup = {
            "IsDeleted": False,
            "IsACLProtected": False,
            "ObjectIdentifier": "%s-S-1-1-0" % domainname,
            "Properties": {
                "domain": domainname,
                "domainsid": self.addomain.domain_object.sid,
                "name": "EVERYONE@%s" % domainname,
            },
            "Members": [],
            "Aces": []
        }
        self.result_q.put(evgroup)

        # Authenticated users
        augroup = {
            "IsDeleted": False,
            "IsACLProtected": False,
            "ObjectIdentifier": "%s-S-1-5-11" % domainname,
            "Properties": {
                "domain": domainname,
                "domainsid": self.addomain.domain_object.sid,
                "name": "AUTHENTICATED USERS@%s" % domainname,
            },
            "Members": [],
            "Aces": []
        }
        self.result_q.put(augroup)

        # Interactive
        iugroup = {
            "IsDeleted": False,
            "IsACLProtected": False,
            "ObjectIdentifier": "%s-S-1-5-4" % domainname,
            "Properties": {
                "domain": domainname,
                "domainsid": self.addomain.domain_object.sid,
                "name": "INTERACTIVE@%s" % domainname,
            },
            "Members": [],
            "Aces": []
        }
        self.result_q.put(iugroup)
Exemplo n.º 22
0
    def process_computer(self, hostname, samname, objectsid, entry, 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,
                       samname=samname,
                       ad=self.addomain,
                       objectsid=objectsid)
        c.primarygroup = self.get_primary_membership(entry)
        if c.try_connect() == True:
            try:

                if 'session' in self.collect:
                    sessions = c.rpc_get_sessions()
                else:
                    sessions = []
                if 'localadmin' in self.collect:
                    unresolved = c.rpc_get_group_members(544, c.admins)
                    c.rpc_resolve_sids(unresolved, c.admins)
                if 'rdp' in self.collect:
                    unresolved = c.rpc_get_group_members(555, c.rdp)
                    c.rpc_resolve_sids(unresolved, c.rdp)
                if 'dcom' in self.collect:
                    unresolved = c.rpc_get_group_members(562, c.dcom)
                    c.rpc_resolve_sids(unresolved, c.dcom)
                if 'loggedon' in self.collect:
                    loggedon = c.rpc_get_loggedon()
                else:
                    loggedon = []
                if 'experimental' in self.collect:
                    services = c.rpc_get_services()
                    tasks = c.rpc_get_schtasks()
                else:
                    services = []
                    tasks = []

                c.rpc_close()
                # c.rpc_get_domain_trusts()

                results_q.put(
                    ('computer', c.get_bloodhound_data(entry, self.collect)))

                if sessions is None:
                    sessions = []

                # Process found sessions
                for ses in sessions:
                    # For every session, resolve the SAM name in the GC if needed
                    domain = self.addomain.domain
                    if self.addomain.num_domains > 1 and self.do_gc_lookup:
                        try:
                            users = self.addomain.samcache.get(samname)
                        except KeyError:
                            # Look up the SAM name in the GC
                            users = self.addomain.objectresolver.gc_sam_lookup(
                                ses['user'])
                            if users is None:
                                # Unknown user
                                continue
                            self.addomain.samcache.put(samname, users)
                    else:
                        users = [((u'%s@%s' % (ses['user'], domain)).upper(),
                                  2)]

                    # Resolve the IP to obtain the host the session is from
                    try:
                        target = self.addomain.dnscache.get(ses['source'])
                    except KeyError:
                        target = ADUtils.ip2host(ses['source'],
                                                 self.addomain.dnsresolver,
                                                 self.addomain.dns_tcp)
                        # Even if the result is the IP (aka could not resolve PTR) we still cache
                        # it since this result is unlikely to change during this run
                        self.addomain.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.
                    for user in users:
                        results_q.put(('session', {
                            'UserName': user[0].upper(),
                            'ComputerName': target.upper(),
                            'Weight': user[1]
                        }))
                if loggedon is None:
                    loggedon = []

                # Put the logged on users on the queue too
                for user in loggedon:
                    results_q.put(('session', {
                        'UserName': ('%s@%s' % user).upper(),
                        'ComputerName': hostname.upper(),
                        'Weight': 1
                    }))

                # Process Tasks
                for taskuser in tasks:
                    try:
                        user = self.addomain.sidcache.get(taskuser)
                    except KeyError:
                        # Resolve SID in GC
                        userentry = self.addomain.objectresolver.resolve_sid(
                            taskuser)
                        # Resolve it to an entry and store in the cache
                        user = ADUtils.resolve_ad_entry(userentry)
                        self.addomain.sidcache.put(taskuser, user)
                    logging.debug('Resolved TASK SID to username: %s',
                                  user['principal'])
                    # Use sessions for now
                    results_q.put(('session', {
                        'UserName': user['principal'].upper(),
                        'ComputerName': hostname.upper(),
                        'Weight': 2
                    }))

                # Process Services
                for serviceuser in services:
                    # Todo: use own cache
                    try:
                        user = self.addomain.sidcache.get(serviceuser)
                    except KeyError:
                        # Resolve UPN in GC
                        userentry = self.addomain.objectresolver.resolve_upn(
                            serviceuser)
                        # Resolve it to an entry and store in the cache
                        user = ADUtils.resolve_ad_entry(userentry)
                        self.addomain.sidcache.put(serviceuser, user)
                    logging.debug('Resolved Service UPN to username: %s',
                                  user['principal'])
                    # Use sessions for now
                    results_q.put(('session', {
                        'UserName': user['principal'].upper(),
                        'ComputerName': hostname.upper(),
                        'Weight': 2
                    }))

            except DCERPCException:
                logging.warning('Querying computer failed: %s' % hostname)
            except Exception as e:
                logging.error(
                    'Unhandled exception in computer %s processing: %s',
                    hostname, str(e))
                logging.info(traceback.format_exc())
        else:
            # Write the info we have to the file regardless
            try:
                results_q.put(
                    ('computer', c.get_bloodhound_data(entry, self.collect)))
            except Exception as e:
                logging.error(
                    'Unhandled exception in computer %s processing: %s',
                    hostname, str(e))
                logging.info(traceback.format_exc())
Exemplo n.º 23
0
    def enumerate_computers_dconly(self, timestamp=""):
        '''
        Enumerate computer objects. This function is only used if no
        collection was requested that required connecting to computers anyway.
        '''
        filename = timestamp + 'computers.json'

        acl = 'acl' in self.collect
        entries = self.addc.ad.computers.values()

        logging.debug('Writing computers ACL to file: %s', filename)

        # Use a separate queue for processing the results
        self.result_q = queue.Queue()
        results_worker = threading.Thread(
            target=OutputWorker.membership_write_worker,
            args=(self.result_q, 'computers', filename))
        results_worker.daemon = True
        results_worker.start()

        if acl and not self.disable_pooling:
            self.aclenumerator.init_pool()

        # This loops over the cached entries
        for entry in entries:
            if not 'attributes' in entry:
                continue

            if 'dNSHostName' not in entry['attributes']:
                continue

            hostname = entry['attributes']['dNSHostName']
            if not hostname:
                continue
            samname = entry['attributes']['sAMAccountName']

            cobject = ADComputer(hostname=hostname,
                                 samname=samname,
                                 ad=self.addomain,
                                 addc=self.addc,
                                 objectsid=entry['attributes']['objectSid'])
            cobject.primarygroup = MembershipEnumerator.get_primary_membership(
                entry)
            computer = cobject.get_bloodhound_data(entry,
                                                   self.collect,
                                                   skip_acl=True)

            # If we are enumerating ACLs, we break out of the loop here
            # this is because parsing ACLs is computationally heavy and therefor is done in subprocesses
            if acl:
                if self.disable_pooling:
                    # Debug mode, don't run this pooled since it hides exceptions
                    self.process_acldata(
                        parse_binary_acl(
                            computer, 'computer',
                            ADUtils.get_entry_property(entry,
                                                       'nTSecurityDescriptor',
                                                       raw=True),
                            self.addc.objecttype_guid_map))
                else:
                    # Process ACLs in separate processes, then call the processing function to resolve entries and write them to file
                    self.aclenumerator.pool.apply_async(
                        parse_binary_acl,
                        args=(computer, 'computer',
                              ADUtils.get_entry_property(
                                  entry, 'nTSecurityDescriptor',
                                  raw=True), self.addc.objecttype_guid_map),
                        callback=self.process_acldata)
            else:
                # Write it to the queue -> write to file in separate thread
                # this is solely for consistency with acl parsing, the performance improvement is probably minimal
                self.result_q.put(computer)

        # If we are parsing ACLs, close the parsing pool first
        # then close the result queue and join it
        if acl and not self.disable_pooling:
            self.aclenumerator.pool.close()
            self.aclenumerator.pool.join()
            self.result_q.put(None)
        else:
            self.result_q.put(None)
        self.result_q.join()

        logging.debug('Finished writing computers')
Exemplo n.º 24
0
    def enumerate_groups(self, timestamp=""):

        highvalue = [
            "S-1-5-32-544", "S-1-5-32-550", "S-1-5-32-549", "S-1-5-32-551",
            "S-1-5-32-548"
        ]

        def is_highvalue(sid):
            if sid.endswith("-512") or sid.endswith("-516") or sid.endswith(
                    "-519") or sid.endswith("-520"):
                return True
            if sid in highvalue:
                return True
            return False

        # Should we include extra properties in the query?
        with_properties = 'objectprops' in self.collect
        acl = 'acl' in self.collect

        filename = timestamp + 'groups.json'
        entries = self.addc.get_groups(include_properties=with_properties,
                                       acl=acl)

        logging.debug('Writing groups to file: %s', filename)

        # Use a separate queue for processing the results
        self.result_q = queue.Queue()
        results_worker = threading.Thread(
            target=OutputWorker.membership_write_worker,
            args=(self.result_q, 'groups', filename))
        results_worker.daemon = True
        results_worker.start()

        if acl and not self.disable_pooling:
            self.aclenumerator.init_pool()

        for entry in entries:
            resolved_entry = ADUtils.resolve_ad_entry(entry)
            self.addomain.groups[entry['dn']] = resolved_entry
            try:
                sid = entry['attributes']['objectSid']
            except KeyError:
                #Somehow we found a group without a sid?
                logging.warning('Could not determine SID for group %s',
                                entry['attributes']['distinguishedName'])
                continue
            group = {
                "ObjectIdentifier":
                sid,
                "Properties": {
                    "domain":
                    self.addomain.domain.upper(),
                    "domainsid":
                    self.addomain.domain_object.sid,
                    "name":
                    resolved_entry['principal'],
                    "distinguishedname":
                    ADUtils.get_entry_property(entry,
                                               'distinguishedName').upper()
                },
                "Members": [],
                "Aces": [],
                "IsDeleted":
                ADUtils.get_entry_property(entry, 'isDeleted', default=False)
            }
            if sid in ADUtils.WELLKNOWN_SIDS:
                # Prefix it with the domain
                group['ObjectIdentifier'] = '%s-%s' % (
                    self.addomain.domain.upper(), sid)
            if with_properties:
                group['Properties']['admincount'] = ADUtils.get_entry_property(
                    entry, 'adminCount', default=0) == 1
                group['Properties'][
                    'description'] = ADUtils.get_entry_property(
                        entry, 'description')
                whencreated = ADUtils.get_entry_property(entry,
                                                         'whencreated',
                                                         default=0)
                group['Properties']['whencreated'] = calendar.timegm(
                    whencreated.timetuple())

            for member in entry['attributes']['member']:
                resolved_member = self.get_membership(member)
                if resolved_member:
                    group['Members'].append(resolved_member)

            # If we are enumerating ACLs, we break out of the loop here
            # this is because parsing ACLs is computationally heavy and therefor is done in subprocesses
            if acl:
                if self.disable_pooling:
                    # Debug mode, don't run this pooled since it hides exceptions
                    self.process_acldata(
                        parse_binary_acl(
                            group, 'group',
                            ADUtils.get_entry_property(entry,
                                                       'nTSecurityDescriptor',
                                                       raw=True),
                            self.addc.objecttype_guid_map))
                else:
                    # Process ACLs in separate processes, then call the processing function to resolve entries and write them to file
                    self.aclenumerator.pool.apply_async(
                        parse_binary_acl,
                        args=(group, 'group',
                              ADUtils.get_entry_property(
                                  entry, 'nTSecurityDescriptor',
                                  raw=True), self.addc.objecttype_guid_map),
                        callback=self.process_acldata)
            else:
                # Write it to the queue -> write to file in separate thread
                # this is solely for consistency with acl parsing, the performance improvement is probably minimal
                self.result_q.put(group)

        self.write_default_groups()

        # If we are parsing ACLs, close the parsing pool first
        # then close the result queue and join it
        if acl and not self.disable_pooling:
            self.aclenumerator.pool.close()
            self.aclenumerator.pool.join()
            self.result_q.put(None)
        else:
            self.result_q.put(None)
        self.result_q.join()

        logging.debug('Finished writing groups')
Exemplo n.º 25
0
    def enumerate_users(self, timestamp=""):
        filename = timestamp + 'users.json'

        # Should we include extra properties in the query?
        with_properties = 'objectprops' in self.collect
        acl = 'acl' in self.collect
        entries = self.addc.get_users(include_properties=with_properties,
                                      acl=acl)

        logging.debug('Writing users to file: %s', filename)

        # Use a separate queue for processing the results
        self.result_q = queue.Queue()
        results_worker = threading.Thread(
            target=OutputWorker.membership_write_worker,
            args=(self.result_q, 'users', filename))
        results_worker.daemon = True
        results_worker.start()

        if acl and not self.disable_pooling:
            self.aclenumerator.init_pool()

        # This loops over a generator, results are fetched from LDAP on the go
        for entry in entries:
            resolved_entry = ADUtils.resolve_ad_entry(entry)
            # Skip trust objects
            if resolved_entry['type'] == 'trustaccount':
                continue
            user = {
                "AllowedToDelegate": [],
                "ObjectIdentifier":
                ADUtils.get_entry_property(entry, 'objectSid'),
                "PrimaryGroupSID":
                MembershipEnumerator.get_primary_membership(entry),
                "Properties": {
                    "name":
                    resolved_entry['principal'],
                    "domain":
                    self.addomain.domain.upper(),
                    "domainsid":
                    self.addomain.domain_object.sid,
                    "distinguishedname":
                    ADUtils.get_entry_property(entry,
                                               'distinguishedName').upper(),
                    "unconstraineddelegation":
                    ADUtils.get_entry_property(
                        entry, 'userAccountControl', default=0)
                    & 0x00080000 == 0x00080000,
                    "trustedtoauth":
                    ADUtils.get_entry_property(
                        entry, 'userAccountControl', default=0)
                    & 0x01000000 == 0x01000000,
                    "passwordnotreqd":
                    ADUtils.get_entry_property(
                        entry, 'userAccountControl', default=0)
                    & 0x00000020 == 0x00000020
                },
                "Aces": [],
                "SPNTargets": [],
                "HasSIDHistory": [],
                "IsDeleted":
                ADUtils.get_entry_property(entry, 'isDeleted', default=False)
            }

            if with_properties:
                MembershipEnumerator.add_user_properties(user, entry)
                if 'allowedtodelegate' in user['Properties']:
                    for host in user['Properties']['allowedtodelegate']:
                        try:
                            target = host.split('/')[1]
                        except IndexError:
                            logging.warning('Invalid delegation target: %s',
                                            host)
                            continue
                        try:
                            sid = self.addomain.computersidcache.get(
                                target.lower())
                            user['AllowedToDelegate'].append(sid)
                        except KeyError:
                            if '.' in target:
                                user['AllowedToDelegate'].append(
                                    target.upper())
                # Parse SID history
                if len(user['Properties']['sidhistory']) > 0:
                    for historysid in user['Properties']['sidhistory']:
                        user['HasSIDHistory'].append(
                            self.aceresolver.resolve_sid(historysid))

            # If this is a GMSA, process it's ACL. We don't bother with threads/processes here
            # since these accounts shouldn't be that common and neither should they have very complex
            # DACLs which control who can read their password
            if ADUtils.get_entry_property(
                    entry, 'msDS-GroupMSAMembership', default=b'',
                    raw=True) != b'':
                self.parse_gmsa(user, entry)

            self.addomain.users[entry['dn']] = resolved_entry
            # If we are enumerating ACLs, we break out of the loop here
            # this is because parsing ACLs is computationally heavy and therefor is done in subprocesses
            if acl:
                if self.disable_pooling:
                    # Debug mode, don't run this pooled since it hides exceptions
                    self.process_acldata(
                        parse_binary_acl(
                            user, 'user',
                            ADUtils.get_entry_property(entry,
                                                       'nTSecurityDescriptor',
                                                       raw=True),
                            self.addc.objecttype_guid_map))
                else:
                    # Process ACLs in separate processes, then call the processing function to resolve entries and write them to file
                    self.aclenumerator.pool.apply_async(
                        parse_binary_acl,
                        args=(user, 'user',
                              ADUtils.get_entry_property(
                                  entry, 'nTSecurityDescriptor',
                                  raw=True), self.addc.objecttype_guid_map),
                        callback=self.process_acldata)
            else:
                # Write it to the queue -> write to file in separate thread
                # this is solely for consistency with acl parsing, the performance improvement is probably minimal
                self.result_q.put(user)

        self.write_default_users()

        # If we are parsing ACLs, close the parsing pool first
        # then close the result queue and join it
        if acl and not self.disable_pooling:
            self.aclenumerator.pool.close()
            self.aclenumerator.pool.join()
            self.result_q.put(None)
        else:
            self.result_q.put(None)
        self.result_q.join()

        logging.debug('Finished writing users')
Exemplo n.º 26
0
 def add_user_properties(user, entry):
     """
     Resolve properties for user objects
     """
     props = user['Properties']
     # print entry
     # Is user enabled? Checked by seeing if the UAC flag 2 (ACCOUNT_DISABLED) is not set
     props['enabled'] = ADUtils.get_entry_property(
         entry, 'userAccountControl', default=0) & 2 == 0
     props['lastlogon'] = ADUtils.win_timestamp_to_unix(
         ADUtils.get_entry_property(entry, 'lastLogon', default=0,
                                    raw=True))
     props['lastlogontimestamp'] = ADUtils.win_timestamp_to_unix(
         ADUtils.get_entry_property(entry,
                                    'lastlogontimestamp',
                                    default=0,
                                    raw=True))
     if props['lastlogontimestamp'] == 0:
         props['lastlogontimestamp'] = -1
     props['pwdlastset'] = ADUtils.win_timestamp_to_unix(
         ADUtils.get_entry_property(entry,
                                    'pwdLastSet',
                                    default=0,
                                    raw=True))
     props['dontreqpreauth'] = ADUtils.get_entry_property(
         entry, 'userAccountControl', default=0) & 0x00400000 == 0x00400000
     props['pwdneverexpires'] = ADUtils.get_entry_property(
         entry, 'userAccountControl', default=0) & 0x00010000 == 0x00010000
     props['sensitive'] = ADUtils.get_entry_property(
         entry, 'userAccountControl', default=0) & 0x00100000 == 0x00100000
     props['serviceprincipalnames'] = ADUtils.get_entry_property(
         entry, 'servicePrincipalName', [])
     props['hasspn'] = len(props['serviceprincipalnames']) > 0
     props['displayname'] = ADUtils.get_entry_property(entry, 'displayName')
     props['email'] = ADUtils.get_entry_property(entry, 'mail')
     props['title'] = ADUtils.get_entry_property(entry, 'title')
     props['homedirectory'] = ADUtils.get_entry_property(
         entry, 'homeDirectory')
     props['description'] = ADUtils.get_entry_property(entry, 'description')
     props['userpassword'] = ADUtils.ensure_string(
         ADUtils.get_entry_property(entry, 'userPassword'))
     props['admincount'] = ADUtils.get_entry_property(
         entry, 'adminCount', 0) == 1
     if len(
             ADUtils.get_entry_property(entry, 'msDS-AllowedToDelegateTo',
                                        [])) > 0:
         props['allowedtodelegate'] = ADUtils.get_entry_property(
             entry, 'msDS-AllowedToDelegateTo', [])
     props['sidhistory'] = [
         LDAP_SID(bsid).formatCanonical()
         for bsid in ADUtils.get_entry_property(entry, 'sIDHistory', [])
     ]
     # v4 props
     whencreated = ADUtils.get_entry_property(entry,
                                              'whencreated',
                                              default=0)
     if isinstance(whencreated, int):
         props['whencreated'] = whencreated
     else:
         props['whencreated'] = calendar.timegm(whencreated.timetuple())
     props['unixpassword'] = ADUtils.ensure_string(
         ADUtils.get_entry_property(entry, 'unixuserpassword'))
     props['unicodepassword'] = ADUtils.ensure_string(
         ADUtils.get_entry_property(entry, 'unicodepwd'))
     # Non-default schema?
     # props['sfupassword'] = ADUtils.ensure_string(ADUtils.get_entry_property(entry, 'msSFU30Password'))
     props['sfupassword'] = None
Exemplo n.º 27
0
    def get_bloodhound_data(self, entry, collect, skip_acl=False):
        data = {
            'ObjectIdentifier':
            self.objectsid,
            'AllowedToAct': [],
            'PrimaryGroupSID':
            self.primarygroup,
            'LocalAdmins': {
                'Collected': 'localadmin' in collect
                and not self.permanentfailure,
                'FailureReason': None,
                'Results': self.admins,
            },
            'PSRemoteUsers': {
                'Collected': 'psremote' in collect
                and not self.permanentfailure,
                'FailureReason': None,
                'Results': self.psremote
            },
            'Properties': {
                'name':
                self.hostname.upper(),
                'domainsid':
                self.ad.domain_object.sid,
                'domain':
                self.ad.domain.upper(),
                'distinguishedname':
                ADUtils.get_entry_property(entry, 'distinguishedName').upper()
            },
            'RemoteDesktopUsers': {
                'Collected': 'rdp' in collect and not self.permanentfailure,
                'FailureReason': None,
                'Results': self.rdp
            },
            'DcomUsers': {
                'Collected': 'dcom' in collect and not self.permanentfailure,
                'FailureReason': None,
                'Results': self.dcom
            },
            'AllowedToDelegate': [],
            'Sessions': {
                'Collected': 'session' in collect
                and not self.permanentfailure,
                'FailureReason': None,
                'Results': self.sessions
            },
            'PrivilegedSessions': {
                'Collected': 'loggedon' in collect
                and not self.permanentfailure,
                'FailureReason': None,
                'Results': self.loggedon
            },
            # Unsupported for now
            'RegistrySessions': {
                'Collected': False,
                'FailureReason': None,
                'Results': []
            },
            'Aces': [],
            'HasSIDHistory': [],
            'IsDeleted':
            ADUtils.get_entry_property(entry, 'isDeleted', default=False),
            'Status':
            None
        }
        props = data['Properties']
        # via the TRUSTED_FOR_DELEGATION (0x00080000) flag in UAC
        props['unconstraineddelegation'] = ADUtils.get_entry_property(
            entry, 'userAccountControl', default=0) & 0x00080000 == 0x00080000
        props['enabled'] = ADUtils.get_entry_property(
            entry, 'userAccountControl', default=0) & 2 == 0
        props['trustedtoauth'] = ADUtils.get_entry_property(
            entry, 'userAccountControl', default=0) & 0x01000000 == 0x01000000

        if 'objectprops' in collect or 'acl' in collect:
            props['haslaps'] = ADUtils.get_entry_property(
                entry, 'ms-mcs-admpwdexpirationtime', 0) != 0

        if 'objectprops' in collect:
            props['lastlogon'] = ADUtils.win_timestamp_to_unix(
                ADUtils.get_entry_property(entry,
                                           'lastlogon',
                                           default=0,
                                           raw=True))
            props['lastlogontimestamp'] = ADUtils.win_timestamp_to_unix(
                ADUtils.get_entry_property(entry,
                                           'lastlogontimestamp',
                                           default=0,
                                           raw=True))
            if props['lastlogontimestamp'] == 0:
                props['lastlogontimestamp'] = -1
            props['pwdlastset'] = ADUtils.win_timestamp_to_unix(
                ADUtils.get_entry_property(entry,
                                           'pwdLastSet',
                                           default=0,
                                           raw=True))
            whencreated = ADUtils.get_entry_property(entry,
                                                     'whencreated',
                                                     default=0)
            if not isinstance(whencreated, int):
                whencreated = calendar.timegm(whencreated.timetuple())
            props['whencreated'] = whencreated
            props['serviceprincipalnames'] = ADUtils.get_entry_property(
                entry, 'servicePrincipalName', [])
            props['description'] = ADUtils.get_entry_property(
                entry, 'description')
            props['operatingsystem'] = ADUtils.get_entry_property(
                entry, 'operatingSystem')
            # Add SP to OS if specified
            servicepack = ADUtils.get_entry_property(
                entry, 'operatingSystemServicePack')
            if servicepack:
                props['operatingsystem'] = '%s %s' % (props['operatingsystem'],
                                                      servicepack)
            props['sidhistory'] = [
                LDAP_SID(bsid).formatCanonical()
                for bsid in ADUtils.get_entry_property(entry, 'sIDHistory', [])
            ]
            delegatehosts = ADUtils.get_entry_property(
                entry, 'msDS-AllowedToDelegateTo', [])
            for host in delegatehosts:
                try:
                    target = host.split('/')[1]
                except IndexError:
                    logging.warning('Invalid delegation target: %s', host)
                    continue
                try:
                    sid = self.ad.computersidcache.get(target.lower())
                    data['AllowedToDelegate'].append(sid)
                except KeyError:
                    if '.' in target:
                        data['AllowedToDelegate'].append(target.upper())
            if len(delegatehosts) > 0:
                props['allowedtodelegate'] = delegatehosts

            # Process resource-based constrained delegation
            _, aces = parse_binary_acl(
                data, 'computer',
                ADUtils.get_entry_property(
                    entry,
                    'msDS-AllowedToActOnBehalfOfOtherIdentity',
                    raw=True), self.addc.objecttype_guid_map)
            outdata = self.aceresolver.resolve_aces(aces)
            for delegated in outdata:
                if delegated['RightName'] == 'Owner':
                    continue
                if delegated['RightName'] == 'GenericAll':
                    data['AllowedToAct'].append({
                        'ObjectIdentifier':
                        delegated['PrincipalSID'],
                        'ObjectType':
                        delegated['PrincipalType']
                    })

        # Run ACL collection if this was not already done centrally
        if 'acl' in collect and not skip_acl:
            _, aces = parse_binary_acl(
                data, 'computer',
                ADUtils.get_entry_property(entry,
                                           'nTSecurityDescriptor',
                                           raw=True),
                self.addc.objecttype_guid_map)
            # Parse aces
            data['Aces'] = self.aceresolver.resolve_aces(aces)

        return data
Exemplo n.º 28
0
    def rpc_resolve_sids(self):
        """
        Resolve any remaining unknown SIDs for local administrator accounts.
        """
        # If all sids were already cached, we can just return
        if len(self.admin_sids) == 0:
            return
        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']

        # We could look up the SIDs all at once, but if not all SIDs are mapped, we don't know which
        # ones were resolved and which not, making it impossible to map them in the cache.
        # Therefor we use more SAMR calls at the start, but after a while most SIDs will be reliable
        # in our cache and this function doesn't even need to get called anymore.
        for sid_string in self.admin_sids:
            try:
                resp = lsat.hLsarLookupSids(dce, policyHandle, [sid_string], lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
            except DCERPCException as e:
                if str(e).find('STATUS_NONE_MAPPED') >= 0:
                    logging.warning('SID %s lookup failed, return status: STATUS_NONE_MAPPED', sid_string)
                    # Try next SID
                    continue
                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']:
                domains.append(entry['Name'])

            for entry in resp['TranslatedNames']['Names']:
                domain = domains[entry['DomainIndex']]
                domain_entry = self.ad.get_domain_by_name(domain)
                if domain_entry is not None:
                    domain = ADUtils.ldap2domain(domain_entry['attributes']['distinguishedName'])
                # TODO: what if it isn't? Should we fall back to LDAP?

                if entry['Name'] != '':
                    resolved_entry = ADUtils.resolve_sid_entry(entry, domain)
                    logging.debug('Resolved SID to name: %s', resolved_entry['principal'])
                    self.admins.append({'Name': resolved_entry['principal'],
                                        'Type': resolved_entry['type'].capitalize()})
                    # Add it to our cache
                    self.ad.sidcache.put(sid_string, resolved_entry)
                else:
                    logging.warning('Resolved name is empty [%s]', entry)

        dce.disconnect()
Exemplo n.º 29
0
    def enumerate_groups(self):

        highvalue = [
            "S-1-5-32-544", "S-1-5-32-550", "S-1-5-32-549", "S-1-5-32-551",
            "S-1-5-32-548"
        ]

        def is_highvalue(sid):
            if sid.endswith("-512") or sid.endswith("-516") or sid.endswith(
                    "-519") or sid.endswith("-520"):
                return True
            if sid in highvalue:
                return True
            return False

        # Should we include extra properties in the query?
        with_properties = 'objectprops' in self.collect
        acl = 'acl' in self.collect

        filename = 'groups.json'
        entries = self.addc.get_groups(include_properties=with_properties,
                                       acl=acl)

        # If the logging level is DEBUG, we ident the objects
        if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
            indent_level = 1
        else:
            indent_level = None

        try:
            out = codecs.open(filename, 'w', 'utf-8')
        except:
            logging.warning('Could not write file: %s' % filename)
            return

        logging.debug('Writing groups to file: %s' % filename)

        # Initialize json header
        out.write('{"groups":[')

        num_entries = 0
        for entry in entries:
            resolved_entry = ADUtils.resolve_ad_entry(entry)
            self.addomain.groups[entry['dn']] = resolved_entry
            try:
                sid = entry['attributes']['objectSid']
            except KeyError:
                #Somehow we found a group without a sid?
                logging.warning('Could not determine SID for group %s' %
                                entry['attributes']['distinguishedName'])
                continue
            group = {
                "Name": resolved_entry['principal'],
                "Properties": {
                    "domain": self.addomain.domain,
                    "objectsid": sid,
                    "highvalue": is_highvalue(sid)
                },
                "Members": []
            }
            if with_properties:
                group['Properties']['admincount'] = ADUtils.get_entry_property(
                    entry, 'adminCount', default=0) == 1
                group['Properties'][
                    'description'] = ADUtils.get_entry_property(
                        entry, 'description')

            for member in entry['attributes']['member']:
                resolved_member = self.get_membership(member)
                if resolved_member:
                    group['Members'].append(resolved_member)

            if num_entries != 0:
                out.write(',')
            json.dump(group, out, indent=indent_level)
            num_entries += 1

        logging.info('Found %d groups', num_entries)
        out.write('],"meta":{"type":"groups","count":%d}}' % num_entries)
        logging.debug('Finished writing groups')
        out.close()
Exemplo n.º 30
0
 def add_user_properties(user, entry):
     props = user['Properties']
     # print entry
     # Is user enabled? Checked by seeing if the UAC flag 2 (ACCOUNT_DISABLED) is not set
     props['enabled'] = ADUtils.get_entry_property(
         entry, 'userAccountControl', default=0) & 2 == 0
     props['lastlogon'] = ADUtils.win_timestamp_to_unix(
         ADUtils.get_entry_property(entry, 'lastLogon', default=0,
                                    raw=True))
     props['pwdlastset'] = ADUtils.win_timestamp_to_unix(
         ADUtils.get_entry_property(entry,
                                    'pwdLastSet',
                                    default=0,
                                    raw=True))
     props['serviceprincipalnames'] = ADUtils.get_entry_property(
         entry, 'servicePrincipalName', [])
     props['hasspn'] = len(props['serviceprincipalnames']) > 0
     props['displayname'] = ADUtils.get_entry_property(entry, 'displayName')
     props['email'] = ADUtils.get_entry_property(entry, 'mail')
     props['title'] = ADUtils.get_entry_property(entry, 'title')
     props['homedirectory'] = ADUtils.get_entry_property(
         entry, 'homeDirectory')
     props['description'] = ADUtils.get_entry_property(entry, 'description')
     props['userpassword'] = ADUtils.get_entry_property(
         entry, 'userPassword')
     props['admincount'] = ADUtils.get_entry_property(
         entry, 'adminCount', 0) == 1