aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/README30
-rw-r--r--examples/add_header.py2
-rw-r--r--examples/change_upstream_proxy.py24
-rw-r--r--examples/custom_contentviews.py68
-rw-r--r--examples/dns_spoofing.py50
-rw-r--r--examples/dup_and_replay.py4
-rw-r--r--examples/filt.py16
-rw-r--r--examples/flowbasic44
-rw-r--r--examples/flowwriter.py20
-rw-r--r--examples/har_extractor.py253
-rw-r--r--examples/iframe_injector.py27
-rw-r--r--examples/mitmproxywrapper.py166
-rw-r--r--examples/modify_form.py5
-rw-r--r--examples/modify_querystring.py6
-rw-r--r--examples/modify_response_body.py18
-rw-r--r--examples/nonblocking.py9
-rw-r--r--examples/proxapp.py24
-rw-r--r--examples/read_dumpfile20
-rw-r--r--examples/redirect_requests.py22
-rw-r--r--examples/sslstrip.py40
-rw-r--r--examples/stickycookies42
-rw-r--r--examples/stream.py5
-rw-r--r--examples/stream_modify.py20
-rw-r--r--examples/stub.py79
-rw-r--r--examples/tcp_message.py24
-rw-r--r--examples/tls_passthrough.py136
-rw-r--r--examples/upsidedownternet.py17
27 files changed, 1171 insertions, 0 deletions
diff --git a/examples/README b/examples/README
new file mode 100644
index 00000000..cf5c4d7d
--- /dev/null
+++ b/examples/README
@@ -0,0 +1,30 @@
+Some inline scripts may require additional dependencies, which can be installed using
+`pip install mitmproxy[examples]`.
+
+
+# inline script examples
+add_header.py Simple script that just adds a header to every request.
+change_upstream_proxy.py Dynamically change the upstream proxy
+dns_spoofing.py Use mitmproxy in a DNS spoofing scenario.
+dup_and_replay.py Duplicates each request, changes it, and then replays the modified request.
+filt.py Use mitmproxy's filter expressions in your script.
+flowwriter.py Only write selected flows into a mitmproxy dumpfile.
+iframe_injector.py Inject configurable iframe into pages.
+modify_form.py Modify all form submissions to add a parameter.
+modify_querystring.py Modify all query strings to add a parameters.
+modify_response_body.py Replace arbitrary strings in all responses
+nonblocking.py Demonstrate parallel processing with a blocking script.
+proxapp.py How to embed a WSGI app in a mitmproxy server
+redirect_requests.py Redirect requests or directly reply to them.
+stub.py Script stub with a method definition for every event.
+upsidedownternet.py Rewrites traffic to turn images upside down.
+
+
+# mitmproxy examples
+flowbasic Basic use of mitmproxy as a library.
+stickycookies An example of writing a custom proxy with mitmproxy.
+
+
+# misc
+read_dumpfile Read a dumpfile generated by mitmproxy.
+mitmproxywrapper.py Bracket mitmproxy run with proxy enable/disable on OS X
diff --git a/examples/add_header.py b/examples/add_header.py
new file mode 100644
index 00000000..cf1b53cc
--- /dev/null
+++ b/examples/add_header.py
@@ -0,0 +1,2 @@
+def response(context, flow):
+ flow.response.headers["newheader"] = "foo"
diff --git a/examples/change_upstream_proxy.py b/examples/change_upstream_proxy.py
new file mode 100644
index 00000000..9c454897
--- /dev/null
+++ b/examples/change_upstream_proxy.py
@@ -0,0 +1,24 @@
+# This scripts demonstrates how mitmproxy can switch to a second/different upstream proxy
+# in upstream proxy mode.
+#
+# Usage: mitmdump -U http://default-upstream-proxy.local:8080/ -s change_upstream_proxy.py
+#
+# If you want to change the target server, you should modify flow.request.host and flow.request.port
+
+
+def proxy_address(flow):
+ # Poor man's loadbalancing: route every second domain through the alternative proxy.
+ if hash(flow.request.host) % 2 == 1:
+ return ("localhost", 8082)
+ else:
+ return ("localhost", 8081)
+
+
+def request(context, flow):
+ if flow.request.method == "CONNECT":
+ # If the decision is done by domain, one could also modify the server address here.
+ # We do it after CONNECT here to have the request data available as well.
+ return
+ address = proxy_address(flow)
+ if flow.live:
+ flow.live.change_upstream_proxy_server(address) \ No newline at end of file
diff --git a/examples/custom_contentviews.py b/examples/custom_contentviews.py
new file mode 100644
index 00000000..776ba99d
--- /dev/null
+++ b/examples/custom_contentviews.py
@@ -0,0 +1,68 @@
+import string
+import lxml.html
+import lxml.etree
+from mitmproxy import utils, contentviews
+
+
+class ViewPigLatin(contentviews.View):
+ name = "pig_latin_HTML"
+ prompt = ("pig latin HTML", "l")
+ content_types = ["text/html"]
+
+ def __call__(self, data, **metadata):
+ if utils.isXML(data):
+ parser = lxml.etree.HTMLParser(
+ strip_cdata=True,
+ remove_blank_text=True
+ )
+ d = lxml.html.fromstring(data, parser=parser)
+ docinfo = d.getroottree().docinfo
+
+ def piglify(src):
+ words = string.split(src)
+ ret = ''
+ for word in words:
+ idx = -1
+ while word[idx] in string.punctuation and (idx * -1) != len(word): idx -= 1
+ if word[0].lower() in 'aeiou':
+ if idx == -1:
+ ret += word[0:] + "hay"
+ else:
+ ret += word[0:len(word) + idx + 1] + "hay" + word[idx + 1:]
+ else:
+ if idx == -1:
+ ret += word[1:] + word[0] + "ay"
+ else:
+ ret += word[1:len(word) + idx + 1] + word[0] + "ay" + word[idx + 1:]
+ ret += ' '
+ return ret.strip()
+
+ def recurse(root):
+ if hasattr(root, 'text') and root.text:
+ root.text = piglify(root.text)
+ if hasattr(root, 'tail') and root.tail:
+ root.tail = piglify(root.tail)
+
+ if len(root):
+ for child in root:
+ recurse(child)
+
+ recurse(d)
+
+ s = lxml.etree.tostring(
+ d,
+ pretty_print=True,
+ doctype=docinfo.doctype
+ )
+ return "HTML", contentviews.format_text(s)
+
+
+pig_view = ViewPigLatin()
+
+
+def start(context, argv):
+ context.add_contentview(pig_view)
+
+
+def stop(context):
+ context.remove_contentview(pig_view)
diff --git a/examples/dns_spoofing.py b/examples/dns_spoofing.py
new file mode 100644
index 00000000..7eb79695
--- /dev/null
+++ b/examples/dns_spoofing.py
@@ -0,0 +1,50 @@
+"""
+This inline scripts makes it possible to use mitmproxy in scenarios where IP spoofing has been used to redirect
+connections to mitmproxy. The way this works is that we rely on either the TLS Server Name Indication (SNI) or the
+Host header of the HTTP request.
+Of course, this is not foolproof - if an HTTPS connection comes without SNI, we don't
+know the actual target and cannot construct a certificate that looks valid.
+Similarly, if there's no Host header or a spoofed Host header, we're out of luck as well.
+Using transparent mode is the better option most of the time.
+
+Usage:
+ mitmproxy
+ -p 443
+ -s dns_spoofing.py
+ # Used as the target location if neither SNI nor host header are present.
+ -R http://example.com/
+ mitmdump
+ -p 80
+ -R http://localhost:443/
+
+ (Setting up a single proxy instance and using iptables to redirect to it
+ works as well)
+"""
+import re
+
+
+# This regex extracts splits the host header into host and port.
+# Handles the edge case of IPv6 addresses containing colons.
+# https://bugzilla.mozilla.org/show_bug.cgi?id=45891
+parse_host_header = re.compile(r"^(?P<host>[^:]+|\[.+\])(?::(?P<port>\d+))?$")
+
+
+def request(context, flow):
+ if flow.client_conn.ssl_established:
+ flow.request.scheme = "https"
+ sni = flow.client_conn.connection.get_servername()
+ port = 443
+ else:
+ flow.request.scheme = "http"
+ sni = None
+ port = 80
+
+ host_header = flow.request.pretty_host
+ m = parse_host_header.match(host_header)
+ if m:
+ host_header = m.group("host").strip("[]")
+ if m.group("port"):
+ port = int(m.group("port"))
+
+ flow.request.host = sni or host_header
+ flow.request.port = port \ No newline at end of file
diff --git a/examples/dup_and_replay.py b/examples/dup_and_replay.py
new file mode 100644
index 00000000..9ba91d3b
--- /dev/null
+++ b/examples/dup_and_replay.py
@@ -0,0 +1,4 @@
+def request(context, flow):
+ f = context.duplicate_flow(flow)
+ f.request.path = "/changed"
+ context.replay_request(f)
diff --git a/examples/filt.py b/examples/filt.py
new file mode 100644
index 00000000..f99b675c
--- /dev/null
+++ b/examples/filt.py
@@ -0,0 +1,16 @@
+# This scripts demonstrates how to use mitmproxy's filter pattern in inline scripts.
+# Usage: mitmdump -s "filt.py FILTER"
+
+from mitmproxy import filt
+
+
+def start(context, argv):
+ if len(argv) != 2:
+ raise ValueError("Usage: -s 'filt.py FILTER'")
+ context.filter = filt.parse(argv[1])
+
+
+def response(context, flow):
+ if flow.match(context.filter):
+ print("Flow matches filter:")
+ print(flow)
diff --git a/examples/flowbasic b/examples/flowbasic
new file mode 100644
index 00000000..4a87b86a
--- /dev/null
+++ b/examples/flowbasic
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+"""
+ This example shows how to build a proxy based on mitmproxy's Flow
+ primitives.
+
+ Heads Up: In the majority of cases, you want to use inline scripts.
+
+ Note that request and response messages are not automatically replied to,
+ so we need to implement handlers to do this.
+"""
+from mitmproxy import flow
+from mitmproxy.proxy import ProxyServer, ProxyConfig
+
+
+class MyMaster(flow.FlowMaster):
+ def run(self):
+ try:
+ flow.FlowMaster.run(self)
+ except KeyboardInterrupt:
+ self.shutdown()
+
+ def handle_request(self, f):
+ f = flow.FlowMaster.handle_request(self, f)
+ if f:
+ f.reply()
+ return f
+
+ def handle_response(self, f):
+ f = flow.FlowMaster.handle_response(self, f)
+ if f:
+ f.reply()
+ print(f)
+ return f
+
+
+config = ProxyConfig(
+ port=8080,
+ # use ~/.mitmproxy/mitmproxy-ca.pem as default CA file.
+ cadir="~/.mitmproxy/"
+)
+state = flow.State()
+server = ProxyServer(config)
+m = MyMaster(server, state)
+m.run()
diff --git a/examples/flowwriter.py b/examples/flowwriter.py
new file mode 100644
index 00000000..8fb8cc60
--- /dev/null
+++ b/examples/flowwriter.py
@@ -0,0 +1,20 @@
+import random
+import sys
+
+from mitmproxy.flow import FlowWriter
+
+
+def start(context, argv):
+ if len(argv) != 2:
+ raise ValueError('Usage: -s "flowriter.py filename"')
+
+ if argv[1] == "-":
+ f = sys.stdout
+ else:
+ f = open(argv[1], "wb")
+ context.flow_writer = FlowWriter(f)
+
+
+def response(context, flow):
+ if random.choice([True, False]):
+ context.flow_writer.add(flow)
diff --git a/examples/har_extractor.py b/examples/har_extractor.py
new file mode 100644
index 00000000..4e905438
--- /dev/null
+++ b/examples/har_extractor.py
@@ -0,0 +1,253 @@
+"""
+
+ This inline script utilizes harparser.HAR from
+ https://github.com/JustusW/harparser to generate a HAR log object.
+"""
+from harparser import HAR
+
+from datetime import datetime
+
+
+class _HARLog(HAR.log):
+ # The attributes need to be registered here for them to actually be
+ # available later via self. This is due to HAREncodable linking __getattr__
+ # to __getitem__. Anything that is set only in __init__ will just be added
+ # as key/value pair to self.__classes__.
+ __page_list__ = []
+ __page_count__ = 0
+ __page_ref__ = {}
+
+ def __init__(self, page_list):
+ self.__page_list__ = page_list
+ self.__page_count__ = 0
+ self.__page_ref__ = {}
+
+ HAR.log.__init__(self, {"version": "1.2",
+ "creator": {"name": "MITMPROXY HARExtractor",
+ "version": "0.1",
+ "comment": ""},
+ "pages": [],
+ "entries": []})
+
+ def reset(self):
+ self.__init__(self.__page_list__)
+
+ def add(self, obj):
+ if isinstance(obj, HAR.pages):
+ self['pages'].append(obj)
+ if isinstance(obj, HAR.entries):
+ self['entries'].append(obj)
+
+ def create_page_id(self):
+ self.__page_count__ += 1
+ return "autopage_%s" % str(self.__page_count__)
+
+ def set_page_ref(self, page, ref):
+ self.__page_ref__[page] = ref
+
+ def get_page_ref(self, page):
+ return self.__page_ref__.get(page, None)
+
+ def get_page_list(self):
+ return self.__page_list__
+
+
+def start(context, argv):
+ """
+ On start we create a HARLog instance. You will have to adapt this to
+ suit your actual needs of HAR generation. As it will probably be
+ necessary to cluster logs by IPs or reset them from time to time.
+ """
+ context.dump_file = None
+ if len(argv) > 1:
+ context.dump_file = argv[1]
+ else:
+ raise ValueError(
+ 'Usage: -s "har_extractor.py filename" '
+ '(- will output to stdout, filenames ending with .zhar '
+ 'will result in compressed har)'
+ )
+ context.HARLog = _HARLog(['https://github.com'])
+ context.seen_server = set()
+
+
+def response(context, flow):
+ """
+ Called when a server response has been received. At the time of this
+ message both a request and a response are present and completely done.
+ """
+ # Values are converted from float seconds to int milliseconds later.
+ ssl_time = -.001
+ connect_time = -.001
+ if flow.server_conn not in context.seen_server:
+ # Calculate the connect_time for this server_conn. Afterwards add it to
+ # seen list, in order to avoid the connect_time being present in entries
+ # that use an existing connection.
+ connect_time = flow.server_conn.timestamp_tcp_setup - \
+ flow.server_conn.timestamp_start
+ context.seen_server.add(flow.server_conn)
+
+ if flow.server_conn.timestamp_ssl_setup is not None:
+ # Get the ssl_time for this server_conn as the difference between
+ # the start of the successful tcp setup and the successful ssl
+ # setup. If no ssl setup has been made it is left as -1 since it
+ # doesn't apply to this connection.
+ ssl_time = flow.server_conn.timestamp_ssl_setup - \
+ flow.server_conn.timestamp_tcp_setup
+
+ # Calculate the raw timings from the different timestamps present in the
+ # request and response object. For lack of a way to measure it dns timings
+ # can not be calculated. The same goes for HAR blocked: MITMProxy will open
+ # a server connection as soon as it receives the host and port from the
+ # client connection. So the time spent waiting is actually spent waiting
+ # between request.timestamp_end and response.timestamp_start thus it
+ # correlates to HAR wait instead.
+ timings_raw = {
+ 'send': flow.request.timestamp_end - flow.request.timestamp_start,
+ 'wait': flow.response.timestamp_start - flow.request.timestamp_end,
+ 'receive': flow.response.timestamp_end - flow.response.timestamp_start,
+ 'connect': connect_time,
+ 'ssl': ssl_time
+ }
+
+ # HAR timings are integers in ms, so we have to re-encode the raw timings to
+ # that format.
+ timings = dict([(key, int(1000 * value))
+ for key, value in timings_raw.iteritems()])
+
+ # The full_time is the sum of all timings. Timings set to -1 will be ignored
+ # as per spec.
+ full_time = 0
+ for item in timings.values():
+ if item > -1:
+ full_time += item
+
+ started_date_time = datetime.fromtimestamp(
+ flow.request.timestamp_start,
+ tz=utc).isoformat()
+
+ request_query_string = [{"name": k, "value": v}
+ for k, v in flow.request.get_query()]
+ request_http_version = flow.request.http_version
+ # Cookies are shaped as tuples by MITMProxy.
+ request_cookies = [{"name": k.strip(), "value": v[0]}
+ for k, v in (flow.request.get_cookies() or {}).iteritems()]
+ request_headers = [{"name": k, "value": v} for k, v in flow.request.headers]
+ request_headers_size = len(str(flow.request.headers))
+ request_body_size = len(flow.request.content)
+
+ response_http_version = flow.response.http_version
+ # Cookies are shaped as tuples by MITMProxy.
+ response_cookies = [{"name": k.strip(), "value": v[0]}
+ for k, v in (flow.response.get_cookies() or {}).iteritems()]
+ response_headers = [{"name": k, "value": v}
+ for k, v in flow.response.headers]
+ response_headers_size = len(str(flow.response.headers))
+ response_body_size = len(flow.response.content)
+ response_body_decoded_size = len(flow.response.get_decoded_content())
+ response_body_compression = response_body_decoded_size - response_body_size
+ response_mime_type = flow.response.headers.get('Content-Type', '')
+ response_redirect_url = flow.response.headers.get('Location', '')
+
+ entry = HAR.entries(
+ {
+ "startedDateTime": started_date_time,
+ "time": full_time,
+ "request": {
+ "method": flow.request.method,
+ "url": flow.request.url,
+ "httpVersion": request_http_version,
+ "cookies": request_cookies,
+ "headers": request_headers,
+ "queryString": request_query_string,
+ "headersSize": request_headers_size,
+ "bodySize": request_body_size,
+ },
+ "response": {
+ "status": flow.response.status_code,
+ "statusText": flow.response.msg,
+ "httpVersion": response_http_version,
+ "cookies": response_cookies,
+ "headers": response_headers,
+ "content": {
+ "size": response_body_size,
+ "compression": response_body_compression,
+ "mimeType": response_mime_type},
+ "redirectURL": response_redirect_url,
+ "headersSize": response_headers_size,
+ "bodySize": response_body_size,
+ },
+ "cache": {},
+ "timings": timings,
+ })
+
+ # If the current url is in the page list of context.HARLog or does not have
+ # a referrer we add it as a new pages object.
+ if flow.request.url in context.HARLog.get_page_list() or flow.request.headers.get(
+ 'Referer',
+ None) is None:
+ page_id = context.HARLog.create_page_id()
+ context.HARLog.add(
+ HAR.pages({
+ "startedDateTime": entry['startedDateTime'],
+ "id": page_id,
+ "title": flow.request.url,
+ })
+ )
+ context.HARLog.set_page_ref(flow.request.url, page_id)
+ entry['pageref'] = page_id
+
+ # Lookup the referer in the page_ref of context.HARLog to point this entries
+ # pageref attribute to the right pages object, then set it as a new
+ # reference to build a reference tree.
+ elif context.HARLog.get_page_ref(flow.request.headers.get('Referer')) is not None:
+ entry['pageref'] = context.HARLog.get_page_ref(
+ flow.request.headers['Referer']
+ )
+ context.HARLog.set_page_ref(
+ flow.request.headers['Referer'], entry['pageref']
+ )
+
+ context.HARLog.add(entry)
+
+
+def done(context):
+ """
+ Called once on script shutdown, after any other events.
+ """
+ from pprint import pprint
+ import json
+
+ json_dump = context.HARLog.json()
+ compressed_json_dump = context.HARLog.compress()
+
+ if context.dump_file == '-':
+ context.log(pprint.pformat(json.loads(json_dump)))
+ elif context.dump_file.endswith('.zhar'):
+ file(context.dump_file, "w").write(compressed_json_dump)
+ else:
+ file(context.dump_file, "w").write(json_dump)
+ context.log(
+ "HAR log finished with %s bytes (%s bytes compressed)" % (
+ len(json_dump), len(compressed_json_dump)
+ )
+ )
+ context.log(
+ "Compression rate is %s%%" % str(
+ 100. * len(compressed_json_dump) / len(json_dump)
+ )
+ )
+
+
+def print_attributes(obj, filter_string=None, hide_privates=False):
+ """
+ Useful helper method to quickly get all attributes of an object and its
+ values.
+ """
+ for attr in dir(obj):
+ if hide_privates and "__" in attr:
+ continue
+ if filter_string is not None and filter_string not in attr:
+ continue
+ value = getattr(obj, attr)
+ print("%s.%s" % ('obj', attr), value, type(value))
diff --git a/examples/iframe_injector.py b/examples/iframe_injector.py
new file mode 100644
index 00000000..fc38b136
--- /dev/null
+++ b/examples/iframe_injector.py
@@ -0,0 +1,27 @@
+# Usage: mitmdump -s "iframe_injector.py url"
+# (this script works best with --anticache)
+from bs4 import BeautifulSoup
+from mitmproxy.models import decoded
+
+
+def start(context, argv):
+ if len(argv) != 2:
+ raise ValueError('Usage: -s "iframe_injector.py url"')
+ context.iframe_url = argv[1]
+
+
+def response(context, flow):
+ if flow.request.host in context.iframe_url:
+ return
+ with decoded(flow.response): # Remove content encoding (gzip, ...)
+ html = BeautifulSoup(flow.response.content)
+ if html.body:
+ iframe = html.new_tag(
+ "iframe",
+ src=context.iframe_url,
+ frameborder=0,
+ height=0,
+ width=0)
+ html.body.insert(0, iframe)
+ flow.response.content = str(html)
+ context.log("Iframe inserted.")
diff --git a/examples/mitmproxywrapper.py b/examples/mitmproxywrapper.py
new file mode 100644
index 00000000..7ea10715
--- /dev/null
+++ b/examples/mitmproxywrapper.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+#
+# Helper tool to enable/disable OS X proxy and wrap mitmproxy
+#
+# Get usage information with:
+#
+# mitmproxywrapper.py -h
+#
+
+import subprocess
+import re
+import argparse
+import contextlib
+import os
+import sys
+
+
+class Wrapper(object):
+
+ def __init__(self, port, extra_arguments=None):
+ self.port = port
+ self.extra_arguments = extra_arguments
+
+ def run_networksetup_command(self, *arguments):
+ return subprocess.check_output(
+ ['sudo', 'networksetup'] + list(arguments))
+
+ def proxy_state_for_service(self, service):
+ state = self.run_networksetup_command(
+ '-getwebproxy',
+ service).splitlines()
+ return dict([re.findall(r'([^:]+): (.*)', line)[0] for line in state])
+
+ def enable_proxy_for_service(self, service):
+ print('Enabling proxy on {}...'.format(service))
+ for subcommand in ['-setwebproxy', '-setsecurewebproxy']:
+ self.run_networksetup_command(
+ subcommand, service, '127.0.0.1', str(
+ self.port))
+
+ def disable_proxy_for_service(self, service):
+ print('Disabling proxy on {}...'.format(service))
+ for subcommand in ['-setwebproxystate', '-setsecurewebproxystate']:
+ self.run_networksetup_command(subcommand, service, 'Off')
+
+ def interface_name_to_service_name_map(self):
+ order = self.run_networksetup_command('-listnetworkserviceorder')
+ mapping = re.findall(
+ r'\(\d+\)\s(.*)$\n\(.*Device: (.+)\)$',
+ order,
+ re.MULTILINE)
+ return dict([(b, a) for (a, b) in mapping])
+
+ def run_command_with_input(self, command, input):
+ popen = subprocess.Popen(
+ command,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ (stdout, stderr) = popen.communicate(input)
+ return stdout
+
+ def primary_interace_name(self):
+ scutil_script = 'get State:/Network/Global/IPv4\nd.show\n'
+ stdout = self.run_command_with_input('/usr/sbin/scutil', scutil_script)
+ interface, = re.findall(r'PrimaryInterface\s*:\s*(.+)', stdout)
+ return interface
+
+ def primary_service_name(self):
+ return self.interface_name_to_service_name_map()[
+ self.primary_interace_name()]
+
+ def proxy_enabled_for_service(self, service):
+ return self.proxy_state_for_service(service)['Enabled'] == 'Yes'
+
+ def toggle_proxy(self):
+ new_state = not self.proxy_enabled_for_service(
+ self.primary_service_name())
+ for service_name in self.connected_service_names():
+ if self.proxy_enabled_for_service(service_name) and not new_state:
+ self.disable_proxy_for_service(service_name)
+ elif not self.proxy_enabled_for_service(service_name) and new_state:
+ self.enable_proxy_for_service(service_name)
+
+ def connected_service_names(self):
+ scutil_script = 'list\n'
+ stdout = self.run_command_with_input('/usr/sbin/scutil', scutil_script)
+ service_ids = re.findall(r'State:/Network/Service/(.+)/IPv4', stdout)
+
+ service_names = []
+ for service_id in service_ids:
+ scutil_script = 'show Setup:/Network/Service/{}\n'.format(
+ service_id)
+ stdout = self.run_command_with_input(
+ '/usr/sbin/scutil',
+ scutil_script)
+ service_name, = re.findall(r'UserDefinedName\s*:\s*(.+)', stdout)
+ service_names.append(service_name)
+
+ return service_names
+
+ def wrap_mitmproxy(self):
+ with self.wrap_proxy():
+ cmd = ['mitmproxy', '-p', str(self.port)]
+ if self.extra_arguments:
+ cmd.extend(self.extra_arguments)
+ subprocess.check_call(cmd)
+
+ def wrap_honeyproxy(self):
+ with self.wrap_proxy():
+ popen = subprocess.Popen('honeyproxy.sh')
+ try:
+ popen.wait()
+ except KeyboardInterrupt:
+ popen.terminate()
+
+ @contextlib.contextmanager
+ def wrap_proxy(self):
+ connected_service_names = self.connected_service_names()
+ for service_name in connected_service_names:
+ if not self.proxy_enabled_for_service(service_name):
+ self.enable_proxy_for_service(service_name)
+
+ yield
+
+ for service_name in connected_service_names:
+ if self.proxy_enabled_for_service(service_name):
+ self.disable_proxy_for_service(service_name)
+
+ @classmethod
+ def ensure_superuser(cls):
+ if os.getuid() != 0:
+ print('Relaunching with sudo...')
+ os.execv('/usr/bin/sudo', ['/usr/bin/sudo'] + sys.argv)
+
+ @classmethod
+ def main(cls):
+ parser = argparse.ArgumentParser(
+ description='Helper tool for OS X proxy configuration and mitmproxy.',
+ epilog='Any additional arguments will be passed on unchanged to mitmproxy.')
+ parser.add_argument(
+ '-t',
+ '--toggle',
+ action='store_true',
+ help='just toggle the proxy configuration')
+# parser.add_argument('--honeyproxy', action='store_true', help='run honeyproxy instead of mitmproxy')
+ parser.add_argument(
+ '-p',
+ '--port',
+ type=int,
+ help='override the default port of 8080',
+ default=8080)
+ args, extra_arguments = parser.parse_known_args()
+
+ wrapper = cls(port=args.port, extra_arguments=extra_arguments)
+
+ if args.toggle:
+ wrapper.toggle_proxy()
+# elif args.honeyproxy:
+# wrapper.wrap_honeyproxy()
+ else:
+ wrapper.wrap_mitmproxy()
+
+
+if __name__ == '__main__':
+ Wrapper.ensure_superuser()
+ Wrapper.main()
diff --git a/examples/modify_form.py b/examples/modify_form.py
new file mode 100644
index 00000000..3e9d15c0
--- /dev/null
+++ b/examples/modify_form.py
@@ -0,0 +1,5 @@
+def request(context, flow):
+ if "application/x-www-form-urlencoded" in flow.request.headers.get("content-type", ""):
+ form = flow.request.get_form_urlencoded()
+ form["mitmproxy"] = ["rocks"]
+ flow.request.set_form_urlencoded(form)
diff --git a/examples/modify_querystring.py b/examples/modify_querystring.py
new file mode 100644
index 00000000..7f31a48f
--- /dev/null
+++ b/examples/modify_querystring.py
@@ -0,0 +1,6 @@
+
+def request(context, flow):
+ q = flow.request.get_query()
+ if q:
+ q["mitmproxy"] = ["rocks"]
+ flow.request.set_query(q)
diff --git a/examples/modify_response_body.py b/examples/modify_response_body.py
new file mode 100644
index 00000000..88f69e15
--- /dev/null
+++ b/examples/modify_response_body.py
@@ -0,0 +1,18 @@
+# Usage: mitmdump -s "modify_response_body.py mitmproxy bananas"
+# (this script works best with --anticache)
+from mitmproxy.models import decoded
+
+
+def start(context, argv):
+ if len(argv) != 3:
+ raise ValueError('Usage: -s "modify-response-body.py old new"')
+ # You may want to use Python's argparse for more sophisticated argument
+ # parsing.
+ context.old, context.new = argv[1], argv[2]
+
+
+def response(context, flow):
+ with decoded(flow.response): # automatically decode gzipped responses.
+ flow.response.content = flow.response.content.replace(
+ context.old,
+ context.new)
diff --git a/examples/nonblocking.py b/examples/nonblocking.py
new file mode 100644
index 00000000..41674b2a
--- /dev/null
+++ b/examples/nonblocking.py
@@ -0,0 +1,9 @@
+import time
+from mitmproxy.script import concurrent
+
+
+@concurrent # Remove this and see what happens
+def request(context, flow):
+ print("handle request: %s%s" % (flow.request.host, flow.request.path))
+ time.sleep(5)
+ print("start request: %s%s" % (flow.request.host, flow.request.path))
diff --git a/examples/proxapp.py b/examples/proxapp.py
new file mode 100644
index 00000000..4d8e7b58
--- /dev/null
+++ b/examples/proxapp.py
@@ -0,0 +1,24 @@
+"""
+This example shows how to graft a WSGI app onto mitmproxy. In this
+instance, we're using the Flask framework (http://flask.pocoo.org/) to expose
+a single simplest-possible page.
+"""
+from flask import Flask
+
+app = Flask("proxapp")
+
+
+@app.route('/')
+def hello_world():
+ return 'Hello World!'
+
+
+# Register the app using the magic domain "proxapp" on port 80. Requests to
+# this domain and port combination will now be routed to the WSGI app instance.
+def start(context, argv):
+ context.app_registry.add(app, "proxapp", 80)
+
+ # SSL works too, but the magic domain needs to be resolvable from the mitmproxy machine due to mitmproxy's design.
+ # mitmproxy will connect to said domain and use serve its certificate (unless --no-upstream-cert is set)
+ # but won't send any data.
+ context.app_registry.add(app, "example.com", 443)
diff --git a/examples/read_dumpfile b/examples/read_dumpfile
new file mode 100644
index 00000000..56746bb8
--- /dev/null
+++ b/examples/read_dumpfile
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+#
+# Simple script showing how to read a mitmproxy dump file
+#
+
+from mitmproxy import flow
+import pprint
+import sys
+
+with open(sys.argv[1], "rb") as logfile:
+ freader = flow.FlowReader(logfile)
+ pp = pprint.PrettyPrinter(indent=4)
+ try:
+ for f in freader.stream():
+ print(f)
+ print(f.request.host)
+ pp.pprint(f.get_state())
+ print("")
+ except flow.FlowReadError as v:
+ print "Flow file corrupted. Stopped loading."
diff --git a/examples/redirect_requests.py b/examples/redirect_requests.py
new file mode 100644
index 00000000..c0a0ccba
--- /dev/null
+++ b/examples/redirect_requests.py
@@ -0,0 +1,22 @@
+"""
+This example shows two ways to redirect flows to other destinations.
+"""
+from mitmproxy.models import HTTPResponse
+from netlib.http import Headers
+
+def request(context, flow):
+ # pretty_host takes the "Host" header of the request into account,
+ # which is useful in transparent mode where we usually only have the IP
+ # otherwise.
+
+ # Method 1: Answer with a locally generated response
+ if flow.request.pretty_host.endswith("example.com"):
+ resp = HTTPResponse(
+ "HTTP/1.1", 200, "OK",
+ Headers(Content_Type="text/html"),
+ "helloworld")
+ flow.reply(resp)
+
+ # Method 2: Redirect the request to a different server
+ if flow.request.pretty_host.endswith("example.org"):
+ flow.request.host = "mitmproxy.org"
diff --git a/examples/sslstrip.py b/examples/sslstrip.py
new file mode 100644
index 00000000..369427a2
--- /dev/null
+++ b/examples/sslstrip.py
@@ -0,0 +1,40 @@
+from netlib.http import decoded
+import re
+from six.moves import urllib
+
+def start(context, argv) :
+
+ #set of SSL/TLS capable hosts
+ context.secure_hosts = set()
+
+def request(context, flow) :
+
+ flow.request.headers.pop('If-Modified-Since', None)
+ flow.request.headers.pop('Cache-Control', None)
+
+ #proxy connections to SSL-enabled hosts
+ if flow.request.pretty_host in context.secure_hosts :
+ flow.request.scheme = 'https'
+ flow.request.port = 443
+
+def response(context, flow) :
+
+ with decoded(flow.response) :
+ flow.request.headers.pop('Strict-Transport-Security', None)
+ flow.request.headers.pop('Public-Key-Pins', None)
+
+ #strip links in response body
+ flow.response.content = flow.response.content.replace('https://', 'http://')
+
+ #strip links in 'Location' header
+ if flow.response.headers.get('Location','').startswith('https://'):
+ location = flow.response.headers['Location']
+ hostname = urllib.parse.urlparse(location).hostname
+ if hostname:
+ context.secure_hosts.add(hostname)
+ flow.response.headers['Location'] = location.replace('https://', 'http://', 1)
+
+ #strip secure flag from 'Set-Cookie' headers
+ cookies = flow.response.headers.get_all('Set-Cookie')
+ cookies = [re.sub(r';\s*secure\s*', '', s) for s in cookies]
+ flow.response.headers.set_all('Set-Cookie', cookies)
diff --git a/examples/stickycookies b/examples/stickycookies
new file mode 100644
index 00000000..8f11de8d
--- /dev/null
+++ b/examples/stickycookies
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+"""
+This example builds on mitmproxy's base proxying infrastructure to
+implement functionality similar to the "sticky cookies" option.
+
+Heads Up: In the majority of cases, you want to use inline scripts.
+"""
+import os
+from mitmproxy import controller, proxy
+from mitmproxy.proxy.server import ProxyServer
+
+
+class StickyMaster(controller.Master):
+ def __init__(self, server):
+ controller.Master.__init__(self, server)
+ self.stickyhosts = {}
+
+ def run(self):
+ try:
+ return controller.Master.run(self)
+ except KeyboardInterrupt:
+ self.shutdown()
+
+ def handle_request(self, flow):
+ hid = (flow.request.host, flow.request.port)
+ if "cookie" in flow.request.headers:
+ self.stickyhosts[hid] = flow.request.headers.get_all("cookie")
+ elif hid in self.stickyhosts:
+ flow.request.headers.set_all("cookie", self.stickyhosts[hid])
+ flow.reply()
+
+ def handle_response(self, flow):
+ hid = (flow.request.host, flow.request.port)
+ if "set-cookie" in flow.response.headers:
+ self.stickyhosts[hid] = flow.response.headers.get_all("set-cookie")
+ flow.reply()
+
+
+config = proxy.ProxyConfig(port=8080)
+server = ProxyServer(config)
+m = StickyMaster(server)
+m.run()
diff --git a/examples/stream.py b/examples/stream.py
new file mode 100644
index 00000000..3adbe437
--- /dev/null
+++ b/examples/stream.py
@@ -0,0 +1,5 @@
+def responseheaders(context, flow):
+ """
+ Enables streaming for all responses.
+ """
+ flow.response.stream = True
diff --git a/examples/stream_modify.py b/examples/stream_modify.py
new file mode 100644
index 00000000..aa395c03
--- /dev/null
+++ b/examples/stream_modify.py
@@ -0,0 +1,20 @@
+"""
+This inline script modifies a streamed response.
+If you do not need streaming, see the modify_response_body example.
+Be aware that content replacement isn't trivial:
+ - If the transfer encoding isn't chunked, you cannot simply change the content length.
+ - If you want to replace all occurences of "foobar", make sure to catch the cases
+ where one chunk ends with [...]foo" and the next starts with "bar[...].
+"""
+
+
+def modify(chunks):
+ """
+ chunks is a generator that can be used to iterate over all chunks.
+ """
+ for chunk in chunks:
+ yield chunk.replace("foo", "bar")
+
+
+def responseheaders(context, flow):
+ flow.response.stream = modify
diff --git a/examples/stub.py b/examples/stub.py
new file mode 100644
index 00000000..516b71a5
--- /dev/null
+++ b/examples/stub.py
@@ -0,0 +1,79 @@
+"""
+ This is a script stub, with definitions for all events.
+"""
+
+
+def start(context, argv):
+ """
+ Called once on script startup, before any other events.
+ """
+ context.log("start")
+
+
+def clientconnect(context, root_layer):
+ """
+ Called when a client initiates a connection to the proxy. Note that a
+ connection can correspond to multiple HTTP requests
+ """
+ context.log("clientconnect")
+
+
+def request(context, flow):
+ """
+ Called when a client request has been received.
+ """
+ context.log("request")
+
+
+def serverconnect(context, server_conn):
+ """
+ Called when the proxy initiates a connection to the target server. Note that a
+ connection can correspond to multiple HTTP requests
+ """
+ context.log("serverconnect")
+
+
+def responseheaders(context, flow):
+ """
+ Called when the response headers for a server response have been received,
+ but the response body has not been processed yet. Can be used to tell mitmproxy
+ to stream the response.
+ """
+ context.log("responseheaders")
+
+
+def response(context, flow):
+ """
+ Called when a server response has been received.
+ """
+ context.log("response")
+
+
+def error(context, flow):
+ """
+ Called when a flow error has occured, e.g. invalid server responses, or
+ interrupted connections. This is distinct from a valid server HTTP error
+ response, which is simply a response with an HTTP error code.
+ """
+ context.log("error")
+
+
+def serverdisconnect(context, server_conn):
+ """
+ Called when the proxy closes the connection to the target server.
+ """
+ context.log("serverdisconnect")
+
+
+def clientdisconnect(context, root_layer):
+ """
+ Called when a client disconnects from the proxy.
+ """
+ context.log("clientdisconnect")
+
+
+def done(context):
+ """
+ Called once on script shutdown, after any other events.
+ """
+ context.log("done")
diff --git a/examples/tcp_message.py b/examples/tcp_message.py
new file mode 100644
index 00000000..c63368e4
--- /dev/null
+++ b/examples/tcp_message.py
@@ -0,0 +1,24 @@
+'''
+tcp_message Inline Script Hook API Demonstration
+------------------------------------------------
+
+* modifies packets containing "foo" to "bar"
+* prints various details for each packet.
+
+example cmdline invocation:
+mitmdump -T --host --tcp ".*" -q -s examples/tcp_message.py
+'''
+from netlib.utils import clean_bin
+
+def tcp_message(ctx, tcp_msg):
+ modified_msg = tcp_msg.message.replace("foo", "bar")
+
+ is_modified = False if modified_msg == tcp_msg.message else True
+ tcp_msg.message = modified_msg
+
+ print("[tcp_message{}] from {} {} to {} {}:\r\n{}".format(
+ " (modified)" if is_modified else "",
+ "client" if tcp_msg.sender == tcp_msg.client_conn else "server",
+ tcp_msg.sender.address,
+ "server" if tcp_msg.receiver == tcp_msg.server_conn else "client",
+ tcp_msg.receiver.address, clean_bin(tcp_msg.message)))
diff --git a/examples/tls_passthrough.py b/examples/tls_passthrough.py
new file mode 100644
index 00000000..8c8fa4eb
--- /dev/null
+++ b/examples/tls_passthrough.py
@@ -0,0 +1,136 @@
+"""
+This inline script allows conditional TLS Interception based
+on a user-defined strategy.
+
+Example:
+
+ > mitmdump -s tls_passthrough.py
+
+ 1. curl --proxy http://localhost:8080 https://example.com --insecure
+ // works - we'll also see the contents in mitmproxy
+
+ 2. curl --proxy http://localhost:8080 https://example.com --insecure
+ // still works - we'll also see the contents in mitmproxy
+
+ 3. curl --proxy http://localhost:8080 https://example.com
+ // fails with a certificate error, which we will also see in mitmproxy
+
+ 4. curl --proxy http://localhost:8080 https://example.com
+ // works again, but mitmproxy does not intercept and we do *not* see the contents
+
+Authors: Maximilian Hils, Matthew Tuusberg
+"""
+from __future__ import (absolute_import, print_function, division)
+import collections
+import random
+
+from enum import Enum
+
+from mitmproxy.exceptions import TlsProtocolException
+from mitmproxy.protocol import TlsLayer, RawTCPLayer
+
+
+class InterceptionResult(Enum):
+ success = True
+ failure = False
+ skipped = None
+
+
+class _TlsStrategy(object):
+ """
+ Abstract base class for interception strategies.
+ """
+ def __init__(self):
+ # A server_address -> interception results mapping
+ self.history = collections.defaultdict(lambda: collections.deque(maxlen=200))
+
+ def should_intercept(self, server_address):
+ """
+ Returns:
+ True, if we should attempt to intercept the connection.
+ False, if we want to employ pass-through instead.
+ """
+ raise NotImplementedError()
+
+ def record_success(self, server_address):
+ self.history[server_address].append(InterceptionResult.success)
+
+ def record_failure(self, server_address):
+ self.history[server_address].append(InterceptionResult.failure)
+
+ def record_skipped(self, server_address):
+ self.history[server_address].append(InterceptionResult.skipped)
+
+
+class ConservativeStrategy(_TlsStrategy):
+ """
+ Conservative Interception Strategy - only intercept if there haven't been any failed attempts
+ in the history.
+ """
+
+ def should_intercept(self, server_address):
+ if InterceptionResult.failure in self.history[server_address]:
+ return False
+ return True
+
+
+class ProbabilisticStrategy(_TlsStrategy):
+ """
+ Fixed probability that we intercept a given connection.
+ """
+ def __init__(self, p):
+ self.p = p
+ super(ProbabilisticStrategy, self).__init__()
+
+ def should_intercept(self, server_address):
+ return random.uniform(0, 1) < self.p
+
+
+class TlsFeedback(TlsLayer):
+ """
+ Monkey-patch _establish_tls_with_client to get feedback if TLS could be established
+ successfully on the client connection (which may fail due to cert pinning).
+ """
+
+ def _establish_tls_with_client(self):
+ server_address = self.server_conn.address
+ tls_strategy = self.script_context.tls_strategy
+
+ try:
+ super(TlsFeedback, self)._establish_tls_with_client()
+ except TlsProtocolException as e:
+ tls_strategy.record_failure(server_address)
+ raise e
+ else:
+ tls_strategy.record_success(server_address)
+
+
+# inline script hooks below.
+
+
+def start(context, argv):
+ if len(argv) == 2:
+ context.tls_strategy = ProbabilisticStrategy(float(argv[1]))
+ else:
+ context.tls_strategy = ConservativeStrategy()
+
+
+def next_layer(context, next_layer):
+ """
+ This hook does the actual magic - if the next layer is planned to be a TLS layer,
+ we check if we want to enter pass-through mode instead.
+ """
+ if isinstance(next_layer, TlsLayer) and next_layer._client_tls:
+ server_address = next_layer.server_conn.address
+
+ if context.tls_strategy.should_intercept(server_address):
+ # We try to intercept.
+ # Monkey-Patch the layer to get feedback from the TLSLayer if interception worked.
+ next_layer.__class__ = TlsFeedback
+ next_layer.script_context = context
+ else:
+ # We don't intercept - reply with a pass-through layer and add a "skipped" entry.
+ context.log("TLS passthrough for %s" % repr(next_layer.server_conn.address), "info")
+ next_layer_replacement = RawTCPLayer(next_layer.ctx, logging=False)
+ next_layer.reply(next_layer_replacement)
+ context.tls_strategy.record_skipped(server_address)
diff --git a/examples/upsidedownternet.py b/examples/upsidedownternet.py
new file mode 100644
index 00000000..b7489cb6
--- /dev/null
+++ b/examples/upsidedownternet.py
@@ -0,0 +1,17 @@
+import cStringIO
+from PIL import Image
+from mitmproxy.models import decoded
+
+
+def response(context, flow):
+ if flow.response.headers.get("content-type", "").startswith("image"):
+ with decoded(flow.response): # automatically decode gzipped responses.
+ try:
+ s = cStringIO.StringIO(flow.response.content)
+ img = Image.open(s).rotate(180)
+ s2 = cStringIO.StringIO()
+ img.save(s2, "png")
+ flow.response.content = s2.getvalue()
+ flow.response.headers["content-type"] = "image/png"
+ except: # Unknown image types etc.
+ pass