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