diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/README | 4 | ||||
-rw-r--r-- | examples/add_header.py | 2 | ||||
-rw-r--r-- | examples/change_upstream_proxy.py | 12 | ||||
-rwxr-xr-x | examples/flowbasic | 7 | ||||
-rw-r--r-- | examples/har_extractor.py | 10 | ||||
-rw-r--r-- | examples/iframe_injector.py | 2 | ||||
-rw-r--r-- | examples/modify_form.py | 4 | ||||
-rw-r--r-- | examples/modify_response_body.py | 2 | ||||
-rwxr-xr-x | examples/read_dumpfile | 1 | ||||
-rw-r--r-- | examples/redirect_requests.py | 8 | ||||
-rwxr-xr-x | examples/stickycookies | 10 | ||||
-rw-r--r-- | examples/stream_modify.py | 6 | ||||
-rw-r--r-- | examples/stub.py | 16 | ||||
-rw-r--r-- | examples/tls_passthrough.py | 136 | ||||
-rw-r--r-- | examples/upsidedownternet.py | 6 |
15 files changed, 174 insertions, 52 deletions
diff --git a/examples/README b/examples/README index adfcd0f2..b4dec8e5 100644 --- a/examples/README +++ b/examples/README @@ -1,3 +1,7 @@ +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 diff --git a/examples/add_header.py b/examples/add_header.py index 0c0593d1..cf1b53cc 100644 --- a/examples/add_header.py +++ b/examples/add_header.py @@ -1,2 +1,2 @@ def response(context, flow): - flow.response.headers["newheader"] = ["foo"] + flow.response.headers["newheader"] = "foo" diff --git a/examples/change_upstream_proxy.py b/examples/change_upstream_proxy.py index 8f58e1f2..9c454897 100644 --- a/examples/change_upstream_proxy.py +++ b/examples/change_upstream_proxy.py @@ -4,7 +4,6 @@ # 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 -# flow.live.set_server should only be used by inline scripts to change the upstream proxy. def proxy_address(flow): @@ -22,13 +21,4 @@ def request(context, flow): return address = proxy_address(flow) if flow.live: - if flow.request.scheme == "http": - # For a normal HTTP request, we just change the proxy server and we're done! - if address != flow.live.server_conn.address: - flow.live.set_server(address, depth=1) - else: - # If we have CONNECTed (and thereby established "destination state"), the story is - # a bit more complex. Now we don't want to change the top level address (which is - # the connect destination) but the address below that. (Notice the `.via` and depth=2). - if address != flow.live.server_conn.via.address: - flow.live.set_server(address, depth=2) + flow.live.change_upstream_proxy_server(address)
\ No newline at end of file diff --git a/examples/flowbasic b/examples/flowbasic index 6663dc46..78b9eff7 100755 --- a/examples/flowbasic +++ b/examples/flowbasic @@ -8,9 +8,8 @@ Note that request and response messages are not automatically replied to, so we need to implement handlers to do this. """ -import os -from libmproxy import flow, proxy -from libmproxy.proxy.server import ProxyServer +from libmproxy import flow +from libmproxy.proxy import ProxyServer, ProxyConfig class MyMaster(flow.FlowMaster): @@ -34,7 +33,7 @@ class MyMaster(flow.FlowMaster): return f -config = proxy.ProxyConfig( +config = ProxyConfig( port=8080, # use ~/.mitmproxy/mitmproxy-ca.pem as default CA file. cadir="~/.mitmproxy/" diff --git a/examples/har_extractor.py b/examples/har_extractor.py index f06efec3..bc784dc4 100644 --- a/examples/har_extractor.py +++ b/examples/har_extractor.py @@ -147,8 +147,8 @@ def response(context, flow): 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_first('Content-Type', '') - response_redirect_url = flow.response.headers.get_first('Location', '') + response_mime_type = flow.response.headers.get('Content-Type', '') + response_redirect_url = flow.response.headers.get('Location', '') entry = HAR.entries( { @@ -201,12 +201,12 @@ def response(context, flow): # 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', (None, ))[0]) is not None: + 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'][0] + flow.request.headers['Referer'] ) context.HARLog.set_page_ref( - flow.request.headers['Referer'][0], entry['pageref'] + flow.request.headers['Referer'], entry['pageref'] ) context.HARLog.add(entry) diff --git a/examples/iframe_injector.py b/examples/iframe_injector.py index b2fa2d26..29de9b63 100644 --- a/examples/iframe_injector.py +++ b/examples/iframe_injector.py @@ -1,7 +1,7 @@ # Usage: mitmdump -s "iframe_injector.py url" # (this script works best with --anticache) from bs4 import BeautifulSoup -from libmproxy.protocol.http import decoded +from libmproxy.models import decoded def start(context, argv): diff --git a/examples/modify_form.py b/examples/modify_form.py index 37ba2fac..3e9d15c0 100644 --- a/examples/modify_form.py +++ b/examples/modify_form.py @@ -1,7 +1,5 @@ - def request(context, flow): - if "application/x-www-form-urlencoded" in flow.request.headers[ - "content-type"]: + 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_response_body.py b/examples/modify_response_body.py index 68d3d4ab..a35e1525 100644 --- a/examples/modify_response_body.py +++ b/examples/modify_response_body.py @@ -1,6 +1,6 @@ # Usage: mitmdump -s "modify_response_body.py mitmproxy bananas" # (this script works best with --anticache) -from libmproxy.protocol.http import decoded +from libmproxy.models import decoded def start(context, argv): diff --git a/examples/read_dumpfile b/examples/read_dumpfile index eb1c93bb..b329c0e1 100755 --- a/examples/read_dumpfile +++ b/examples/read_dumpfile @@ -4,7 +4,6 @@ # from libmproxy import flow -import json import pprint import sys diff --git a/examples/redirect_requests.py b/examples/redirect_requests.py index 48512f1b..ca24c42a 100644 --- a/examples/redirect_requests.py +++ b/examples/redirect_requests.py @@ -1,10 +1,8 @@ -from libmproxy.protocol.http import HTTPResponse -from netlib.odict import ODictCaseless - """ This example shows two ways to redirect flows to other destinations. """ - +from libmproxy.models import HTTPResponse +from netlib.http import Headers def request(context, flow): # pretty_host(hostheader=True) takes the Host: header of the request into account, @@ -15,7 +13,7 @@ def request(context, flow): if flow.request.pretty_host(hostheader=True).endswith("example.com"): resp = HTTPResponse( [1, 1], 200, "OK", - ODictCaseless([["Content-Type", "text/html"]]), + Headers(Content_Type="text/html"), "helloworld") flow.reply(resp) diff --git a/examples/stickycookies b/examples/stickycookies index 67b31da1..7e84f71c 100755 --- a/examples/stickycookies +++ b/examples/stickycookies @@ -23,16 +23,16 @@ class StickyMaster(controller.Master): def handle_request(self, flow): hid = (flow.request.host, flow.request.port) - if flow.request.headers["cookie"]: - self.stickyhosts[hid] = flow.request.headers["cookie"] + if "cookie" in flow.request.headers: + self.stickyhosts[hid] = flow.request.headers.get_all("cookie") elif hid in self.stickyhosts: - flow.request.headers["cookie"] = self.stickyhosts[hid] + flow.request.headers.set_all("cookie", self.stickyhosts[hid]) flow.reply() def handle_response(self, flow): hid = (flow.request.host, flow.request.port) - if flow.response.headers["set-cookie"]: - self.stickyhosts[hid] = flow.response.headers["set-cookie"] + if "set-cookie" in flow.response.headers: + self.stickyhosts[hid] = flow.response.headers.get_all("set-cookie") flow.reply() diff --git a/examples/stream_modify.py b/examples/stream_modify.py index e3f1f3cf..aa395c03 100644 --- a/examples/stream_modify.py +++ b/examples/stream_modify.py @@ -11,11 +11,9 @@ Be aware that content replacement isn't trivial: def modify(chunks): """ chunks is a generator that can be used to iterate over all chunks. - Each chunk is a (prefix, content, suffix) tuple. - For example, in the case of chunked transfer encoding: ("3\r\n","foo","\r\n") """ - for prefix, content, suffix in chunks: - yield prefix, content.replace("foo", "bar"), suffix + for chunk in chunks: + yield chunk.replace("foo", "bar") def responseheaders(context, flow): diff --git a/examples/stub.py b/examples/stub.py index bd3e7cd0..516b71a5 100644 --- a/examples/stub.py +++ b/examples/stub.py @@ -18,19 +18,19 @@ def clientconnect(context, root_layer): context.log("clientconnect") -def serverconnect(context, server_connection): +def request(context, flow): """ - Called when the proxy initiates a connection to the target server. Note that a - connection can correspond to multiple HTTP requests + Called when a client request has been received. """ - context.log("serverconnect") + context.log("request") -def request(context, flow): +def serverconnect(context, server_conn): """ - Called when a client request has been received. + Called when the proxy initiates a connection to the target server. Note that a + connection can correspond to multiple HTTP requests """ - context.log("request") + context.log("serverconnect") def responseheaders(context, flow): @@ -58,7 +58,7 @@ def error(context, flow): context.log("error") -def serverdisconnect(context, server_connection): +def serverdisconnect(context, server_conn): """ Called when the proxy closes the connection to the target server. """ diff --git a/examples/tls_passthrough.py b/examples/tls_passthrough.py new file mode 100644 index 00000000..7b4dec62 --- /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 libmproxy.exceptions import TlsException +from libmproxy.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 TlsException 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 index a6de97e4..f2e73047 100644 --- a/examples/upsidedownternet.py +++ b/examples/upsidedownternet.py @@ -1,10 +1,10 @@ import cStringIO from PIL import Image -from libmproxy.protocol.http import decoded +from libmproxy.models import decoded def response(context, flow): - if flow.response.headers.get_first("content-type", "").startswith("image"): + if flow.response.headers.get("content-type", "").startswith("image"): with decoded(flow.response): # automatically decode gzipped responses. try: s = cStringIO.StringIO(flow.response.content) @@ -12,6 +12,6 @@ def response(context, flow): s2 = cStringIO.StringIO() img.save(s2, "png") flow.response.content = s2.getvalue() - flow.response.headers["content-type"] = ["image/png"] + flow.response.headers["content-type"] = "image/png" except: # Unknown image types etc. pass |