Repository: mitmproxy/mitmproxy

Issue:
allow_remote=false does not prevent remote access
##### Steps to reproduce the problem:

1. Run mitmproxy on a publicly routable host, with default configuration including `listen_host=""`, `listen_port=8080`, and `allow_remote=false`.
2. From a host on a different network, send a request through that instance of mitmproxy, e.g. with `curl --proxy http://your-host.example:8080`

The default `allow_remote=false` should prevent this request from succeeding. However, it is served by mitmproxy just fine.

##### Any other comments? What have you tried so far?

I have a laptop sitting in the “DMZ” of a home router, which is globally IPv4 routable. I also have a VPS which is globally IPv4 routable. Both the laptop and the VPS are running Ubuntu 16.04 “Xenial Xerus”. I can reproduce the problem with mitmproxy running on the VPS and curl on the laptop, as well as vice-versa.

Both tcpdump and mitmproxy’s own Details pane show the request as originating from a remote network.

I only noticed this because I saw strange flows in a mitmproxy instance that I spun up on the laptop.

##### System information

Mitmproxy: 3.0.3 binary
Python:    3.5.2
OpenSSL:   OpenSSL 1.1.0g  2 Nov 2017
Platform:  Linux-4.4.0-116-generic-x86_64-with-debian-stretch-sid



Discussion:
Thanks - I can verify it!
Not sure why yet, but the tests and the implementation are not that complex... let me look into that!
Looks like the check fails because IPv4 source addresses get mapped into IPv6 address with `::ffff:<IPv4>`, and then it apparently becomes a private address range in https://github.com/python/cpython/blob/10b134a07c898c2fbc5fd3582503680a54ed80a2/Lib/ipaddress.py#L2258.

Resolving patch:
diff --git a/mitmproxy/addons/allowremote.py b/mitmproxy/addons/allowremote.py
--- a/mitmproxy/addons/allowremote.py
+++ b/mitmproxy/addons/allowremote.py
@@ -14,11 +14,13 @@ def load(self, loader):
         )
 
     def clientconnect(self, layer):
-        address = layer.client_conn.address
+        address = ipaddress.ip_address(layer.client_conn.address[0])
+        if isinstance(address, ipaddress.IPv6Address):
+            address = address.ipv4_mapped or address
 
         accept_connection = (
             ctx.options.allow_remote or
-            ipaddress.ip_address(address[0]).is_private or
+            ipaddress.ip_address(address).is_private or
             ctx.options.proxyauth is not None
         )
 


Test patch:
diff --git a/test/mitmproxy/addons/test_allowremote.py b/test/mitmproxy/addons/test_allowremote.py
--- a/test/mitmproxy/addons/test_allowremote.py
+++ b/test/mitmproxy/addons/test_allowremote.py
@@ -5,27 +5,49 @@
 from mitmproxy.test import taddons
 
 
-@pytest.mark.parametrize("allow_remote, ip, should_be_killed", [
-    (True, "192.168.1.3", False),
-    (True, "122.176.243.101", False),
-    (False, "192.168.1.3", False),
-    (False, "122.176.243.101", True),
-    (True, "::ffff:1:2", False),
-    (True, "fe80::", False),
-    (True, "2001:4860:4860::8888", False),
-    (False, "::ffff:1:2", False),
-    (False, "fe80::", False),
-    (False, "2001:4860:4860::8888", True),
+@pytest.mark.parametrize("allow_remote, should_be_killed, address", [
+    (True, False, ("10.0.0.1",)),
+    (True, False, ("172.20.0.1",)),
+    (True, False, ("192.168.1.1",)),
+    (True, False, ("1.1.1.1",)),
+    (True, False, ("8.8.8.8",)),
+    (True, False, ("216.58.207.174",)),
+    (True, False, ("::ffff:1.1.1.1",)),
+    (True, False, ("::ffff:8.8.8.8",)),
+    (True, False, ("::ffff:216.58.207.174",)),
+    (True, False, ("::ffff:10.0.0.1",)),
+    (True, False, ("::ffff:172.20.0.1",)),
+    (True, False, ("::ffff:192.168.1.1",)),
+    (True, False, ("fe80::",)),
+    (True, False, ("2001:4860:4860::8888",)),
+    (False, False, ("10.0.0.1",)),
+    (False, False, ("172.20.0.1",)),
+    (False, False, ("192.168.1.1",)),
+    (False, True, ("1.1.1.1",)),
+    (False, True, ("8.8.8.8",)),
+    (False, True, ("216.58.207.174",)),
+    (False, True, ("::ffff:1.1.1.1",)),
+    (False, True, ("::ffff:8.8.8.8",)),
+    (False, True, ("::ffff:216.58.207.174",)),
+    (False, False, ("::ffff:10.0.0.1",)),
+    (False, False, ("::ffff:172.20.0.1",)),
+    (False, False, ("::ffff:192.168.1.1",)),
+    (False, False, ("fe80::",)),
+    (False, True, ("2001:4860:4860::8888",)),
 ])
 @pytest.mark.asyncio
-async def test_allowremote(allow_remote, ip, should_be_killed):
+async def test_allowremote(allow_remote, should_be_killed, address):
+    if allow_remote:
+        # prevent faulty tests
+        assert not should_be_killed
+
     ar = allowremote.AllowRemote()
     up = proxyauth.ProxyAuth()
     with taddons.context(ar, up) as tctx:
         tctx.options.allow_remote = allow_remote
 
         with mock.patch('mitmproxy.proxy.protocol.base.Layer') as layer:
-            layer.client_conn.address = (ip, 12345)
+            layer.client_conn.address = address
 
             ar.clientconnect(layer)
             if should_be_killed: