diff --git a/config/app_cache_poison.cfg b/config/app_cache_poison.cfg index 703c877..e30ee93 100644 --- a/config/app_cache_poison.cfg +++ b/config/app_cache_poison.cfg @@ -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 diff --git a/libs/responder/Responder.py b/libs/responder/Responder.py index 6f617ef..19d00fd 100755 --- a/libs/responder/Responder.py +++ b/libs/responder/Responder.py @@ -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 diff --git a/libs/sslstripkoto/ResponseTampererFactory.py b/libs/sslstripkoto/ResponseTampererFactory.py index 16d271c..ef15559 100644 --- a/libs/sslstripkoto/ResponseTampererFactory.py +++ b/libs/sslstripkoto/ResponseTampererFactory.py @@ -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)) diff --git a/libs/sslstripplus/CookieCleaner.py b/libs/sslstripplus/CookieCleaner.py new file mode 100644 index 0000000..591584a --- /dev/null +++ b/libs/sslstripplus/CookieCleaner.py @@ -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 + + diff --git a/libs/sslstripplus/DnsCache.py b/libs/sslstripplus/DnsCache.py new file mode 100644 index 0000000..91931a4 --- /dev/null +++ b/libs/sslstripplus/DnsCache.py @@ -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) diff --git a/libs/sslstripplus/ServerConnectionFactory.py b/libs/sslstripplus/ServerConnectionFactory.py new file mode 100644 index 0000000..793bdc6 --- /dev/null +++ b/libs/sslstripplus/ServerConnectionFactory.py @@ -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() + diff --git a/libs/sslstripplus/URLMonitor.py b/libs/sslstripplus/URLMonitor.py index 956f3b9..1ebe36e 100644 --- a/libs/sslstripplus/URLMonitor.py +++ b/libs/sslstripplus/URLMonitor.py @@ -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)] diff --git a/plugins/AppCachePoison.py b/plugins/AppCachePoison.py index a81313d..f6474a5 100644 --- a/plugins/AppCachePoison.py +++ b/plugins/AppCachePoison.py @@ -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" @@ -13,6 +12,6 @@ class AppCachePlugin(Plugin): '''Called if plugin is enabled, passed the options namespace''' self.options = options self.config_file = "./config/app_cache_poison.cfg" - + print "[*] App Cache Poison plugin online" - ResponseTampererFactory.buildTamperer(self.config_file) + ResponseTampererFactory().buildTamperer(self.config_file) diff --git a/plugins/Responder.py b/plugins/Responder.py index be3cd09..03732c0 100644 --- a/plugins/Responder.py +++ b/plugins/Responder.py @@ -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") \ No newline at end of file + options.add_argument('--verbose', dest="Verbose", default= False, action="store_true", help="More verbose") \ No newline at end of file diff --git a/plugins/SessionHijacker.py b/plugins/SessionHijacker.py index ea93ab4..7cf1b6f 100644 --- a/plugins/SessionHijacker.py +++ b/plugins/SessionHijacker.py @@ -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="