diff --git a/CHANGELOG.md b/CHANGELOG.md index 39d1eae6f..29f6ce56b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ ([#7067](https://github.com/mitmproxy/mitmproxy/pull/7067), @errorxyz) - mitmproxy now officially supports Python 3.13. ([#6934](https://github.com/mitmproxy/mitmproxy/pull/6934), @mhils) +- Addon to update the hosts in alt-svc header in reverse mode + ([#7093](https://github.com/mitmproxy/mitmproxy/pull/7093), @errorxyz) ## 02 August 2024: mitmproxy 10.4.2 diff --git a/mitmproxy/addons/__init__.py b/mitmproxy/addons/__init__.py index 71d82defb..074072872 100644 --- a/mitmproxy/addons/__init__.py +++ b/mitmproxy/addons/__init__.py @@ -27,6 +27,7 @@ from mitmproxy.addons import stickyauth from mitmproxy.addons import stickycookie from mitmproxy.addons import strip_ech from mitmproxy.addons import tlsconfig +from mitmproxy.addons import update_alt_svc from mitmproxy.addons import upstream_auth @@ -62,4 +63,5 @@ def default_addons(): savehar.SaveHar(), tlsconfig.TlsConfig(), upstream_auth.UpstreamAuth(), + update_alt_svc.UpdateAltSvc(), ] diff --git a/mitmproxy/addons/update_alt_svc.py b/mitmproxy/addons/update_alt_svc.py new file mode 100644 index 000000000..fa514e28b --- /dev/null +++ b/mitmproxy/addons/update_alt_svc.py @@ -0,0 +1,33 @@ +import re + +from mitmproxy import ctx +from mitmproxy.http import HTTPFlow +from mitmproxy.proxy import mode_specs + +ALT_SVC = "alt-svc" +HOST_PATTERN = r"([a-zA-Z0-9.-]*:\d{1,5})" + + +def update_alt_svc_header(header: str, port: int) -> str: + return re.sub(HOST_PATTERN, f":{port}", header) + + +class UpdateAltSvc: + def load(self, loader): + loader.add_option( + "keep_alt_svc_header", + bool, + False, + "Reverse Proxy: Keep Alt-Svc headers as-is, even if they do not point to mitmproxy. Enabling this option may cause clients to bypass the proxy.", + ) + + def responseheaders(self, flow: HTTPFlow): + assert flow.response + if ( + not ctx.options.keep_alt_svc_header + and isinstance(flow.client_conn.proxy_mode, mode_specs.ReverseMode) + and ALT_SVC in flow.response.headers + ): + _, listen_port, *_ = flow.client_conn.sockname + headers = flow.response.headers + headers[ALT_SVC] = update_alt_svc_header(headers[ALT_SVC], listen_port) diff --git a/test/mitmproxy/addons/test_update_alt_svc.py b/test/mitmproxy/addons/test_update_alt_svc.py new file mode 100644 index 000000000..e0509d2e8 --- /dev/null +++ b/test/mitmproxy/addons/test_update_alt_svc.py @@ -0,0 +1,44 @@ +from mitmproxy import http +from mitmproxy.addons import update_alt_svc +from mitmproxy.proxy.mode_specs import ProxyMode +from mitmproxy.test import taddons +from mitmproxy.test import tflow + + +def test_simple(): + header = 'h3="example.com:443"; ma=3600, h2=":443"; ma=3600' + modified = update_alt_svc.update_alt_svc_header(header, 1234) + assert modified == 'h3=":1234"; ma=3600, h2=":1234"; ma=3600' + + +def test_updates_alt_svc_header(): + upd = update_alt_svc.UpdateAltSvc() + with taddons.context(upd) as ctx: + headers = http.Headers( + host="example.com", + content_type="application/xml", + alt_svc='h3="example.com:443"; ma=3600, h2=":443"; ma=3600', + ) + resp = tflow.tresp(headers=headers) + f = tflow.tflow(resp=resp) + f.client_conn.sockname = ("", 1234) + + upd.responseheaders(f) + assert ( + f.response.headers["alt-svc"] + == 'h3="example.com:443"; ma=3600, h2=":443"; ma=3600' + ) + + ctx.options.keep_alt_svc_header = True + f.client_conn.proxy_mode = ProxyMode.parse("reverse:https://example.com") + upd.responseheaders(f) + assert ( + f.response.headers["alt-svc"] + == 'h3="example.com:443"; ma=3600, h2=":443"; ma=3600' + ) + + ctx.options.keep_alt_svc_header = False + upd.responseheaders(f) + assert ( + f.response.headers["alt-svc"] == 'h3=":1234"; ma=3600, h2=":1234"; ma=3600' + ) diff --git a/web/src/js/ducks/_options_gen.ts b/web/src/js/ducks/_options_gen.ts index bf49a81ec..eae4fc42f 100644 --- a/web/src/js/ducks/_options_gen.ts +++ b/web/src/js/ducks/_options_gen.ts @@ -33,6 +33,7 @@ export interface OptionsState { ignore_hosts: string[]; intercept: string | undefined; intercept_active: boolean; + keep_alt_svc_header: boolean; keep_host_header: boolean; key_size: number; listen_host: string; @@ -134,6 +135,7 @@ export const defaultState: OptionsState = { ignore_hosts: [], intercept: undefined, intercept_active: false, + keep_alt_svc_header: false, keep_host_header: false, key_size: 2048, listen_host: "",