aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/README1
-rw-r--r--examples/dns_spoofing.py35
-rw-r--r--libmproxy/proxy/server.py14
-rw-r--r--test/test_server.py31
-rw-r--r--test/tservers.py4
5 files changed, 79 insertions, 6 deletions
diff --git a/examples/README b/examples/README
index 85ab272a..f24c4de7 100644
--- a/examples/README
+++ b/examples/README
@@ -1,6 +1,7 @@
# inline script examples
add_header.py Simple script that just adds a header to every request.
change_upstream_proxy.py Dynamically change the upstream proxy
+dns_spoofing.py Use mitmproxy in a DNS spoofing scenario.
dup_and_replay.py Duplicates each request, changes it, and then replays the modified request.
iframe_injector.py Inject configurable iframe into pages.
modify_form.py Modify all form submissions to add a parameter.
diff --git a/examples/dns_spoofing.py b/examples/dns_spoofing.py
new file mode 100644
index 00000000..cfba7c54
--- /dev/null
+++ b/examples/dns_spoofing.py
@@ -0,0 +1,35 @@
+"""
+This inline scripts makes it possible to use mitmproxy in scenarios where IP spoofing has been used to redirect
+connections to mitmproxy. The way this works is that we rely on either the TLS Server Name Indication (SNI) or the
+Host header of the HTTP request.
+Of course, this is not foolproof - if an HTTPS connection comes without SNI, we don't
+know the actual target and cannot construct a certificate that looks valid.
+Similarly, if there's no Host header or a spoofed Host header, we're out of luck as well.
+Using transparent mode is the better option most of the time.
+
+Usage:
+ mitmproxy
+ -p 80
+ -R http://example.com/ // Used as the target location if no Host header is present
+ mitmproxy
+ -p 443
+ -R https://example.com/ // Used as the target locaction if neither SNI nor host header are present.
+
+mitmproxy will always connect to the default location first, so it must be reachable.
+As a workaround, you can spawn an arbitrary HTTP server and use that for both endpoints, e.g.
+mitmproxy -p 80 -R http://localhost:8000
+mitmproxy -p 443 -R https2http://localhost:8000
+"""
+
+
+def request(context, flow):
+ if flow.client_conn.ssl_established:
+ # TLS SNI or Host header
+ flow.request.host = flow.client_conn.connection.get_servername() or flow.request.pretty_host(hostheader=True)
+
+ # If you use a https2http location as default destination, these attributes need to be corrected as well:
+ flow.request.port = 443
+ flow.request.scheme = "https"
+ else:
+ # Host header
+ flow.request.host = flow.request.pretty_host(hostheader=True) \ No newline at end of file
diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py
index ea78d964..8544ff72 100644
--- a/libmproxy/proxy/server.py
+++ b/libmproxy/proxy/server.py
@@ -260,11 +260,12 @@ class ConnectionHandler:
sans = []
if self.server_conn.ssl_established and (not self.config.no_upstream_cert):
upstream_cert = self.server_conn.cert
+ sans.extend(upstream_cert.altnames)
if upstream_cert.cn:
+ sans.append(host)
host = upstream_cert.cn.decode("utf8").encode("idna")
- sans = upstream_cert.altnames
- elif self.server_conn.sni:
- sans = [self.server_conn.sni]
+ if self.server_conn.sni:
+ sans.append(self.server_conn.sni)
ret = self.config.certstore.get_cert(host, sans)
if not ret:
@@ -285,7 +286,12 @@ class ConnectionHandler:
if sni != self.server_conn.sni:
self.log("SNI received: %s" % sni, "debug")
- self.server_reconnect(sni) # reconnect to upstream server with SNI
+ # We should only re-establish upstream SSL if one of the following conditions is true:
+ # - We established SSL with the server previously
+ # - We initially wanted to establish SSL with the server,
+ # but the server refused to negotiate without SNI.
+ if self.server_conn.ssl_established or hasattr(self.server_conn, "may_require_sni"):
+ self.server_reconnect(sni) # reconnect to upstream server with SNI
# Now, change client context to reflect changed certificate:
cert, key, chain_file = self.find_cert()
new_context = self.client_conn._create_ssl_context(
diff --git a/test/test_server.py b/test/test_server.py
index a611d30f..e387293f 100644
--- a/test/test_server.py
+++ b/test/test_server.py
@@ -1,5 +1,6 @@
import socket, time
from libmproxy.proxy.config import HostMatcher
+import libpathod
from netlib import tcp, http_auth, http
from libpathod import pathoc, pathod
from netlib.certutils import SSLCert
@@ -332,6 +333,36 @@ class TestReverse(tservers.ReverseProxTest, CommonMixin, TcpMixin):
reverse = True
+class TestHttps2Http(tservers.ReverseProxTest):
+ @classmethod
+ def get_proxy_config(cls):
+ d = super(TestHttps2Http, cls).get_proxy_config()
+ d["upstream_server"][0] = True
+ return d
+
+ def pathoc(self, ssl, sni=None):
+ """
+ Returns a connected Pathoc instance.
+ """
+ p = libpathod.pathoc.Pathoc(("localhost", self.proxy.port), ssl=ssl, sni=sni)
+ p.connect()
+ return p
+
+ def test_all(self):
+ p = self.pathoc(ssl=True)
+ assert p.request("get:'/p/200'").status_code == 200
+
+ def test_sni(self):
+ p = self.pathoc(ssl=True, sni="example.com")
+ assert p.request("get:'/p/200'").status_code == 200
+ assert all("Error in handle_sni" not in msg for msg in self.proxy.log)
+
+ def test_http(self):
+ p = self.pathoc(ssl=False)
+ assert p.request("get:'/p/200'").status_code == 400
+
+
+
class TestTransparent(tservers.TransparentProxTest, CommonMixin, TcpMixin):
ssl = False
diff --git a/test/tservers.py b/test/tservers.py
index 37929d1a..30c8b52e 100644
--- a/test/tservers.py
+++ b/test/tservers.py
@@ -218,12 +218,12 @@ class ReverseProxTest(ProxTestBase):
@classmethod
def get_proxy_config(cls):
d = ProxTestBase.get_proxy_config()
- d["upstream_server"] = (
+ d["upstream_server"] = [
True if cls.ssl else False,
True if cls.ssl else False,
"127.0.0.1",
cls.server.port
- )
+ ]
d["mode"] = "reverse"
return d