mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 18:18:25 +00:00
[sans-io] enable proxying by host header
This mirrors the current examples/complex/dns_spoofing.py script.
This commit is contained in:
parent
3f9441ac5f
commit
c00a78751e
@ -1,10 +1,11 @@
|
|||||||
import collections
|
import collections
|
||||||
|
import re
|
||||||
import typing
|
import typing
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from mitmproxy import exceptions, flow, http
|
from mitmproxy import flow, http
|
||||||
from mitmproxy.net import server_spec
|
from mitmproxy.net import server_spec
|
||||||
from mitmproxy.proxy.protocol.http import HTTPMode, validate_request_form
|
from mitmproxy.proxy.protocol.http import HTTPMode
|
||||||
from mitmproxy.proxy2 import commands, events, layer, tunnel
|
from mitmproxy.proxy2 import commands, events, layer, tunnel
|
||||||
from mitmproxy.proxy2.context import Connection, Context, Server
|
from mitmproxy.proxy2.context import Connection, Context, Server
|
||||||
from mitmproxy.proxy2.layers import tls
|
from mitmproxy.proxy2.layers import tls
|
||||||
@ -19,6 +20,28 @@ from ._hooks import HttpConnectHook, HttpErrorHook, HttpRequestHeadersHook, Http
|
|||||||
from ._http1 import Http1Client, Http1Server
|
from ._http1 import Http1Client, Http1Server
|
||||||
from ._http2 import Http2Client
|
from ._http2 import Http2Client
|
||||||
|
|
||||||
|
# This regex extracts splits the host header into host and port.
|
||||||
|
# Handles the edge case of IPv6 addresses containing colons.
|
||||||
|
# https://bugzilla.mozilla.org/show_bug.cgi?id=45891
|
||||||
|
parse_host_header = re.compile(r"^(?P<host>[^:]+|\[.+\])(?::(?P<port>\d+))?$")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_request(mode, request) -> typing.Optional[str]:
|
||||||
|
if request.scheme not in ("http", None):
|
||||||
|
return f"Invalid request scheme: {request.scheme}"
|
||||||
|
if mode is HTTPMode.transparent:
|
||||||
|
if request.first_line_format == "authority":
|
||||||
|
return (
|
||||||
|
f"mitmproxy received an HTTP CONNECT request even though it is not running in regular/upstream mode. "
|
||||||
|
f"This usually indicates a misconfiguration, please see the mitmproxy mode documentation for details."
|
||||||
|
)
|
||||||
|
if request.first_line_format == "absolute":
|
||||||
|
return (
|
||||||
|
f"mitmproxy received an absolute-form HTTP request even though it is not running in regular/upstream mode. "
|
||||||
|
f"This usually indicates a misconfiguration, please see the mitmproxy mode documentation for details."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(unsafe_hash=True)
|
@dataclass(unsafe_hash=True)
|
||||||
class GetHttpConnection(HttpCommand):
|
class GetHttpConnection(HttpCommand):
|
||||||
@ -119,23 +142,40 @@ class HttpStream(layer.Layer):
|
|||||||
# self.send_response(http.expect_continue_response)
|
# self.send_response(http.expect_continue_response)
|
||||||
# request.headers.pop("expect")
|
# request.headers.pop("expect")
|
||||||
|
|
||||||
try:
|
if err := validate_request(self.mode, self.flow.request):
|
||||||
validate_request_form(self.mode, self.flow.request)
|
self.flow.response = http.HTTPResponse.make(502, str(err))
|
||||||
except exceptions.HttpException as e:
|
self.client_state = self.state_errored
|
||||||
self.flow.response = http.HTTPResponse.make(502, str(e))
|
|
||||||
return (yield from self.send_response())
|
return (yield from self.send_response())
|
||||||
|
|
||||||
|
if self.flow.request.first_line_format == "authority":
|
||||||
|
return (yield from self.handle_connect())
|
||||||
|
|
||||||
|
if self.mode is HTTPMode.regular and self.flow.request.first_line_format == "absolute":
|
||||||
# set first line format to relative in regular mode,
|
# set first line format to relative in regular mode,
|
||||||
# see https://github.com/mitmproxy/mitmproxy/issues/1759
|
# see https://github.com/mitmproxy/mitmproxy/issues/1759
|
||||||
if self.mode is HTTPMode.regular and self.flow.request.first_line_format == "absolute":
|
|
||||||
self.flow.request.first_line_format = "relative"
|
self.flow.request.first_line_format = "relative"
|
||||||
|
elif self.mode in (HTTPMode.regular, HTTPMode.upstream) and self.flow.request.first_line_format == "relative":
|
||||||
if self.mode is HTTPMode.upstream:
|
# We need to extract destination information from the host header.
|
||||||
self.context.server.via = server_spec.parse_with_mode(self.context.options.mode)[1]
|
if m := parse_host_header.match(self.flow.request.host_header or ""):
|
||||||
|
host = m.group("host").strip("[]")
|
||||||
# Determine .scheme, .host and .port attributes for relative-form requests
|
if m.group("port"):
|
||||||
if self.mode is HTTPMode.transparent:
|
port = int(m.group("port"))
|
||||||
# Setting request.host also updates the host header, which we want to preserve
|
else:
|
||||||
|
port = 443 if self.context.client.tls else 80
|
||||||
|
host_header = self.flow.request.host_header
|
||||||
|
self.flow.request.host = host
|
||||||
|
self.flow.request.port = port
|
||||||
|
self.flow.request.host_header = host_header # set again as .host overwrites this.
|
||||||
|
self.flow.request.scheme = "https" if self.context.client.tls else "http"
|
||||||
|
else:
|
||||||
|
self.flow.response = http.HTTPResponse.make(
|
||||||
|
400,
|
||||||
|
"HTTP request has no host header, destination unknown."
|
||||||
|
)
|
||||||
|
self.client_state = self.state_errored
|
||||||
|
return (yield from self.send_response())
|
||||||
|
elif self.mode is HTTPMode.transparent:
|
||||||
|
# Determine .scheme, .host and .port attributes for transparent requests
|
||||||
host_header = self.flow.request.host_header
|
host_header = self.flow.request.host_header
|
||||||
self.flow.request.host = self.context.server.address[0]
|
self.flow.request.host = self.context.server.address[0]
|
||||||
self.flow.request.port = self.context.server.address[1]
|
self.flow.request.port = self.context.server.address[1]
|
||||||
@ -146,9 +186,6 @@ class HttpStream(layer.Layer):
|
|||||||
if self.context.options.mode.startswith("reverse:") and not self.context.options.keep_host_header:
|
if self.context.options.mode.startswith("reverse:") and not self.context.options.keep_host_header:
|
||||||
self.flow.request.host_header = self.context.server.address[0]
|
self.flow.request.host_header = self.context.server.address[0]
|
||||||
|
|
||||||
if self.flow.request.first_line_format == "authority":
|
|
||||||
return (yield from self.handle_connect())
|
|
||||||
|
|
||||||
yield HttpRequestHeadersHook(self.flow)
|
yield HttpRequestHeadersHook(self.flow)
|
||||||
|
|
||||||
if self.flow.request.stream:
|
if self.flow.request.stream:
|
||||||
@ -230,13 +267,13 @@ class HttpStream(layer.Layer):
|
|||||||
self.flow.response.data.content = self.response_body_buf
|
self.flow.response.data.content = self.response_body_buf
|
||||||
self.response_body_buf = b""
|
self.response_body_buf = b""
|
||||||
yield from self.send_response()
|
yield from self.send_response()
|
||||||
|
self.server_state = self.state_done
|
||||||
|
|
||||||
def send_response(self):
|
def send_response(self):
|
||||||
yield HttpResponseHook(self.flow)
|
yield HttpResponseHook(self.flow)
|
||||||
yield SendHttp(ResponseHeaders(self.stream_id, self.flow.response), self.context.client)
|
yield SendHttp(ResponseHeaders(self.stream_id, self.flow.response), self.context.client)
|
||||||
yield SendHttp(ResponseData(self.stream_id, self.flow.response.data.content), self.context.client)
|
yield SendHttp(ResponseData(self.stream_id, self.flow.response.data.content), self.context.client)
|
||||||
yield SendHttp(ResponseEndOfMessage(self.stream_id), self.context.client)
|
yield SendHttp(ResponseEndOfMessage(self.stream_id), self.context.client)
|
||||||
self.server_state = self.state_done
|
|
||||||
|
|
||||||
def handle_protocol_error(
|
def handle_protocol_error(
|
||||||
self,
|
self,
|
||||||
@ -313,6 +350,7 @@ class HttpStream(layer.Layer):
|
|||||||
http_proxy.child_layer = layer.NextLayer(self.context)
|
http_proxy.child_layer = layer.NextLayer(self.context)
|
||||||
yield from http_proxy.child_layer.handle_event(events.Start())
|
yield from http_proxy.child_layer.handle_event(events.Start())
|
||||||
self._handle_event = self.passthrough
|
self._handle_event = self.passthrough
|
||||||
|
|
||||||
self._handle_event = _wait_for_reply
|
self._handle_event = _wait_for_reply
|
||||||
else:
|
else:
|
||||||
self.child_layer = http_proxy
|
self.child_layer = http_proxy
|
||||||
@ -395,7 +433,8 @@ class HttpLayer(layer.Layer):
|
|||||||
|
|
||||||
def _handle_event(self, event: events.Event):
|
def _handle_event(self, event: events.Event):
|
||||||
if isinstance(event, events.Start):
|
if isinstance(event, events.Start):
|
||||||
return
|
if self.mode is HTTPMode.upstream:
|
||||||
|
self.context.server.via = server_spec.parse_with_mode(self.context.options.mode)[1]
|
||||||
elif isinstance(event, events.CommandReply):
|
elif isinstance(event, events.CommandReply):
|
||||||
stream = self.stream_by_command.pop(event.command)
|
stream = self.stream_by_command.pop(event.command)
|
||||||
self.event_to_child(stream, event)
|
self.event_to_child(stream, event)
|
||||||
|
@ -253,7 +253,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
opts = moptions.Options()
|
opts = moptions.Options()
|
||||||
opts.add_option(
|
opts.add_option(
|
||||||
"connection_strategy", str, "eager",
|
"connection_strategy", str, "lazy",
|
||||||
"Determine when server connections should be established.",
|
"Determine when server connections should be established.",
|
||||||
choices=("eager", "lazy")
|
choices=("eager", "lazy")
|
||||||
)
|
)
|
||||||
|
@ -5,8 +5,8 @@ from mitmproxy.proxy.protocol.http import HTTPMode
|
|||||||
from mitmproxy.proxy2 import layer
|
from mitmproxy.proxy2 import layer
|
||||||
from mitmproxy.proxy2.commands import CloseConnection, OpenConnection, SendData
|
from mitmproxy.proxy2.commands import CloseConnection, OpenConnection, SendData
|
||||||
from mitmproxy.proxy2.events import ConnectionClosed, DataReceived
|
from mitmproxy.proxy2.events import ConnectionClosed, DataReceived
|
||||||
from mitmproxy.proxy2.layers import http, tls, TCPLayer
|
from mitmproxy.proxy2.layers import TCPLayer, http, tls
|
||||||
from test.mitmproxy.proxy2.tutils import Placeholder, Playbook, reply, reply_next_layer, EchoLayer
|
from test.mitmproxy.proxy2.tutils import Placeholder, Playbook, reply, reply_next_layer
|
||||||
|
|
||||||
|
|
||||||
def test_http_proxy(tctx):
|
def test_http_proxy(tctx):
|
||||||
@ -510,11 +510,10 @@ def test_proxy_chain(tctx, strategy):
|
|||||||
playbook >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent))
|
playbook >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent))
|
||||||
playbook << SendData(tctx.client,
|
playbook << SendData(tctx.client,
|
||||||
b"HTTP/1.1 502 Bad Gateway\r\n"
|
b"HTTP/1.1 502 Bad Gateway\r\n"
|
||||||
b"content-length: 189\r\n"
|
b"content-length: 198\r\n"
|
||||||
b"\r\n"
|
b"\r\n"
|
||||||
b"Mitmproxy received an HTTP CONNECT request even though it is not running\n"
|
b"mitmproxy received an HTTP CONNECT request even though it is not running in regular/upstream mode. "
|
||||||
b"in regular mode. This usually indicates a misconfiguration,\n"
|
b"This usually indicates a misconfiguration, please see the mitmproxy mode documentation for details.")
|
||||||
b"please see the mitmproxy mode documentation for details.")
|
|
||||||
|
|
||||||
assert playbook
|
assert playbook
|
||||||
|
|
||||||
@ -535,9 +534,8 @@ def test_no_headers(tctx):
|
|||||||
assert server().address == ("example.com", 80)
|
assert server().address == ("example.com", 80)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail
|
|
||||||
def test_http_proxy_relative_request(tctx):
|
def test_http_proxy_relative_request(tctx):
|
||||||
"""Test handling of a relative-form "GET /"."""
|
"""Test handling of a relative-form "GET /" in regular proxy mode."""
|
||||||
server = Placeholder()
|
server = Placeholder()
|
||||||
assert (
|
assert (
|
||||||
Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False)
|
Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False)
|
||||||
@ -549,3 +547,15 @@ def test_http_proxy_relative_request(tctx):
|
|||||||
<< SendData(tctx.client, b"HTTP/1.1 204 No Content\r\n\r\n")
|
<< SendData(tctx.client, b"HTTP/1.1 204 No Content\r\n\r\n")
|
||||||
)
|
)
|
||||||
assert server().address == ("example.com", 80)
|
assert server().address == ("example.com", 80)
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_proxy_relative_request_no_host_header(tctx):
|
||||||
|
"""Test handling of a relative-form "GET /" in regular proxy mode, but without a host header."""
|
||||||
|
assert (
|
||||||
|
Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False)
|
||||||
|
>> DataReceived(tctx.client, b"GET / HTTP/1.1\r\n\r\n")
|
||||||
|
<< SendData(tctx.client, b"HTTP/1.1 400 Bad Request\r\n"
|
||||||
|
b"content-length: 53\r\n"
|
||||||
|
b"\r\n"
|
||||||
|
b"HTTP request has no host header, destination unknown.")
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user