diff --git a/mitmproxy/addons/__init__.py b/mitmproxy/addons/__init__.py index 165106403..80e3b2cbf 100644 --- a/mitmproxy/addons/__init__.py +++ b/mitmproxy/addons/__init__.py @@ -4,7 +4,7 @@ from mitmproxy.addons import check_alpn from mitmproxy.addons import check_ca from mitmproxy.addons import clientplayback from mitmproxy.addons import core_option_validation -from mitmproxy.addons import disable_h2c_upgrade +from mitmproxy.addons import disable_h2c from mitmproxy.addons import onboarding from mitmproxy.addons import proxyauth from mitmproxy.addons import replace @@ -26,7 +26,7 @@ def default_addons(): check_alpn.CheckALPN(), check_ca.CheckCA(), clientplayback.ClientPlayback(), - disable_h2c_upgrade.DisableH2CleartextUpgrade(), + disable_h2c.DisableH2C(), onboarding.Onboarding(), proxyauth.ProxyAuth(), replace.Replace(), diff --git a/mitmproxy/addons/disable_h2c.py b/mitmproxy/addons/disable_h2c.py new file mode 100644 index 000000000..b43d52073 --- /dev/null +++ b/mitmproxy/addons/disable_h2c.py @@ -0,0 +1,41 @@ +import mitmproxy + + +class DisableH2C: + + """ + We currently only support HTTP/2 over a TLS connection. + + Some clients try to upgrade a connection from HTTP/1.1 to h2c. We need to + remove those headers to avoid protocol errors if one endpoints suddenly + starts sending HTTP/2 frames. + + Some clients might use HTTP/2 Prior Knowledge to directly initiate a session + by sending the connection preface. We just kill those flows. + """ + + def configure(self, options, updated): + pass + + def process_flow(self, f): + if f.request.headers.get('upgrade', '') == 'h2c': + mitmproxy.ctx.log.warn("HTTP/2 cleartext connections (h2c upgrade requests) are currently not supported.") + del f.request.headers['upgrade'] + if 'connection' in f.request.headers: + del f.request.headers['connection'] + if 'http2-settings' in f.request.headers: + del f.request.headers['http2-settings'] + + is_connection_preface = ( + f.request.method == 'PRI' and + f.request.path == '*' and + f.request.http_version == 'HTTP/2.0' + ) + if is_connection_preface: + f.kill() + mitmproxy.ctx.log.warn("Initiating HTTP/2 connections with prior knowledge are currently not supported.") + + # Handlers + + def request(self, f): + self.process_flow(f) diff --git a/mitmproxy/addons/disable_h2c_upgrade.py b/mitmproxy/addons/disable_h2c_upgrade.py deleted file mode 100644 index f4a36d5f0..000000000 --- a/mitmproxy/addons/disable_h2c_upgrade.py +++ /dev/null @@ -1,21 +0,0 @@ -class DisableH2CleartextUpgrade: - - """ - We currently only support HTTP/2 over a TLS connection. Some clients try - to upgrade a connection from HTTP/1.1 to h2c, so we need to remove those - headers to avoid protocol errors if one endpoints suddenly starts sending - HTTP/2 frames. - """ - - def process_flow(self, f): - if f.request.headers.get('upgrade', '') == 'h2c': - del f.request.headers['upgrade'] - if 'connection' in f.request.headers: - del f.request.headers['connection'] - if 'http2-settings' in f.request.headers: - del f.request.headers['http2-settings'] - - # Handlers - - def request(self, f): - self.process_flow(f) diff --git a/test/mitmproxy/addons/test_disable_h2c.py b/test/mitmproxy/addons/test_disable_h2c.py new file mode 100644 index 000000000..d4df8390d --- /dev/null +++ b/test/mitmproxy/addons/test_disable_h2c.py @@ -0,0 +1,39 @@ +import io +from mitmproxy import http +from mitmproxy.addons import disable_h2c +from mitmproxy.net.http import http1 +from mitmproxy.exceptions import Kill +from mitmproxy.test import tflow +from mitmproxy.test import taddons + + +class TestDisableH2CleartextUpgrade: + def test_upgrade(self): + with taddons.context() as tctx: + a = disable_h2c.DisableH2C() + tctx.configure(a) + + f = tflow.tflow() + f.request.headers['upgrade'] = 'h2c' + f.request.headers['connection'] = 'foo' + f.request.headers['http2-settings'] = 'bar' + + a.request(f) + assert 'upgrade' not in f.request.headers + assert 'connection' not in f.request.headers + assert 'http2-settings' not in f.request.headers + + def test_prior_knowledge(self): + with taddons.context() as tctx: + a = disable_h2c.DisableH2C() + tctx.configure(a) + + b = io.BytesIO(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") + f = tflow.tflow() + f.request = http.HTTPRequest.wrap(http1.read_request(b)) + f.reply.handle() + f.intercept() + + a.request(f) + assert not f.killable + assert f.reply.value == Kill diff --git a/test/mitmproxy/addons/test_disable_h2c_upgrade.py b/test/mitmproxy/addons/test_disable_h2c_upgrade.py deleted file mode 100644 index 6cab713d1..000000000 --- a/test/mitmproxy/addons/test_disable_h2c_upgrade.py +++ /dev/null @@ -1,17 +0,0 @@ -from mitmproxy.addons import disable_h2c_upgrade -from mitmproxy.test import tflow - - -class TestTermLog: - def test_simple(self): - a = disable_h2c_upgrade.DisableH2CleartextUpgrade() - - f = tflow.tflow() - f.request.headers['upgrade'] = 'h2c' - f.request.headers['connection'] = 'foo' - f.request.headers['http2-settings'] = 'bar' - - a.request(f) - assert 'upgrade' not in f.request.headers - assert 'connection' not in f.request.headers - assert 'http2-settings' not in f.request.headers