async def get_tree_plot(self, dn, level=2): """ Returns a dictionary representing a tree starting from 'dn' containing all subtrees. Parameters: dn (str): Distinguished name of the root of the tree level (int): Recursion level Returns: dict """ logger.debug('Tree, dn: %s level: %s' % (dn, level)) tree = {} async for entry, err in self._con.pagedsearch( dn.encode(), query_syntax_converter('(distinguishedName=*)'), attributes=[b'distinguishedName'], paged_size=self.ldap_query_page_size, search_scope=LEVEL, controls=None, ): if err is not None: raise err if level == 0: return {} #print(entry) #print(entry['attributes']['distinguishedName']) if 'distinguishedName' not in entry['attributes'] or entry[ 'attributes']['distinguishedName'] is None or entry[ 'attributes']['distinguishedName'] == []: continue subtree = await self.get_tree_plot( entry['attributes']['distinguishedName'], level=level - 1) tree[entry['attributes']['distinguishedName']] = subtree return {dn: tree}
async def pagedsearch(self, ldap_filter, attributes, controls=None): """ Performs a paged search on the AD, using the filter and attributes as a normal query does. Needs to connect to the server first! Parameters: ldap_filter (str): LDAP query filter attributes (list): Attributes list to recieve in the result controls (obj): Additional control dict Returns: generator """ logger.debug('Paged search, filter: %s attributes: %s' % (ldap_filter, ','.join(attributes))) if self._con.status != MSLDAPClientStatus.RUNNING: if self._con.status == MSLDAPClientStatus.ERROR: print('There was an error in the connection!') return elif self._con.status == MSLDAPClientStatus.ERROR: print('Theconnection is in stopped state!') return if self._tree is None: raise Exception('BIND first!') t = [] for x in attributes: t.append(x.encode()) attributes = t ldap_filter = query_syntax_converter(ldap_filter) t = [] if controls is not None: for control in controls: t.append( Control({ 'controlType': control[0].encode(), 'criticality': control[1], 'controlValue': control[2] })) controls = t async for entry, err in self._con.pagedsearch( self._tree.encode(), ldap_filter, attributes=attributes, paged_size=self.ldap_query_page_size, controls=controls): if err is not None: raise err if entry['objectName'] == '' and entry['attributes'] == '': #searchresref... continue #print('et %s ' % entry) yield entry
async def get_tokengroups(self, dn): """ returns the tokengroups attribute for a given DN """ ldap_filter = query_syntax_converter(r'(distinguishedName=%s)' % escape_filter_chars(dn)) attributes = [b'tokenGroups'] async for entry, err in self._con.pagedsearch( dn.encode(), ldap_filter, attributes=attributes, paged_size=self.ldap_query_page_size, search_scope=BASE, ): if err is not None: yield None, err break #print(entry['attributes']) if 'tokenGroups' in entry: for sid_data in entry['tokenGroups']: yield sid_data
async def get_all_tokengroups(self): """ returns the tokengroups attribute for a given DN """ ldap_filter = r'(|(sAMAccountType=805306369)(objectClass=group)(sAMAccountType=805306368))' async for entry in self.pagedsearch(ldap_filter, attributes=[ 'dn', 'cn', 'objectSid', 'objectClass', 'objectGUID' ]): if 'objectName' in entry: #print(entry['objectName']) async for entry2, err in self._con.pagedsearch( entry['objectName'].encode(), query_syntax_converter( r'(distinguishedName=%s)' % escape_filter_chars(entry['objectName'])), attributes=[b'tokenGroups'], paged_size=self.ldap_query_page_size, search_scope=BASE, ): #print(entry2) if err is not None: yield None, err break if 'tokenGroups' in entry2['attributes']: for token in entry2['attributes']['tokenGroups']: yield { 'cn': entry['attributes']['cn'], 'dn': entry['objectName'], 'guid': entry['attributes']['objectGUID'], 'sid': entry['attributes']['objectSid'], 'type': entry['attributes']['objectClass'][-1], 'token': token }
async def search(self, base, query, attributes, search_scope=2, size_limit=1000, types_only=False, derefAliases=0, timeLimit=None, controls=None, return_done=False): """ Performs the search operation. :param base: base tree on which the search should be performed :type base: str :param query: filter query that defines what should be searched for :type query: str :param attributes: a list of attributes to be included in the response :type attributes: List[str] :param search_scope: Specifies the search operation's scope. Default: 2 (Subtree) :type search_scope: int :param types_only: indicates whether the entries returned should include attribute types only or both types and values. Default: False (both) :type types_only: bool :param size_limit: Size limit of result elements per query. Default: 1000 :type size_limit: int :param derefAliases: Specifies the behavior on how aliases are dereferenced. Default: 0 (never) :type derefAliases: int :param timeLimit: Maximum time the search should take. If time limit reached the server SHOULD return an error :type timeLimit: int :param controls: additional controls to be passed in the query :type controls: dict :param return_done: Controls wether the final 'done' LDAP message should be returned, or just the actual results :type return_done: bool :return: Async generator which yields (`LDAPMessage`, None) tuple on success or (None, `Exception`) on error :rtype: Iterator[(:class:`LDAPMessage`, :class:`Exception`)] """ if self.status != MSLDAPClientStatus.RUNNING: yield None, Exception( 'Connection not running! Probably encountered an error') return try: if timeLimit is None: timeLimit = 600 #not sure flt = query_syntax_converter(query) searchreq = { 'baseObject': base.encode(), 'scope': search_scope, 'derefAliases': derefAliases, 'sizeLimit': size_limit, 'timeLimit': timeLimit, 'typesOnly': types_only, 'filter': flt, 'attributes': attributes, } br = {'searchRequest': SearchRequest(searchreq)} msg = {'protocolOp': protocolOp(br)} if controls is not None: msg['controls'] = controls msg_id = await self.send_message(msg) while True: results = await self.recv_message(msg_id) for message in results: msg_type = message['protocolOp'].name message = message.native if msg_type == 'searchResDone': #print(message) #print('BREAKING!') if return_done is True: yield (message, None) break elif msg_type == 'searchResRef': #TODO: Check if we need to deal with this further continue if return_done is True: yield (message, None) else: yield (convert_result(message['protocolOp']), None) else: continue break except Exception as e: yield (None, e)
async def amain(): import traceback from msldap.commons.url import MSLDAPURLDecoder base = 'DC=TEST,DC=CORP' #ip = 'WIN2019AD' #domain = 'TEST' #username = '******' #password = '******' ##auth_method = LDAPAuthProtocol.SICILY #auth_method = LDAPAuthProtocol.SIMPLE #cred = MSLDAPCredential(domain, username, password , auth_method) #target = MSLDAPTarget(ip) #target.dc_ip = '10.10.10.2' #target.domain = 'TEST' url = 'ldap+kerberos-password://test\\victim:Passw0rd!1@WIN2019AD/?dc=10.10.10.2' dec = MSLDAPURLDecoder(url) cred = dec.get_credential() target = dec.get_target() print(cred) print(target) input() client = MSLDAPClientConnection(target, cred) await client.connect() res, err = await client.bind() if err is not None: raise err #res = await client.search_test_2() #pprint.pprint(res) #search = bytes.fromhex('30840000007702012663840000006e043c434e3d3430392c434e3d446973706c6179537065636966696572732c434e3d436f6e66696775726174696f6e2c44433d746573742c44433d636f72700a01000a010002010002020258010100870b6f626a656374436c61737330840000000d040b6f626a656374436c617373') #msg = LDAPMessage.load(search) qry = r'(sAMAccountName=*)' #'(userAccountControl:1.2.840.113556.1.4.803:=4194304)' #'(sAMAccountName=*)' #qry = r'(sAMAccountType=805306368)' #a = query_syntax_converter(qry) #print(a.native) #input('press bacon!') flt = query_syntax_converter(qry) i = 0 async for res, err in client.pagedsearch(base.encode(), flt, ['*'.encode()], derefAliases=3, typesOnly=False): if err is not None: print('Error!') raise err i += 1 if i % 1000 == 0: print(i) #pprint.pprint(res) await client.disconnect()
qry = r'(sAMAccountName=*)' #'(userAccountControl:1.2.840.113556.1.4.803:=4194304)' #'(sAMAccountName=*)' #qry = r'(sAMAccountType=805306368)' #a = query_syntax_converter(qry) #print(a.native) #input('press bacon!') schema = SchemaInfo.from_json(ad_2012_r2_schema) auto_escape = True auto_encode = True validator = None check_names = False # # res = parse_filter(qry, schema, auto_escape, auto_encode, validator, check_names) print(repr(res)) res = compile_filter(res.elements[0]) # print(repr(res)) print(encoder.encode(res).hex()) #res = encoder.encode(res) #x = Filter.load(res) #pprint(x.native) flt = query_syntax_converter(qry) input(flt.native) #res = await client.search_test_2() #pprint.pprint(res) #search = bytes.fromhex('30840000007702012663840000006e043c434e3d3430392c434e3d446973706c6179537065636966696572732c434e3d436f6e66696775726174696f6e2c44433d746573742c44433d636f72700a01000a010002010002020258010100870b6f626a656374436c61737330840000000d040b6f626a656374436c617373') #msg = LDAPMessage.load(search)