129 lines
3.6 KiB
Python
129 lines
3.6 KiB
Python
import json
|
|
from dataclasses import dataclass
|
|
|
|
from mitmproxy import ctx
|
|
from mitmproxy.addonmanager import Loader
|
|
from mitmproxy.http import HTTPFlow
|
|
|
|
"""
|
|
This extension implements support for domain fronting.
|
|
|
|
Usage:
|
|
|
|
mitmproxy -s examples/contrib/domain_fronting.py --set domainfrontingfile=./domain_fronting.json
|
|
|
|
In the following basic example, www.example.com will be used for DNS requests and SNI values
|
|
but the secret.example.com value will be used for the HTTP host header:
|
|
|
|
{
|
|
"mappings": [
|
|
{
|
|
"patterns": ["secret.example.com"],
|
|
"server": "www.example.com"
|
|
}
|
|
]
|
|
}
|
|
|
|
The following example demonstrates the usage of a wildcard (at the beginning of the domain name only):
|
|
|
|
{
|
|
"mappings": [
|
|
{
|
|
"patterns": ["*.foo.example.com"],
|
|
"server": "www.example.com"
|
|
}
|
|
]
|
|
}
|
|
|
|
In the following example, we override the HTTP host header:
|
|
|
|
{
|
|
"mappings": [
|
|
{
|
|
"patterns": ["foo.example"],
|
|
"server": "www.example.com",
|
|
"host": "foo.proxy.example.com"
|
|
}
|
|
]
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
@dataclass
|
|
class Mapping:
|
|
server: str | None
|
|
host: str | None
|
|
|
|
|
|
class HttpsDomainFronting:
|
|
# configurations for regular ("foo.example.com") mappings:
|
|
star_mappings: dict[str, Mapping]
|
|
|
|
# Configurations for star ("*.example.com") mappings:
|
|
strict_mappings: dict[str, Mapping]
|
|
|
|
def __init__(self) -> None:
|
|
self.strict_mappings = {}
|
|
self.star_mappings = {}
|
|
|
|
def _resolve_addresses(self, host: str) -> Mapping | None:
|
|
mapping = self.strict_mappings.get(host)
|
|
if mapping is not None:
|
|
return mapping
|
|
|
|
index = 0
|
|
while True:
|
|
index = host.find(".", index)
|
|
if index == -1:
|
|
break
|
|
super_domain = host[(index + 1) :]
|
|
mapping = self.star_mappings.get(super_domain)
|
|
if mapping is not None:
|
|
return mapping
|
|
index += 1
|
|
|
|
return None
|
|
|
|
def load(self, loader: Loader) -> None:
|
|
loader.add_option(
|
|
name="domainfrontingfile",
|
|
typespec=str,
|
|
default="./fronting.json",
|
|
help="Domain fronting configuration file",
|
|
)
|
|
|
|
def _load_configuration_file(self, filename: str) -> None:
|
|
config = json.load(open(filename))
|
|
strict_mappings: dict[str, Mapping] = {}
|
|
star_mappings: dict[str, Mapping] = {}
|
|
for mapping in config["mappings"]:
|
|
item = Mapping(server=mapping.get("server"), host=mapping.get("host"))
|
|
for pattern in mapping["patterns"]:
|
|
if pattern.startswith("*."):
|
|
star_mappings[pattern[2:]] = item
|
|
else:
|
|
strict_mappings[pattern] = item
|
|
self.strict_mappings = strict_mappings
|
|
self.star_mappings = star_mappings
|
|
|
|
def configure(self, updated: set[str]) -> None:
|
|
if "domainfrontingfile" in updated:
|
|
domain_fronting_file = ctx.options.domainfrontingfile
|
|
self._load_configuration_file(domain_fronting_file)
|
|
|
|
def request(self, flow: HTTPFlow) -> None:
|
|
if not flow.request.scheme == "https":
|
|
return
|
|
# We use the host header to dispatch the request:
|
|
target = flow.request.host_header
|
|
if target is None:
|
|
return
|
|
mapping = self._resolve_addresses(target)
|
|
if mapping is not None:
|
|
flow.request.host = mapping.server or target
|
|
flow.request.headers["host"] = mapping.host or target
|
|
|
|
|
|
addons = [HttpsDomainFronting()]
|