From 18ca5a63699d9376d1711ab0ecdb153106416d64 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 15 Jul 2021 11:33:20 +0200 Subject: [PATCH] transparent mode: re-use client SNI when connecting upstream (#4680) --- mitmproxy/proxy/layers/http/__init__.py | 25 ++++++++++++-- test/mitmproxy/proxy/layers/http/test_http.py | 32 +++++++++++++++++ .../mitmproxy/proxy/layers/http/test_http2.py | 34 +++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/mitmproxy/proxy/layers/http/__init__.py b/mitmproxy/proxy/layers/http/__init__.py index 806e65568..05017f2cd 100644 --- a/mitmproxy/proxy/layers/http/__init__.py +++ b/mitmproxy/proxy/layers/http/__init__.py @@ -710,8 +710,22 @@ class HttpLayer(layer.Layer): yield from self.event_to_child(self.streams[stream_id], event) elif isinstance(event, events.ConnectionEvent): if event.connection == self.context.server and self.context.server not in self.connections: - # We didn't do anything with this connection yet, now the peer has closed it - let's close it too! - yield commands.CloseConnection(event.connection) + # We didn't do anything with this connection yet, now the peer is doing something. + if isinstance(event, events.ConnectionClosed): + # The peer has closed it - let's close it too! + yield commands.CloseConnection(event.connection) + elif isinstance(event, events.DataReceived): + # The peer has sent data. This can happen with HTTP/2 servers that already send a settings frame. + child_layer: HttpConnection + if self.context.server.alpn == b"h2": + child_layer = Http2Client(self.context.fork()) + else: + child_layer = Http1Client(self.context.fork()) + self.connections[self.context.server] = child_layer + yield from self.event_to_child(child_layer, events.Start()) + yield from self.event_to_child(child_layer, event) + else: + raise AssertionError(f"Unexpected event: {event}") else: handler = self.connections[event.connection] yield from self.event_to_child(handler, event) @@ -811,7 +825,12 @@ class HttpLayer(layer.Layer): send_connect = event.tls or self.mode != HTTPMode.upstream stack /= _upstream_proxy.HttpUpstreamProxy.make(context, send_connect) if event.tls: - context.server.sni = event.address[0] + # Assume that we are in transparent mode and lazily did not open a connection yet. + # We don't want the IP (which is the address) as the upstream SNI, but the client's SNI instead. + if self.mode == HTTPMode.transparent and event.address == self.context.server.address: + context.server.sni = self.context.client.sni or event.address[0] + else: + context.server.sni = event.address[0] stack /= tls.ServerTLSLayer(context) stack /= HttpClient(context) diff --git a/test/mitmproxy/proxy/layers/http/test_http.py b/test/mitmproxy/proxy/layers/http/test_http.py index ed7ceed77..046cec062 100644 --- a/test/mitmproxy/proxy/layers/http/test_http.py +++ b/test/mitmproxy/proxy/layers/http/test_http.py @@ -1174,3 +1174,35 @@ def test_reuse_error(tctx): ) assert b"502 Bad Gateway" in error_html() assert b"tls verify failed" in error_html() + + +def test_transparent_sni(tctx): + """Test that we keep the SNI in lazy transparent mode.""" + tctx.client.sni = "example.com" + tctx.server.address = ("192.0.2.42", 443) + tctx.server.tls = True + + flow = Placeholder(HTTPFlow) + + server = Placeholder(Server) + assert ( + Playbook(http.HttpLayer(tctx, HTTPMode.transparent)) + >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\n\r\n") + << http.HttpRequestHeadersHook(flow) + >> reply() + << http.HttpRequestHook(flow) + >> reply() + << OpenConnection(server) + ) + assert server().address == ("192.0.2.42", 443) + assert server().sni == "example.com" + + +def test_original_server_disconnects(tctx): + """Test that we correctly handle the case where the initial server conn is just closed.""" + tctx.server.state = ConnectionState.OPEN + assert ( + Playbook(http.HttpLayer(tctx, HTTPMode.transparent)) + >> ConnectionClosed(tctx.server) + << CloseConnection(tctx.server) + ) diff --git a/test/mitmproxy/proxy/layers/http/test_http2.py b/test/mitmproxy/proxy/layers/http/test_http2.py index 387674b35..acd8500da 100644 --- a/test/mitmproxy/proxy/layers/http/test_http2.py +++ b/test/mitmproxy/proxy/layers/http/test_http2.py @@ -693,3 +693,37 @@ class TestClient: >> DataReceived(tctx.server, frame_factory.build_data_frame(b"foo").serialize()) << SendData(tctx.server, frame_factory.build_rst_stream_frame(1, ErrorCodes.STREAM_CLOSED).serialize()) ) # important: no ResponseData event here! + + +def test_early_server_data(tctx): + playbook, cff = start_h2_client(tctx) + sff = FrameFactory() + + tctx.server.address = ("example.com", 80) + tctx.server.state = ConnectionState.OPEN + tctx.server.alpn = b"h2" + + flow = Placeholder(HTTPFlow) + server1 = Placeholder(bytes) + server2 = Placeholder(bytes) + assert ( + playbook + >> DataReceived(tctx.client, + cff.build_headers_frame(example_request_headers, flags=["END_STREAM"]).serialize()) + << http.HttpRequestHeadersHook(flow) + >> reply() + << (h := http.HttpRequestHook(flow)) + # Surprise! We get data from the server before the request hook finishes. + >> DataReceived(tctx.server, sff.build_settings_frame({}).serialize()) + << SendData(tctx.server, server1) + # Request hook finishes... + >> reply(to=h) + << SendData(tctx.server, server2) + ) + assert [type(x) for x in decode_frames(server1())] == [ + hyperframe.frame.SettingsFrame, + hyperframe.frame.SettingsFrame, + ] + assert [type(x) for x in decode_frames(server2())] == [ + hyperframe.frame.HeadersFrame, + ]