From 558b0b664277769310ce99c7d8136eca1ba74658 Mon Sep 17 00:00:00 2001 From: Fujun Lv Date: Wed, 6 Dec 2023 18:00:17 +0800 Subject: [PATCH] Fix for --allow-hosts/--ignore-hosts options in WireGuard mode (#5930) (#6513) --- CHANGELOG.md | 6 ++ mitmproxy/addons/next_layer.py | 16 ++++- test/mitmproxy/addons/test_next_layer.py | 86 +++++++++++++++++++++--- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51bdd36345..14f237559f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ ## Unreleased: mitmproxy next +* Improved handling for `--allow-hosts`/`--ignore-hosts` options in WireGuard mode (#5930). + ([#6513](https://github.com/mitmproxy/mitmproxy/pull/6513), @dsphper) +* DNS resolution is now exempted from `--ignore-hosts` in WireGuard Mode. + ([#6513](https://github.com/mitmproxy/mitmproxy/pull/6513), @dsphper) +* For plaintext traffic, `--ignore-hosts` now also takes HTTP/1 host headers into account. + ([#6513](https://github.com/mitmproxy/mitmproxy/pull/6513), @dsphper) * Fix empty cookie attributes being set to `Key=` instead of `Key` ([#5084](https://github.com/mitmproxy/mitmproxy/pull/5084), @Speedlulu) * Scripts with relative paths are now loaded relative to the config file and not where the command is ran diff --git a/mitmproxy/addons/next_layer.py b/mitmproxy/addons/next_layer.py index 2687e94749..ce63f81390 100644 --- a/mitmproxy/addons/next_layer.py +++ b/mitmproxy/addons/next_layer.py @@ -210,7 +210,11 @@ def _ignore_connection( """ if not ctx.options.ignore_hosts and not ctx.options.allow_hosts: return False - + # Special handling for wireguard mode: if the hostname is "10.0.0.53", do not ignore the connection + if isinstance( + context.client.proxy_mode, mode_specs.WireGuardMode + ) and context.server.address == ("10.0.0.53", 53): + return False hostnames: list[str] = [] if context.server.peername and (peername := context.server.peername[0]): hostnames.append(peername) @@ -220,7 +224,9 @@ def _ignore_connection( client_hello := self._get_client_hello(context, data_client) ) and client_hello.sni: hostnames.append(client_hello.sni) - + # If the client data is not a TLS record, try to extract the domain from the HTTP request + elif host := self._extract_http1_host_header(data_client): + hostnames.append(host) if not hostnames: return False @@ -239,6 +245,12 @@ def _ignore_connection( else: # pragma: no cover raise AssertionError() + @staticmethod + def _extract_http1_host_header(data_client: bytes) -> str: + pattern = rb"Host:\s+(.+?)\r\n" + match = re.search(pattern, data_client) + return match.group(1).decode() if match else "" + def _get_client_hello( self, context: Context, data_client: bytes ) -> ClientHello | None: diff --git a/test/mitmproxy/addons/test_next_layer.py b/test/mitmproxy/addons/test_next_layer.py index c2a0840713..b91a942237 100644 --- a/test/mitmproxy/addons/test_next_layer.py +++ b/test/mitmproxy/addons/test_next_layer.py @@ -90,6 +90,8 @@ dns_query = bytes.fromhex("002a01000001000000000000076578616d706c6503636f6d0000010001") +http_query = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" + class TestNextLayer: def test_configure(self): @@ -101,19 +103,40 @@ def test_configure(self): ) @pytest.mark.parametrize( - "ignore, allow, transport_protocol, server_address, data_client, result", + "mode, ignore, allow, transport_protocol, server_address, data_client, result", [ + pytest.param( + [], + [], + ["example.com"], + "tcp", + "example.org", + http_query, + False, + id="extract host from http request", + ), + pytest.param( + ["wireguard"], + ["example.com"], + [], + "udp", + "10.0.0.53", + dns_query, + False, + id="special handling for wireguard mode", + ), # ignore pytest.param( - [], [], "example.com", "tcp", b"", False, id="nothing ignored" + [], [], [], "example.com", "tcp", b"", False, id="nothing ignored" ), pytest.param( - ["example.com"], [], "tcp", "example.com", b"", True, id="address" + [], ["example.com"], [], "tcp", "example.com", b"", True, id="address" ), pytest.param( - ["1.2.3.4"], [], "tcp", "example.com", b"", True, id="ip address" + [], ["1.2.3.4"], [], "tcp", "example.com", b"", True, id="ip address" ), pytest.param( + [], ["example.com"], [], "tcp", @@ -123,9 +146,17 @@ def test_configure(self): id="partial address match", ), pytest.param( - ["example.com"], [], "tcp", None, b"", False, id="no destination info" + [], + ["example.com"], + [], + "tcp", + None, + b"", + False, + id="no destination info", ), pytest.param( + [], ["example.com"], [], "tcp", @@ -135,6 +166,7 @@ def test_configure(self): id="no sni", ), pytest.param( + [], ["example.com"], [], "tcp", @@ -144,6 +176,7 @@ def test_configure(self): id="sni", ), pytest.param( + [], ["example.com"], [], "tcp", @@ -153,6 +186,7 @@ def test_configure(self): id="incomplete client hello", ), pytest.param( + [], ["example.com"], [], "tcp", @@ -162,6 +196,7 @@ def test_configure(self): id="invalid client hello", ), pytest.param( + [], ["example.com"], [], "tcp", @@ -171,6 +206,7 @@ def test_configure(self): id="sni mismatch", ), pytest.param( + [], ["example.com"], [], "udp", @@ -180,6 +216,7 @@ def test_configure(self): id="dtls sni", ), pytest.param( + [], ["example.com"], [], "udp", @@ -189,6 +226,7 @@ def test_configure(self): id="incomplete dtls client hello", ), pytest.param( + [], ["example.com"], [], "udp", @@ -198,16 +236,38 @@ def test_configure(self): id="invalid dtls client hello", ), pytest.param( - ["example.com"], [], "udp", None, quic_client_hello, True, id="quic sni" + [], + ["example.com"], + [], + "udp", + None, + quic_client_hello, + True, + id="quic sni", ), # allow pytest.param( - [], ["example.com"], "tcp", "example.com", b"", False, id="allow: allow" + [], + [], + ["example.com"], + "tcp", + "example.com", + b"", + False, + id="allow: allow", ), pytest.param( - [], ["example.com"], "tcp", "example.org", b"", True, id="allow: ignore" + [], + [], + ["example.com"], + "tcp", + "example.org", + b"", + True, + id="allow: ignore", ), pytest.param( + [], [], ["example.com"], "tcp", @@ -220,6 +280,7 @@ def test_configure(self): ) def test_ignore_connection( self, + mode: list[str], ignore: list[str], allow: list[str], transport_protocol: TransportProtocol, @@ -233,7 +294,8 @@ def test_ignore_connection( tctx.configure(nl, ignore_hosts=ignore) if allow: tctx.configure(nl, allow_hosts=allow) - + if mode: + tctx.options.mode = mode ctx = Context( Client(peername=("192.168.0.42", 51234), sockname=("0.0.0.0", 8080)), tctx.options, @@ -242,7 +304,10 @@ def test_ignore_connection( if server_address: ctx.server.address = (server_address, 443) ctx.server.peername = ("1.2.3.4", 443) - + if "wireguard" in tctx.options.mode: + ctx.server.peername = ("10.0.0.53", 53) + ctx.server.address = ("10.0.0.53", 53) + ctx.client.proxy_mode = ProxyMode.parse("wireguard") if result is NeedsMoreData: with pytest.raises(NeedsMoreData): nl._ignore_connection(ctx, data_client) @@ -268,6 +333,7 @@ def test_next_layer(self, monkeypatch, caplog): assert m.layer is preexisting m.layer = None + monkeypatch.setattr(m, "data_client", lambda: http_query) nl.next_layer(m) assert m.layer