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_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.º 3
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.º 4
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.º 5
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.º 6
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.º 7
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.º 8
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
Exemplo n.º 9
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)

        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)
            user = {
                "Name": resolved_entry['principal'],
                "PrimaryGroup": self.get_primary_membership(entry),
                "Properties": {
                    "domain":
                    self.addomain.domain.upper(),
                    "objectsid":
                    entry['attributes']['objectSid'],
                    "highvalue":
                    False,
                    "unconstraineddelegation":
                    ADUtils.get_entry_property(
                        entry, 'userAccountControl', default=0)
                    & 0x00080000 == 0x00080000
                },
                "Aces": []
            }

            if with_properties:
                MembershipEnumerator.add_user_properties(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_stuff(
                        parse_binary_acl(
                            user, 'user',
                            ADUtils.get_entry_property(entry,
                                                       'nTSecurityDescriptor',
                                                       raw=True)))
                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)),
                        callback=self.process_stuff)
            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)

        # 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.º 10
0
    def get_bloodhound_data(self, entry, collect, skip_acl=False):
        data = {
            'ObjectIdentifier': self.objectsid,
            'AllowedToAct': [],
            'PrimaryGroupSid': self.primarygroup,
            'LocalAdmins': self.admins,
            'PSRemoteUsers': self.psremote,
            'Properties': {
                'name': self.hostname.upper(),
                'objectid': self.objectsid,
                'domain': self.ad.domain.upper(),
                'highvalue': False,
                'distinguishedname': ADUtils.get_entry_property(entry, 'distinguishedName')
            },
            'RemoteDesktopUsers': self.rdp,
            'DcomUsers': self.dcom,
            'AllowedToDelegate': [],
            'Sessions': self.sessions,
            'Aces': []
        }
        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

        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['lastlogontimestamp'] = ADUtils.win_timestamp_to_unix(
                ADUtils.get_entry_property(entry, 'lastlogontimestamp', 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)

            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({'MemberId': delegated['PrincipalSID'], 'MemberType': 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.º 11
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,
                       addc=self.addc,
                       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 'psremote' in self.collect:
                    unresolved = c.rpc_get_group_members(580, c.psremote)
                    c.rpc_resolve_sids(unresolved, c.psremote)
                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()

                if sessions is None:
                    sessions = []

                # Should we use the GC?
                use_gc = self.addomain.num_domains > 1 and self.do_gc_lookup

                # Process found sessions
                for ses in sessions:
                    # For every session, resolve the SAM name in the GC if needed
                    domain = self.addomain.domain
                    try:
                        users = self.addomain.samcache.get(samname)
                    except KeyError:
                        # Look up the SAM name in the GC
                        entries = self.addomain.objectresolver.resolve_samname(
                            ses['user'], use_gc=use_gc)
                        if entries is not None:
                            users = [
                                user['attributes']['objectSid']
                                for user in entries
                            ]
                        if entries is None or users == []:
                            logging.warning(
                                'Failed to resolve SAM name %s in current forest',
                                samname)
                            continue
                        self.addomain.samcache.put(samname, users)

                    # Resolve the IP to obtain the host the session is from
                    try:
                        target = self.addomain.dnscache.get(ses['source'])
                    except KeyError:
                        # TODO: also use discovery based on port 445 connections similar to sharphound
                        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)
                    # Resolve target hostname
                    try:
                        hostsid = self.addomain.computersidcache.get(
                            target.lower())
                    except KeyError:
                        logging.warning(
                            'Could not resolve hostname to SID: %s', target)
                        continue

                    # Put the result on the results queue.
                    for user in users:
                        c.sessions.append({
                            'ComputerId': hostsid,
                            'UserId': user
                        })
                if loggedon is None:
                    loggedon = []

                # Put the logged on users on the queue too
                for user, userdomain in loggedon:
                    # Construct fake UPN to cache this user
                    fupn = '%s@%s' % (user.upper(), userdomain.upper())
                    try:
                        users = self.addomain.samcache.get(fupn)
                    except KeyError:
                        entries = self.addomain.objectresolver.resolve_samname(
                            user, use_gc=use_gc)
                        if entries is not None:
                            if len(entries) > 1:
                                for resolved_user in entries:
                                    edn = ADUtils.get_entry_property(
                                        resolved_user, 'distinguishedName')
                                    edom = ADUtils.ldap2domain(edn).lower()
                                    if edom == userdomain.lower():
                                        users = [
                                            resolved_user['attributes']
                                            ['objectSid']
                                        ]
                                        break
                                    logging.debug(
                                        'Skipping resolved user %s since domain does not match (%s != %s)',
                                        edn, edom, userdomain.lower())
                            else:
                                users = [
                                    resolved_user['attributes']['objectSid']
                                    for resolved_user in entries
                                ]
                        if entries is None or users == []:
                            logging.warning(
                                'Failed to resolve SAM name %s in current forest',
                                samname)
                            continue
                        self.addomain.samcache.put(fupn, users)
                    for resultuser in users:
                        c.sessions.append({
                            'ComputerId': objectsid,
                            'UserId': resultuser
                        })

                # Process Tasks
                for taskuser in tasks:
                    c.sessions.append({
                        'ComputerId': objectsid,
                        'UserId': taskuser
                    })

                # Process Services
                for serviceuser in services:
                    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
                        self.addomain.sidcache.put(
                            serviceuser, userentry['attributes']['objectSid'])
                        user = userentry['attributes']['objectSid']
                    logging.debug('Resolved Service UPN to SID: %s',
                                  user['objectsid'])
                    c.sessions.append({
                        'ComputerId': objectsid,
                        'UserId': user
                    })

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

            except DCERPCException:
                logging.debug(traceback.format_exc())
                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.º 12
0
    def dump_trusts(self, filename='domains.json'):
        """
        Dump trusts. This is currently the only domain info we support, so
        this function handles the entire domain dumping.
        """
        entries = self.addc.get_trusts()

        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

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

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

        # Todo: fix this properly. Current code is quick fix to work with domains
        # that have custom casing in their DN
        domain_object = None
        for domain in self.addomain.domains.keys():
            if domain.lower() == self.addomain.baseDN.lower():
                domain_object = self.addomain.domains[domain]
                break

        if not domain_object:
            logging.error('Could not find domain object. Abortint trust enumeration')
            return

        # Initialize json structure
        datastruct = {
            "domains": [],
            "meta": {
                "type": "domains",
                "count": 0
            }
        }
        # Get functional level
        level_id = ADUtils.get_entry_property(domain_object, 'msds-behavior-version')
        try:
            functional_level = ADUtils.FUNCTIONAL_LEVELS[int(level_id)]
        except KeyError:
            functional_level = 'Unknown'

        domain = {
            "Name": self.addomain.domain,
            "Properties": {
                "highvalue": True,
                "objectsid": domain_object['attributes']['objectSid'],
                "description": ADUtils.get_entry_property(domain_object, 'description'),
                "functionallevel": functional_level
            },
            "Trusts": [],
            # The below is all for GPO collection, unsupported as of now.
            "Links": [],
            "Aces": [],
            "Users": [],
            "Computers": [],
            "ChildOus": []
        }

        num_entries = 0
        for entry in entries:
            num_entries += 1
            # TODO: self.addomain is currently only a single domain. In multi domain mode
            # this might need to be updated
            trust = ADDomainTrust(self.addomain.domain, entry['attributes']['name'], entry['attributes']['trustDirection'], entry['attributes']['trustType'], entry['attributes']['trustAttributes'])
            domain['Trusts'].append(trust.to_output())

        logging.info('Found %u trusts', num_entries)

        # Single domain only
        datastruct['meta']['count'] = 1
        datastruct['domains'].append(domain)
        json.dump(datastruct, out, indent=indent_level)

        logging.debug('Finished writing trusts')
        out.close()
Exemplo n.º 13
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))
     if props['lastlogon'] == 0:
         props['lastlogon'] = -1
     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.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'] = ADUtils.get_entry_property(
         entry, 'sIDHistory', [])
Exemplo n.º 14
0
    def dump_domain(self, collect, filename='domains.json'):
        """
        Dump trusts. This is currently the only domain info we support, so
        this function handles the entire domain dumping.
        """
        if 'trusts' in collect:
            entries = self.addc.get_trusts()
        else:
            entries = []

        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

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

        # Todo: fix this properly. Current code is quick fix to work with domains
        # that have custom casing in their DN
        domain_object = None
        for domain in self.addomain.domains.keys():
            if domain.lower() == self.addomain.baseDN.lower():
                domain_object = self.addomain.domains[domain]
                break

        if not domain_object:
            logging.error(
                'Could not find domain object. Aborting domain enumeration')
            return

        # Initialize json structure
        datastruct = {
            "domains": [],
            "meta": {
                "type": "domains",
                "count": 0,
                "version": 3
            }
        }
        # Get functional level
        level_id = ADUtils.get_entry_property(domain_object,
                                              'msds-behavior-version')
        try:
            functional_level = ADUtils.FUNCTIONAL_LEVELS[int(level_id)]
        except KeyError:
            functional_level = 'Unknown'

        domain = {
            "ObjectIdentifier": domain_object['attributes']['objectSid'],
            "Properties": {
                "name":
                self.addomain.domain.upper(),
                "domain":
                self.addomain.domain.upper(),
                "highvalue":
                True,
                "objectid":
                ADUtils.get_entry_property(domain_object, 'objectSid'),
                "distinguishedname":
                ADUtils.get_entry_property(domain_object, 'distinguishedName'),
                "description":
                ADUtils.get_entry_property(domain_object, 'description'),
                "functionallevel":
                functional_level
            },
            "Trusts": [],
            "Aces": [],
            # The below is all for GPO collection, unsupported as of now.
            "Links": [],
            "Users": [],
            "Computers": [],
            "ChildOus": []
        }

        if 'acl' in collect:
            resolver = AceResolver(self.addomain, self.addomain.objectresolver)
            _, aces = parse_binary_acl(
                domain, 'domain',
                ADUtils.get_entry_property(domain_object,
                                           'nTSecurityDescriptor'),
                self.addc.objecttype_guid_map)
            domain['Aces'] = resolver.resolve_aces(aces)

        if 'trusts' in collect:
            num_entries = 0
            for entry in entries:
                num_entries += 1
                trust = ADDomainTrust(
                    ADUtils.get_entry_property(entry, 'name'),
                    ADUtils.get_entry_property(entry, 'trustDirection'),
                    ADUtils.get_entry_property(entry, 'trustType'),
                    ADUtils.get_entry_property(entry, 'trustAttributes'),
                    ADUtils.get_entry_property(entry, 'securityIdentifier'))
                domain['Trusts'].append(trust.to_output())

            logging.info('Found %u trusts', num_entries)

        # Single domain only
        datastruct['meta']['count'] = 1
        datastruct['domains'].append(domain)
        json.dump(datastruct, out, indent=indent_level)

        logging.debug('Finished writing domain info')
        out.close()
Exemplo n.º 15
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.º 16
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.º 17
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