2015-09-08 19:35:15 +00:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
import collections
|
|
|
|
import random
|
|
|
|
|
|
|
|
from enum import Enum
|
|
|
|
|
2016-07-08 01:37:33 +00:00
|
|
|
import mitmproxy
|
2017-04-25 23:45:15 +00:00
|
|
|
from mitmproxy import ctx
|
2016-02-16 19:49:10 +00:00
|
|
|
from mitmproxy.exceptions import TlsProtocolException
|
2016-10-19 10:11:56 +00:00
|
|
|
from mitmproxy.proxy.protocol import TlsLayer, RawTCPLayer
|
2015-09-08 19:35:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
class InterceptionResult(Enum):
|
|
|
|
success = True
|
|
|
|
failure = False
|
|
|
|
skipped = None
|
|
|
|
|
|
|
|
|
2016-10-17 04:29:45 +00:00
|
|
|
class _TlsStrategy:
|
2015-09-08 19:35:15 +00:00
|
|
|
"""
|
|
|
|
Abstract base class for interception strategies.
|
|
|
|
"""
|
2016-05-29 08:23:39 +00:00
|
|
|
|
2015-09-08 19:35:15 +00:00
|
|
|
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.
|
|
|
|
"""
|
2016-05-29 08:23:39 +00:00
|
|
|
|
2015-09-08 19:35:15 +00:00
|
|
|
def __init__(self, p):
|
|
|
|
self.p = p
|
2020-11-20 18:25:26 +00:00
|
|
|
super().__init__()
|
2015-09-08 19:35:15 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
try:
|
2020-11-20 18:25:26 +00:00
|
|
|
super()._establish_tls_with_client()
|
2015-09-17 00:13:28 +00:00
|
|
|
except TlsProtocolException as e:
|
2015-09-08 19:35:15 +00:00
|
|
|
tls_strategy.record_failure(server_address)
|
|
|
|
raise e
|
|
|
|
else:
|
|
|
|
tls_strategy.record_success(server_address)
|
|
|
|
|
|
|
|
|
|
|
|
# inline script hooks below.
|
|
|
|
|
2016-07-08 01:37:33 +00:00
|
|
|
tls_strategy = None
|
2015-09-08 19:35:15 +00:00
|
|
|
|
2016-07-08 01:37:33 +00:00
|
|
|
|
2017-03-23 22:29:36 +00:00
|
|
|
def load(l):
|
2017-04-25 23:45:15 +00:00
|
|
|
l.add_option(
|
|
|
|
"tlsstrat", int, 0, "TLS passthrough strategy (0-100)",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def configure(updated):
|
2016-07-08 01:37:33 +00:00
|
|
|
global tls_strategy
|
2017-04-25 23:45:15 +00:00
|
|
|
if ctx.options.tlsstrat > 0:
|
|
|
|
tls_strategy = ProbabilisticStrategy(float(ctx.options.tlsstrat) / 100.0)
|
2015-09-08 19:35:15 +00:00
|
|
|
else:
|
2016-07-08 01:37:33 +00:00
|
|
|
tls_strategy = ConservativeStrategy()
|
2015-09-08 19:35:15 +00:00
|
|
|
|
|
|
|
|
2016-07-08 01:37:33 +00:00
|
|
|
def next_layer(next_layer):
|
2015-09-08 19:35:15 +00:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
|
2016-07-08 01:37:33 +00:00
|
|
|
if tls_strategy.should_intercept(server_address):
|
2015-09-08 19:35:15 +00:00
|
|
|
# We try to intercept.
|
|
|
|
# Monkey-Patch the layer to get feedback from the TLSLayer if interception worked.
|
|
|
|
next_layer.__class__ = TlsFeedback
|
|
|
|
else:
|
|
|
|
# We don't intercept - reply with a pass-through layer and add a "skipped" entry.
|
2016-07-09 02:57:57 +00:00
|
|
|
mitmproxy.ctx.log("TLS passthrough for %s" % repr(next_layer.server_conn.address), "info")
|
2016-07-08 01:37:33 +00:00
|
|
|
next_layer_replacement = RawTCPLayer(next_layer.ctx, ignore=True)
|
2016-06-07 22:44:20 +00:00
|
|
|
next_layer.reply.send(next_layer_replacement)
|
2016-07-08 01:37:33 +00:00
|
|
|
tls_strategy.record_skipped(server_address)
|