github.com/n00py/Slackor@v0.0.0-20200610224921-d007fcea1740/impacket/examples/GetADUsers.py (about) 1 #!/usr/bin/env python 2 # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 3 # 4 # This software is provided under under a slightly modified version 5 # of the Apache Software License. See the accompanying LICENSE file 6 # for more information. 7 # 8 # Author: 9 # Alberto Solino (@agsolino) 10 # 11 # Description: 12 # This script will gather data about the domain's users and their corresponding email addresses. It will also 13 # include some extra information about last logon and last password set attributes. 14 # You can enable or disable the the attributes shown in the final table by changing the values in line 184 and 15 # headers in line 190. 16 # If no entries are returned that means users don't have email addresses specified. If so, you can use the 17 # -all-users parameter. 18 # 19 # Reference for: 20 # LDAP 21 # 22 from __future__ import division 23 from __future__ import print_function 24 from __future__ import unicode_literals 25 import argparse 26 import logging 27 import sys 28 from datetime import datetime 29 30 from impacket import version 31 from impacket.dcerpc.v5.samr import UF_ACCOUNTDISABLE 32 from impacket.examples import logger 33 from impacket.ldap import ldap, ldapasn1 34 from impacket.smbconnection import SMBConnection 35 36 37 class GetADUsers: 38 def __init__(self, username, password, domain, cmdLineOptions): 39 self.options = cmdLineOptions 40 self.__username = username 41 self.__password = password 42 self.__domain = domain 43 self.__lmhash = '' 44 self.__nthash = '' 45 self.__aesKey = cmdLineOptions.aesKey 46 self.__doKerberos = cmdLineOptions.k 47 self.__target = None 48 self.__kdcHost = cmdLineOptions.dc_ip 49 self.__requestUser = cmdLineOptions.user 50 self.__all = cmdLineOptions.all 51 if cmdLineOptions.hashes is not None: 52 self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':') 53 54 # Create the baseDN 55 domainParts = self.__domain.split('.') 56 self.baseDN = '' 57 for i in domainParts: 58 self.baseDN += 'dc=%s,' % i 59 # Remove last ',' 60 self.baseDN = self.baseDN[:-1] 61 62 # Let's calculate the header and format 63 self.__header = ["Name", "Email", "PasswordLastSet", "LastLogon"] 64 # Since we won't process all rows at once, this will be fixed lengths 65 self.__colLen = [20, 30, 19, 19] 66 self.__outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(self.__colLen)]) 67 68 69 70 def getMachineName(self): 71 if self.__kdcHost is not None: 72 s = SMBConnection(self.__kdcHost, self.__kdcHost) 73 else: 74 s = SMBConnection(self.__domain, self.__domain) 75 try: 76 s.login('', '') 77 except Exception: 78 if s.getServerName() == '': 79 raise 'Error while anonymous logging into %s' 80 else: 81 s.logoff() 82 return s.getServerName() 83 84 @staticmethod 85 def getUnixTime(t): 86 t -= 116444736000000000 87 t /= 10000000 88 return t 89 90 def processRecord(self, item): 91 if isinstance(item, ldapasn1.SearchResultEntry) is not True: 92 return 93 sAMAccountName = '' 94 pwdLastSet = '' 95 mail = '' 96 lastLogon = 'N/A' 97 try: 98 for attribute in item['attributes']: 99 if str(attribute['type']) == 'sAMAccountName': 100 if attribute['vals'][0].asOctets().decode('utf-8').endswith('$') is False: 101 # User Account 102 sAMAccountName = attribute['vals'][0].asOctets().decode('utf-8') 103 elif str(attribute['type']) == 'pwdLastSet': 104 if str(attribute['vals'][0]) == '0': 105 pwdLastSet = '<never>' 106 else: 107 pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) 108 elif str(attribute['type']) == 'lastLogon': 109 if str(attribute['vals'][0]) == '0': 110 lastLogon = '<never>' 111 else: 112 lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) 113 elif str(attribute['type']) == 'mail': 114 mail = str(attribute['vals'][0]) 115 116 print((self.__outputFormat.format(*[sAMAccountName, mail, pwdLastSet, lastLogon]))) 117 except Exception as e: 118 logging.debug("Exception", exc_info=True) 119 logging.error('Skipping item, cannot process due to error %s' % str(e)) 120 pass 121 122 def run(self): 123 if self.__doKerberos: 124 self.__target = self.getMachineName() 125 else: 126 if self.__kdcHost is not None: 127 self.__target = self.__kdcHost 128 else: 129 self.__target = self.__domain 130 131 # Connect to LDAP 132 try: 133 ldapConnection = ldap.LDAPConnection('ldap://%s'%self.__target, self.baseDN, self.__kdcHost) 134 if self.__doKerberos is not True: 135 ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 136 else: 137 ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, 138 self.__aesKey, kdcHost=self.__kdcHost) 139 except ldap.LDAPSessionError as e: 140 if str(e).find('strongerAuthRequired') >= 0: 141 # We need to try SSL 142 ldapConnection = ldap.LDAPConnection('ldaps://%s' % self.__target, self.baseDN, self.__kdcHost) 143 if self.__doKerberos is not True: 144 ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 145 else: 146 ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, 147 self.__aesKey, kdcHost=self.__kdcHost) 148 else: 149 raise 150 151 logging.info('Querying %s for information about domain.' % self.__target) 152 # Print header 153 print((self.__outputFormat.format(*self.__header))) 154 print((' '.join(['-' * itemLen for itemLen in self.__colLen]))) 155 156 # Building the search filter 157 if self.__all: 158 searchFilter = "(&(sAMAccountName=*)(objectCategory=user)" 159 else: 160 searchFilter = "(&(sAMAccountName=*)(mail=*)(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))" % UF_ACCOUNTDISABLE 161 162 if self.__requestUser is not None: 163 searchFilter += '(sAMAccountName:=%s))' % self.__requestUser 164 else: 165 searchFilter += ')' 166 167 try: 168 logging.debug('Search Filter=%s' % searchFilter) 169 sc = ldap.SimplePagedResultsControl(size=100) 170 ldapConnection.search(searchFilter=searchFilter, 171 attributes=['sAMAccountName', 'pwdLastSet', 'mail', 'lastLogon'], 172 sizeLimit=0, searchControls = [sc], perRecordCallback=self.processRecord) 173 except ldap.LDAPSearchError: 174 raise 175 176 ldapConnection.close() 177 178 # Process command-line arguments. 179 if __name__ == '__main__': 180 # Init the example's logger theme 181 logger.init() 182 print((version.BANNER)) 183 184 parser = argparse.ArgumentParser(add_help = True, description = "Queries target domain for users data") 185 186 parser.add_argument('target', action='store', help='domain/username[:password]') 187 parser.add_argument('-user', action='store', metavar='username', help='Requests data for specific user ') 188 parser.add_argument('-all', action='store_true', help='Return all users, including those with no email ' 189 'addresses and disabled accounts. When used with -user it ' 190 'will return user\'s info even if the account is disabled') 191 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 192 193 group = parser.add_argument_group('authentication') 194 195 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 196 group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 197 group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 198 '(KRB5CCNAME) based on target parameters. If valid credentials ' 199 'cannot be found, it will use the ones specified in the command ' 200 'line') 201 group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' 202 '(128 or 256 bits)') 203 group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' 204 'ommited it use the domain part (FQDN) ' 205 'specified in the target parameter') 206 207 if len(sys.argv)==1: 208 parser.print_help() 209 sys.exit(1) 210 211 options = parser.parse_args() 212 213 if options.debug is True: 214 logging.getLogger().setLevel(logging.DEBUG) 215 else: 216 logging.getLogger().setLevel(logging.INFO) 217 218 import re 219 # This is because I'm lazy with regex 220 # ToDo: We need to change the regex to fullfil domain/username[:password] 221 targetParam = options.target+'@' 222 domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(targetParam).groups('') 223 224 #In case the password contains '@' 225 if '@' in address: 226 password = password + '@' + address.rpartition('@')[0] 227 address = address.rpartition('@')[2] 228 229 if domain is '': 230 logging.critical('Domain should be specified!') 231 sys.exit(1) 232 233 if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 234 from getpass import getpass 235 password = getpass("Password:") 236 237 if options.aesKey is not None: 238 options.k = True 239 240 try: 241 executer = GetADUsers(username, password, domain, options) 242 executer.run() 243 except Exception as e: 244 if logging.getLogger().level == logging.DEBUG: 245 import traceback 246 traceback.print_exc() 247 print((str(e)))