Merge pull request #893 from dweinstein/sni-ignore

Add ignore based on TLS ClientHello SNI
This commit is contained in:
Maximilian Hils 2016-01-26 21:07:12 +01:00
commit ce132dc180
4 changed files with 98 additions and 58 deletions

View File

@ -31,9 +31,9 @@ mitmproxy allows you to specify a regex which is matched against a ``host:port``
There are two important quirks to consider:
- **In transparent mode, the ignore pattern is matched against the IP.** While we usually infer the
- **In transparent mode, the ignore pattern is matched against the IP and ClientHello SNI host.** While we usually infer the
hostname from the Host header if the :option:`--host` argument is passed to mitmproxy, we do not
have access to this information before the SSL handshake.
have access to this information before the SSL handshake. If the client uses SNI however, then we treat the SNI host as an ignore target.
- In regular mode, explicit HTTP requests are never ignored. [#explicithttp]_ The ignore pattern is
applied on CONNECT requests, which initiate HTTPS or clear-text WebSocket connections.

View File

@ -28,12 +28,12 @@ as late as possible; this makes server replay without any outgoing connections p
from __future__ import (absolute_import, print_function, division)
from .base import Layer, ServerConnectionMixin, Kill
from .http import Http1Layer, UpstreamConnectLayer, Http2Layer
from .tls import TlsLayer, is_tls_record_magic
from .tls import TlsLayer, is_tls_record_magic, TlsClientHello
from .rawtcp import RawTCPLayer
__all__ = [
"Layer", "ServerConnectionMixin", "Kill",
"Http1Layer", "UpstreamConnectLayer", "Http2Layer",
"TlsLayer", "is_tls_record_magic",
"TlsLayer", "is_tls_record_magic", "TlsClientHello"
"RawTCPLayer"
]

View File

@ -221,6 +221,80 @@ def is_tls_record_magic(d):
d[2] in ('\x00', '\x01', '\x02', '\x03')
)
def get_client_hello(client_conn):
"""
Peek into the socket and read all records that contain the initial client hello message.
client_conn:
The :py:class:`client connection <libmproxy.models.ClientConnection>`.
Returns:
The raw handshake packet bytes, without TLS record header(s).
"""
client_hello = ""
client_hello_size = 1
offset = 0
while len(client_hello) < client_hello_size:
record_header = client_conn.rfile.peek(offset + 5)[offset:]
if not is_tls_record_magic(record_header) or len(record_header) != 5:
raise TlsProtocolException('Expected TLS record, got "%s" instead.' % record_header)
record_size = struct.unpack("!H", record_header[3:])[0] + 5
record_body = client_conn.rfile.peek(offset + record_size)[offset + 5:]
if len(record_body) != record_size - 5:
raise TlsProtocolException("Unexpected EOF in TLS handshake: %s" % record_body)
client_hello += record_body
offset += record_size
client_hello_size = struct.unpack("!I", '\x00' + client_hello[1:4])[0] + 4
return client_hello
class TlsClientHello(object):
def __init__(self, raw_client_hello):
self._client_hello = ClientHello.parse(raw_client_hello)
def raw(self):
return self._client_hello
@property
def client_cipher_suites(self):
return self._client_hello.cipher_suites.cipher_suites
@property
def client_sni(self):
for extension in self._client_hello.extensions:
if (extension.type == 0x00 and len(extension.server_names) == 1
and extension.server_names[0].type == 0):
return extension.server_names[0].name
@property
def client_alpn_protocols(self):
for extension in self._client_hello.extensions:
if extension.type == 0x10:
return list(extension.alpn_protocols)
@classmethod
def from_client_conn(cls, client_conn):
"""
Peek into the connection, read the initial client hello and parse it to obtain ALPN values.
client_conn:
The :py:class:`client connection <libmproxy.models.ClientConnection>`.
Returns:
:py:class:`client hello <libmproxy.protocol.tls.TlsClientHello>`.
"""
try:
raw_client_hello = get_client_hello(client_conn)[4:] # exclude handshake header.
except ProtocolException as e:
raise TlsProtocolException('Cannot read raw Client Hello: %s' % repr(e))
try:
return cls(raw_client_hello)
except ConstructError as e:
raise TlsProtocolException('Cannot parse Client Hello: %s, Raw Client Hello: %s' % \
(repr(e), raw_client_hello.encode("hex")))
def __repr__(self):
return "TlsClientHello( sni: %s alpn_protocols: %s, cipher_suites: %s)" % \
(self.client_sni, self.client_alpn_protocols, self.client_cipher_suites)
class TlsLayer(Layer):
def __init__(self, ctx, client_tls, server_tls):
@ -281,60 +355,18 @@ class TlsLayer(Layer):
else:
return "TlsLayer(inactive)"
def _get_client_hello(self):
"""
Peek into the socket and read all records that contain the initial client hello message.
Returns:
The raw handshake packet bytes, without TLS record header(s).
"""
client_hello = ""
client_hello_size = 1
offset = 0
while len(client_hello) < client_hello_size:
record_header = self.client_conn.rfile.peek(offset + 5)[offset:]
if not is_tls_record_magic(record_header) or len(record_header) != 5:
raise TlsProtocolException('Expected TLS record, got "%s" instead.' % record_header)
record_size = struct.unpack("!H", record_header[3:])[0] + 5
record_body = self.client_conn.rfile.peek(offset + record_size)[offset + 5:]
if len(record_body) != record_size - 5:
raise TlsProtocolException("Unexpected EOF in TLS handshake: %s" % record_body)
client_hello += record_body
offset += record_size
client_hello_size = struct.unpack("!I", '\x00' + client_hello[1:4])[0] + 4
return client_hello
def _parse_client_hello(self):
"""
Peek into the connection, read the initial client hello and parse it to obtain ALPN values.
"""
try:
raw_client_hello = self._get_client_hello()[4:] # exclude handshake header.
except ProtocolException as e:
parsed = TlsClientHello.from_client_conn(self.client_conn)
self.client_sni = parsed.client_sni
self.client_alpn_protocols = parsed.client_alpn_protocols
self.client_ciphers = parsed.client_cipher_suites
except TlsProtocolException as e:
self.log("Cannot parse Client Hello: %s" % repr(e), "error")
return
try:
client_hello = ClientHello.parse(raw_client_hello)
except ConstructError as e:
self.log("Cannot parse Client Hello: %s" % repr(e), "error")
self.log("Raw Client Hello: %s" % raw_client_hello.encode("hex"), "debug")
return
self.client_ciphers = client_hello.cipher_suites.cipher_suites
for extension in client_hello.extensions:
if extension.type == 0x00:
if len(extension.server_names) != 1 or extension.server_names[0].type != 0:
self.log("Unknown Server Name Indication: %s" % extension.server_names, "error")
self.client_sni = extension.server_names[0].name
elif extension.type == 0x10:
self.client_alpn_protocols = list(extension.alpn_protocols)
self.log(
"Parsed Client Hello: sni=%s, alpn=%s" % (self.client_sni, self.client_alpn_protocols),
"debug"
)
def connect(self):
if not self.server_conn:

View File

@ -4,15 +4,14 @@ import sys
import six
from libmproxy.exceptions import ProtocolException
from libmproxy.exceptions import ProtocolException, TlsProtocolException
from netlib.exceptions import TcpException
from ..protocol import (
RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin,
UpstreamConnectLayer
UpstreamConnectLayer, TlsClientHello
)
from .modes import HttpProxy, HttpUpstreamProxy, ReverseProxy
class RootContext(object):
"""
The outermost context provided to the root layer.
@ -48,16 +47,25 @@ class RootContext(object):
return self.channel.ask("next_layer", layer)
def _next_layer(self, top_layer):
# 1. Check for --ignore.
if self.config.check_ignore(top_layer.server_conn.address):
return RawTCPLayer(top_layer, logging=False)
try:
d = top_layer.client_conn.rfile.peek(3)
except TcpException as e:
six.reraise(ProtocolException, ProtocolException(str(e)), sys.exc_info()[2])
client_tls = is_tls_record_magic(d)
# 1. check for --ignore
if self.config.check_ignore:
ignore = self.config.check_ignore(top_layer.server_conn.address)
if not ignore and client_tls:
try:
client_hello = TlsClientHello.from_client_conn(self.client_conn)
except TlsProtocolException as e:
self.log("Cannot parse Client Hello: %s" % repr(e), "error")
else:
ignore = self.config.check_ignore((client_hello.client_sni, 443))
if ignore:
return RawTCPLayer(top_layer, logging=False)
# 2. Always insert a TLS layer, even if there's neither client nor server tls.
# An inline script may upgrade from http to https,
# in which case we need some form of TLS layer.