101 lines
4.1 KiB
Python
101 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from impacket.ldap import ldapasn1 as ldapasn1_impacket
|
|
|
|
class NXCModule:
|
|
'''
|
|
Module by CyberCelt: @Cyb3rC3lt
|
|
|
|
Initial module:
|
|
https://github.com/Cyb3rC3lt/CrackMapExec-Modules
|
|
'''
|
|
|
|
name = 'group-mem'
|
|
description = 'Retrieves all the members within a Group'
|
|
supported_protocols = ['ldap']
|
|
opsec_safe = True
|
|
multiple_hosts = False
|
|
primaryGroupID = ''
|
|
answers = []
|
|
|
|
def options(self, context, module_options):
|
|
'''
|
|
group-mem: Specify group-mem to call the module
|
|
GROUP: Specify the GROUP option to query for that group's members
|
|
Usage: nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain admins"
|
|
nxc ldap $DC-IP -u Username -p Password -M group-mem -o GROUP="domain controllers"
|
|
'''
|
|
|
|
self.GROUP = ''
|
|
|
|
if 'GROUP' in module_options:
|
|
self.GROUP = module_options['GROUP']
|
|
else:
|
|
context.log.error('GROUP option is required!')
|
|
exit(1)
|
|
|
|
def on_login(self, context, connection):
|
|
|
|
#First look up the SID of the group passed in
|
|
searchFilter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
|
|
attribute = "objectSid"
|
|
|
|
searchResult = doSearch(self, context, connection, searchFilter, attribute)
|
|
#If no SID for the Group is returned exit the program
|
|
if searchResult is None:
|
|
context.log.success('Unable to find any members of the "' + self.GROUP + '" group')
|
|
return True
|
|
|
|
# Convert the binary SID to a primaryGroupID string to be used further
|
|
sidString = connection.sid_to_str(searchResult).split("-")
|
|
self.primaryGroupID = sidString[-1]
|
|
|
|
#Look up the groups DN
|
|
searchFilter = "(&(objectCategory=group)(cn=" + self.GROUP + "))"
|
|
attribute = "distinguishedName"
|
|
distinguishedName = (doSearch(self, context, connection, searchFilter, attribute)).decode("utf-8")
|
|
|
|
# Carry out the search
|
|
searchFilter = "(|(memberOf="+distinguishedName+")(primaryGroupID="+self.primaryGroupID+"))"
|
|
attribute = "sAMAccountName"
|
|
searchResult = doSearch(self, context, connection, searchFilter, attribute)
|
|
|
|
if len(self.answers) > 0:
|
|
context.log.success('Found the following members of the ' + self.GROUP + ' group:')
|
|
for answer in self.answers:
|
|
context.log.highlight(u'{}'.format(answer[0]))
|
|
|
|
# Carry out an LDAP search for the Group with the supplied Group name
|
|
def doSearch(self,context, connection,searchFilter,attributeName):
|
|
try:
|
|
context.log.debug('Search Filter=%s' % searchFilter)
|
|
resp = connection.ldapConnection.search(searchFilter=searchFilter,
|
|
attributes=[attributeName],
|
|
sizeLimit=0)
|
|
context.log.debug('Total no. of records returned %d' % len(resp))
|
|
for item in resp:
|
|
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
|
|
continue
|
|
attributeValue = '';
|
|
try:
|
|
for attribute in item['attributes']:
|
|
if str(attribute['type']) == attributeName:
|
|
if attributeName == "objectSid":
|
|
attributeValue = bytes(attribute['vals'][0])
|
|
return attributeValue;
|
|
elif attributeName == "distinguishedName":
|
|
attributeValue = bytes(attribute['vals'][0])
|
|
return attributeValue;
|
|
else:
|
|
attributeValue = str(attribute['vals'][0])
|
|
if attributeValue is not None:
|
|
self.answers.append([attributeValue])
|
|
except Exception as e:
|
|
context.log.debug("Exception:", exc_info=True)
|
|
context.log.debug('Skipping item, cannot process due to error %s' % str(e))
|
|
pass
|
|
except Exception as e:
|
|
context.log.debug("Exception:", e)
|
|
return False
|