def test_mitmproxy_ca_is_untrusted(self, tctx: context.Context): """Test the scenario where the client doesn't trust the mitmproxy CA.""" playbook, client_layer, tssl_client = make_client_tls_layer( tctx, sni=b"wrong.host.mitmproxy.org") playbook.logs = True data = tutils.Placeholder(bytes) assert ( playbook >> events.DataReceived( tctx.client, tssl_client.bio_read()) << tls.TlsClienthelloHook( tutils.Placeholder()) >> tutils.reply() << tls.TlsStartClientHook(tutils.Placeholder()) >> reply_tls_start_client() << commands.SendData(tctx.client, data)) tssl_client.bio_write(data()) with pytest.raises(ssl.SSLCertVerificationError): tssl_client.do_handshake() # Finish Handshake assert (playbook >> events.DataReceived( tctx.client, tssl_client.bio_read() ) << commands.Log( "Client TLS handshake failed. The client does not trust the proxy's certificate " "for wrong.host.mitmproxy.org (sslv3 alert bad certificate)", "warn") << commands.CloseConnection(tctx.client) >> events.ConnectionClosed(tctx.client)) assert not tctx.client.tls_established
def check_killed(self, emit_error_hook: bool) -> layer.CommandGenerator[bool]: killed_by_us = (self.flow.error and self.flow.error.msg == flow.Error.KILLED_MESSAGE) # The client may have closed the connection while we were waiting for the hook to complete. # We peek into the event queue to see if that is the case. killed_by_remote = None for evt in self._paused_event_queue: if isinstance(evt, RequestProtocolError): killed_by_remote = evt.message break if killed_by_remote: if not self.flow.error: self.flow.error = flow.Error(killed_by_remote) if killed_by_us or killed_by_remote: if emit_error_hook: yield HttpErrorHook(self.flow) # For HTTP/2 we only want to kill the specific stream, for HTTP/1 we want to kill the connection # *without* sending an HTTP response (that could be achieved by the user by setting flow.response). if self.context.client.alpn == b"h2": yield SendHttp(ResponseProtocolError(self.stream_id, "killed"), self.context.client) else: if self.context.client.state & ConnectionState.CAN_WRITE: yield commands.CloseConnection(self.context.client) self._handle_event = self.state_errored return True return False
def test_unsupported_protocol(self, tctx: context.Context): """Test the scenario where the server only supports an outdated TLS version by default.""" playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.address = ("example.mitmproxy.org", 443) tctx.server.state = ConnectionState.OPEN tctx.server.sni = "example.mitmproxy.org" # noinspection PyTypeChecker tssl = SSLTest(server_side=True, max_ver=ssl.TLSVersion.TLSv1_2) # send ClientHello data = tutils.Placeholder(bytes) assert ( playbook << tls.TlsStartServerHook(tutils.Placeholder()) >> reply_tls_start_server() << commands.SendData(tctx.server, data)) # receive ServerHello tssl.bio_write(data()) with pytest.raises(ssl.SSLError): tssl.do_handshake() # send back error tls_hook_data = tutils.Placeholder(TlsData) assert (playbook >> events.DataReceived(tctx.server, tssl.bio_read( )) << commands.Log( "Server TLS handshake failed. The remote server and mitmproxy cannot agree on a TLS version" " to use. You may need to adjust mitmproxy's tls_version_server_min option.", "warn") << tls.TlsFailedServerHook(tls_hook_data) >> tutils.reply() << commands.CloseConnection(tctx.server)) assert tls_hook_data().conn.error
def read_body(self, event: events.Event) -> layer.CommandGenerator[None]: assert self.stream_id while True: try: if isinstance(event, events.DataReceived): h11_event = self.body_reader(self.buf) elif isinstance(event, events.ConnectionClosed): h11_event = self.body_reader.read_eof() else: raise AssertionError(f"Unexpected event: {event}") except h11.ProtocolError as e: yield commands.CloseConnection(self.conn) yield ReceiveHttp(self.ReceiveProtocolError(self.stream_id, f"HTTP/1 protocol error: {e}")) return if h11_event is None: return elif isinstance(h11_event, h11.Data): data: bytes = bytes(h11_event.data) if data: yield ReceiveHttp(self.ReceiveData(self.stream_id, data)) elif isinstance(h11_event, h11.EndOfMessage): assert self.request if h11_event.headers: raise NotImplementedError(f"HTTP trailers are not implemented yet.") if self.request.data.method.upper() != b"CONNECT": yield ReceiveHttp(self.ReceiveEndOfMessage(self.stream_id)) is_request = isinstance(self, Http1Server) yield from self.mark_done( request=is_request, response=not is_request ) return
def mark_done(self, *, request: bool = False, response: bool = False) -> layer.CommandGenerator[None]: if request: self.request_done = True if response: self.response_done = True if self.request_done and self.response_done: assert self.request assert self.response if should_make_pipe(self.request, self.response): yield from self.make_pipe() return connection_done = ( http1_sansio.expected_http_body_size(self.request, self.response) == -1 or http1.connection_close(self.request.http_version, self.request.headers) or http1.connection_close(self.response.http_version, self.response.headers) # If we proxy HTTP/2 to HTTP/1, we only use upstream connections for one request. # This simplifies our connection management quite a bit as we can rely on # the proxyserver's max-connection-per-server throttling. or (self.request.is_http2 and isinstance(self, Http1Client)) ) if connection_done: yield commands.CloseConnection(self.conn) self.state = self.done return self.request_done = self.response_done = False self.request = self.response = None if isinstance(self, Http1Server): self.stream_id += 2 else: self.stream_id = None self.state = self.read_headers if self.buf: yield from self.state(events.DataReceived(self.conn, b""))
def test_untrusted_cert(self, tctx): """If the certificate is not trusted, we should fail.""" playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.address = ("wrong.host.mitmproxy.org", 443) tctx.server.sni = "wrong.host.mitmproxy.org" tssl = SSLTest(server_side=True) # send ClientHello data = tutils.Placeholder(bytes) assert ( playbook >> events.DataReceived(tctx.client, b"open-connection") << layer.NextLayerHook(tutils.Placeholder()) >> tutils.reply_next_layer(TlsEchoLayer) << commands.OpenConnection( tctx.server) >> tutils.reply(None) << tls.TlsStartServerHook( tutils.Placeholder()) >> reply_tls_start_server() << commands.SendData(tctx.server, data)) # receive ServerHello, finish client handshake tssl.bio_write(data()) with pytest.raises(ssl.SSLWantReadError): tssl.do_handshake() assert (playbook >> events.DataReceived(tctx.server, tssl.bio_read( )) << commands.Log( "Server TLS handshake failed. Certificate verify failed: Hostname mismatch", "warn" ) << commands.CloseConnection(tctx.server) << commands.SendData( tctx.client, b"open-connection failed: Certificate verify failed: Hostname mismatch" )) assert not tctx.server.tls_established
def send_response(self, already_streamed: bool = False): """We have either consumed the entire response from the server or the response was set by an addon.""" assert self.flow.response self.flow.response.timestamp_end = time.time() is_websocket = (self.flow.response.status_code == 101 and self.flow.response.headers.get( "upgrade", "").lower() == "websocket" and self.flow.request.headers.get( "Sec-WebSocket-Version", "").encode() == wsproto.handshake.WEBSOCKET_VERSION and self.context.options.websocket) if is_websocket: # We need to set this before calling the response hook # so that addons can determine if a WebSocket connection is following up. self.flow.websocket = WebSocketData() yield HttpResponseHook(self.flow) self.server_state = self.state_done if (yield from self.check_killed(False)): return if not already_streamed: content = self.flow.response.raw_content done_after_headers = not (content or self.flow.response.trailers) yield SendHttp( ResponseHeaders(self.stream_id, self.flow.response, done_after_headers), self.context.client) if content: yield SendHttp(ResponseData(self.stream_id, content), self.context.client) if self.flow.response.trailers: yield SendHttp( ResponseTrailers(self.stream_id, self.flow.response.trailers), self.context.client) yield SendHttp(ResponseEndOfMessage(self.stream_id), self.context.client) if self.flow.response.status_code == 101: if is_websocket: self.child_layer = websocket.WebsocketLayer( self.context, self.flow) elif self.context.options.rawtcp: self.child_layer = tcp.TCPLayer(self.context) else: yield commands.Log( f"Sent HTTP 101 response, but no protocol is enabled to upgrade to.", "warn") yield commands.CloseConnection(self.context.client) self.client_state = self.server_state = self.state_errored return if self.debug: yield commands.Log( f"{self.debug}[http] upgrading to {self.child_layer}", "debug") yield from self.child_layer.handle_event(events.Start()) self._handle_event = self.passthrough return
def read_headers( self, event: events.ConnectionEvent) -> layer.CommandGenerator[None]: if isinstance(event, events.DataReceived): request_head = self.buf.maybe_extract_lines() if request_head: request_head = [ bytes(x) for x in request_head ] # TODO: Make url.parse compatible with bytearrays try: self.request = http1.read_request_head(request_head) expected_body_size = http1.expected_http_body_size( self.request) except ValueError as e: yield commands.SendData(self.conn, make_error_response(400, str(e))) yield commands.CloseConnection(self.conn) if self.request: # we have headers that we can show in the ui yield ReceiveHttp( RequestHeaders(self.stream_id, self.request, False)) yield ReceiveHttp( RequestProtocolError(self.stream_id, str(e), 400)) else: yield commands.Log( f"{human.format_address(self.conn.peername)}: {e}") self.state = self.done return yield ReceiveHttp( RequestHeaders(self.stream_id, self.request, expected_body_size == 0)) self.body_reader = make_body_reader(expected_body_size) self.state = self.read_body yield from self.state(event) else: pass # FIXME: protect against header size DoS elif isinstance(event, events.ConnectionClosed): buf = bytes(self.buf) if buf.strip(): yield commands.Log( f"Client closed connection before completing request headers: {buf!r}" ) yield commands.CloseConnection(self.conn) else: raise AssertionError(f"Unexpected event: {event}")
def relay_messages(self, event: events.Event) -> layer.CommandGenerator[None]: if isinstance(event, TcpMessageInjected): # we just spoof that we received data here and then process that regularly. event = events.DataReceived( self.context.client if event.message.from_client else self.context.server, event.message.content, ) assert isinstance(event, events.ConnectionEvent) from_client = event.connection == self.context.client send_to: Connection if from_client: send_to = self.context.server else: send_to = self.context.client if isinstance(event, events.DataReceived): if self.flow: tcp_message = tcp.TCPMessage(from_client, event.data) self.flow.messages.append(tcp_message) yield TcpMessageHook(self.flow) yield commands.SendData(send_to, tcp_message.content) else: yield commands.SendData(send_to, event.data) elif isinstance(event, events.ConnectionClosed): all_done = not ( (self.context.client.state & ConnectionState.CAN_READ) or (self.context.server.state & ConnectionState.CAN_READ)) if all_done: if self.context.server.state is not ConnectionState.CLOSED: yield commands.CloseConnection(self.context.server) if self.context.client.state is not ConnectionState.CLOSED: yield commands.CloseConnection(self.context.client) self._handle_event = self.done if self.flow: yield TcpEndHook(self.flow) self.flow.live = False else: yield commands.CloseConnection(send_to, half_close=True) else: raise AssertionError(f"Unexpected event: {event}")
def finish_start(self): if self.context.options.connection_strategy == "eager": err = yield commands.OpenConnection(self.context.server) if err: yield commands.CloseConnection(self.context.client) self._handle_event = self.done return self._handle_event = self.child_layer.handle_event yield from self.child_layer.handle_event(events.Start())
def start_tls(self) -> layer.CommandGenerator[None]: assert not self.tls tls_start = TlsStartData(self.conn, self.context) yield TlsStartHook(tls_start) if not tls_start.ssl_conn: yield commands.Log("No TLS context was provided, failing connection.", "error") yield commands.CloseConnection(self.conn) assert tls_start.ssl_conn self.tls = tls_start.ssl_conn
def send(self, event: HttpEvent) -> layer.CommandGenerator[None]: if isinstance(event, RequestProtocolError): yield commands.CloseConnection(self.conn) return if not self.stream_id: assert isinstance(event, RequestHeaders) self.stream_id = event.stream_id self.request = event.request assert self.stream_id == event.stream_id if isinstance(event, RequestHeaders): request = event.request if request.is_http2: # Convert to an HTTP/1 request. request = request.copy( ) # (we could probably be a bit more efficient here.) request.http_version = "HTTP/1.1" if "Host" not in request.headers and request.authority: request.headers.insert(0, "Host", request.authority) request.authority = "" raw = http1.assemble_request_head(request) yield commands.SendData(self.conn, raw) elif isinstance(event, RequestData): assert self.request if "chunked" in self.request.headers.get("transfer-encoding", "").lower(): raw = b"%x\r\n%s\r\n" % (len(event.data), event.data) else: raw = event.data if raw: yield commands.SendData(self.conn, raw) elif isinstance(event, RequestEndOfMessage): assert self.request if "chunked" in self.request.headers.get("transfer-encoding", "").lower(): yield commands.SendData(self.conn, b"0\r\n\r\n") elif http1.expected_http_body_size(self.request, self.response) == -1: yield commands.CloseConnection(self.conn, half_close=True) yield from self.mark_done(request=True) else: raise AssertionError(f"Unexpected event: {event}")
def state_connect(self): # Parse Connect Request if len(self.buf) < 5: return if self.buf[:3] != b"\x05\x01\x00": yield from self.socks_err(f"Unsupported SOCKS5 request: {self.buf!r}", SOCKS5_REP_COMMAND_NOT_SUPPORTED) return # Determine message length atyp = self.buf[3] message_len: int if atyp == SOCKS5_ATYP_IPV4_ADDRESS: message_len = 4 + 4 + 2 elif atyp == SOCKS5_ATYP_IPV6_ADDRESS: message_len = 4 + 16 + 2 elif atyp == SOCKS5_ATYP_DOMAINNAME: message_len = 4 + 1 + self.buf[4] + 2 else: yield from self.socks_err(f"Unknown address type: {atyp}", SOCKS5_REP_ADDRESS_TYPE_NOT_SUPPORTED) return # Do we have enough bytes yet? if len(self.buf) < message_len: return # Parse host and port msg, self.buf = self.buf[:message_len], self.buf[message_len:] host: str if atyp == SOCKS5_ATYP_IPV4_ADDRESS: host = socket.inet_ntop(socket.AF_INET, msg[4:-2]) elif atyp == SOCKS5_ATYP_IPV6_ADDRESS: host = socket.inet_ntop(socket.AF_INET6, msg[4:-2]) else: host_bytes = msg[5:-2] host = host_bytes.decode("ascii", "replace") port, = struct.unpack("!H", msg[-2:]) # We now have all we need, let's get going. self.context.server.address = (host, port) self.child_layer = layer.NextLayer(self.context) # this already triggers the child layer's Start event, # but that's not a problem in practice... err = yield from self.finish_start() if err: yield commands.SendData(self.context.client, b"\x05\x04\x00\x01\x00\x00\x00\x00\x00\x00") yield commands.CloseConnection(self.context.client) else: yield commands.SendData(self.context.client, b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00") if self.buf: yield from self.child_layer.handle_event(events.DataReceived(self.context.client, self.buf)) del self.buf
def read_headers(self, event: events.ConnectionEvent) -> layer.CommandGenerator[None]: if isinstance(event, events.DataReceived): if not self.request: # we just received some data for an unknown request. yield commands.Log(f"Unexpected data from server: {bytes(self.buf)!r}") yield commands.CloseConnection(self.conn) return assert self.stream_id response_head = self.buf.maybe_extract_lines() if response_head: response_head = [bytes(x) for x in response_head] # TODO: Make url.parse compatible with bytearrays try: self.response = http1.read_response_head(response_head) expected_size = http1.expected_http_body_size(self.request, self.response) except ValueError as e: yield commands.CloseConnection(self.conn) yield ReceiveHttp(ResponseProtocolError(self.stream_id, f"Cannot parse HTTP response: {e}")) return yield ReceiveHttp(ResponseHeaders(self.stream_id, self.response, expected_size == 0)) self.body_reader = make_body_reader(expected_size) self.state = self.read_body yield from self.state(event) else: pass # FIXME: protect against header size DoS elif isinstance(event, events.ConnectionClosed): if self.conn.state & ConnectionState.CAN_WRITE: yield commands.CloseConnection(self.conn) if self.stream_id: if self.buf: yield ReceiveHttp(ResponseProtocolError(self.stream_id, f"unexpected server response: {bytes(self.buf)!r}")) else: # The server has closed the connection to prevent us from continuing. # We need to signal that to the stream. # https://tools.ietf.org/html/rfc7231#section-6.5.11 yield ReceiveHttp(ResponseProtocolError(self.stream_id, "server closed connection")) else: return else: raise AssertionError(f"Unexpected event: {event}")
def test_immediate_disconnect(self, tctx: context.Context, close_at): """Test the scenario where the client is disconnecting during the handshake. This may happen because they are not interested in the connection anymore, or because they do not like the proxy certificate.""" playbook, client_layer, tssl_client = make_client_tls_layer( tctx, sni=b"wrong.host.mitmproxy.org") playbook.logs = True tls_hook_data = tutils.Placeholder(TlsData) playbook >> events.DataReceived(tctx.client, tssl_client.bio_read()) playbook << tls.TlsClienthelloHook(tutils.Placeholder()) if close_at == "tls_clienthello": assert (playbook >> events.ConnectionClosed( tctx.client) >> tutils.reply(to=-2) << tls.TlsStartClientHook( tutils.Placeholder()) >> reply_tls_start_client() << tls.TlsFailedClientHook(tls_hook_data) >> tutils.reply() << commands.CloseConnection(tctx.client)) assert tls_hook_data().conn.error return playbook >> tutils.reply() playbook << tls.TlsStartClientHook(tutils.Placeholder()) if close_at == "tls_start_client": assert (playbook >> events.ConnectionClosed( tctx.client) >> reply_tls_start_client(to=-2) << tls.TlsFailedClientHook(tls_hook_data) >> tutils.reply() << commands.CloseConnection(tctx.client)) assert tls_hook_data().conn.error return assert (playbook >> reply_tls_start_client() << commands.SendData( tctx.client, tutils.Placeholder() ) >> events.ConnectionClosed(tctx.client) << commands.Log( "Client TLS handshake failed. The client disconnected during the handshake. " "If this happens consistently for wrong.host.mitmproxy.org, this may indicate that the " "client does not trust the proxy's certificate.", "info") << tls.TlsFailedClientHook(tls_hook_data) >> tutils.reply() << commands.CloseConnection(tctx.client)) assert tls_hook_data().conn.error
def test_cannot_parse_clienthello(self, tctx: context.Context): """Test the scenario where we cannot parse the ClientHello""" playbook, client_layer, tssl_client = make_client_tls_layer(tctx) invalid = b"\x16\x03\x01\x00\x00" assert (playbook >> events.DataReceived( tctx.client, invalid ) << commands.Log( f"Client TLS handshake failed. Cannot parse ClientHello: {invalid.hex()}", level="warn") << commands.CloseConnection(tctx.client)) assert not tctx.client.tls_established
def _handle_event(self, event: events.Event): if isinstance(event, events.Start): yield from self.event_to_child( self.connections[self.context.client], event) if self.mode is HTTPMode.upstream: self.context.server.via = server_spec.parse_with_mode( self.context.options.mode)[1] elif isinstance(event, events.CommandCompleted): stream = self.command_sources.pop(event.command) yield from self.event_to_child(stream, event) elif isinstance(event, events.MessageInjected): # For injected messages we pass the HTTP stacks entirely and directly address the stream. try: conn = self.connections[event.flow.server_conn] except KeyError: # We have a miss for the server connection, which means we're looking at a connection object # that is tunneled over another connection (for example: over an upstream HTTP proxy). # We now take the stream associated with the client connection. That won't work for HTTP/2, # but it's good enough for HTTP/1. conn = self.connections[event.flow.client_conn] if isinstance(conn, HttpStream): stream_id = conn.stream_id else: # We reach to the end of the connection's child stack to get the HTTP/1 client layer, # which tells us which stream we are dealing with. conn = conn.context.layers[-1] assert isinstance(conn, Http1Connection) assert conn.stream_id stream_id = conn.stream_id yield from self.event_to_child(self.streams[stream_id], event) elif isinstance(event, events.ConnectionEvent): if event.connection == self.context.server and self.context.server not in self.connections: # We didn't do anything with this connection yet, now the peer is doing something. if isinstance(event, events.ConnectionClosed): # The peer has closed it - let's close it too! yield commands.CloseConnection(event.connection) elif isinstance(event, events.DataReceived): # The peer has sent data. This can happen with HTTP/2 servers that already send a settings frame. child_layer: HttpConnection if self.context.server.alpn == b"h2": child_layer = Http2Client(self.context.fork()) else: child_layer = Http1Client(self.context.fork()) self.connections[self.context.server] = child_layer yield from self.event_to_child(child_layer, events.Start()) yield from self.event_to_child(child_layer, event) else: raise AssertionError(f"Unexpected event: {event}") else: handler = self.connections[event.connection] yield from self.event_to_child(handler, event) else: raise AssertionError(f"Unexpected event: {event}")
def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]: if isinstance(event, events.Start): pass elif isinstance(event, events.DataReceived): self.buf += event.data yield from self.state() elif isinstance(event, events.ConnectionClosed): if self.buf: yield commands.Log(f"Client closed connection before completing SOCKS5 handshake: {self.buf!r}") yield commands.CloseConnection(event.connection) else: raise AssertionError(f"Unknown event: {event}")
def test_simple(self, tctx): playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.state = ConnectionState.OPEN tctx.server.address = ("example.mitmproxy.org", 443) tctx.server.sni = b"example.mitmproxy.org" tssl = SSLTest(server_side=True) # send ClientHello data = tutils.Placeholder(bytes) assert ( playbook << tls.TlsStartHook(tutils.Placeholder()) >> reply_tls_start() << commands.SendData(tctx.server, data) ) # receive ServerHello, finish client handshake tssl.bio_write(data()) with pytest.raises(ssl.SSLWantReadError): tssl.do_handshake() interact(playbook, tctx.server, tssl) # finish server handshake tssl.do_handshake() assert ( playbook >> events.DataReceived(tctx.server, tssl.bio_read()) << None ) assert tctx.server.tls_established # Echo assert ( playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(tutils.Placeholder()) >> tutils.reply_next_layer(TlsEchoLayer) << commands.SendData(tctx.client, b"foo") ) _test_echo(playbook, tssl, tctx.server) with pytest.raises(ssl.SSLWantReadError): tssl.obj.unwrap() assert ( playbook >> events.DataReceived(tctx.server, tssl.bio_read()) << commands.CloseConnection(tctx.server) >> events.ConnectionClosed(tctx.server) << None )
def socks_err( self, message: str, reply_code: Optional[int] = None, ) -> layer.CommandGenerator[None]: if reply_code is not None: yield commands.SendData( self.context.client, bytes([SOCKS5_VERSION, reply_code]) + b"\x00\x01\x00\x00\x00\x00\x00\x00") yield commands.CloseConnection(self.context.client) yield commands.Log(message) self._handle_event = self.done
def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]: assert platform.original_addr is not None socket = yield commands.GetSocket(self.context.client) try: self.context.server.address = platform.original_addr(socket) except Exception as e: yield commands.Log(f"Transparent mode failure: {e!r}") self.child_layer = layer.NextLayer(self.context) err = yield from self.finish_start() if err: yield commands.CloseConnection(self.context.client)
def test_unsupported_protocol(self, tctx: context.Context): """Test the scenario where the client only supports an outdated TLS version by default.""" playbook, client_layer, tssl_client = make_client_tls_layer( tctx, max_ver=ssl.TLSVersion.TLSv1_2) playbook.logs = True assert (playbook >> events.DataReceived( tctx.client, tssl_client.bio_read() ) << tls.TlsClienthelloHook(tutils.Placeholder( )) >> tutils.reply() << tls.TlsStartClientHook(tutils.Placeholder( )) >> reply_tls_start_client() << commands.Log( "Client TLS handshake failed. Client and mitmproxy cannot agree on a TLS version to " "use. You may need to adjust mitmproxy's tls_version_client_min option.", "warn") << commands.CloseConnection(tctx.client))
def _handle_event(self, event: mevents.Event): self.events.append(event) # We receive new data. Let's find out if we can determine the next layer now? if self._ask_on_start and isinstance(event, events.Start): yield from self._ask() elif isinstance(event, mevents.ConnectionClosed ) and event.connection == self.context.client: # If we have not determined the next protocol yet and the client already closes the connection, # we abort everything. yield commands.CloseConnection(self.context.client) elif isinstance(event, mevents.DataReceived): # For now, we only ask if we have received new data to reduce hook noise. yield from self._ask()
def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]: spec = server_spec.parse_with_mode(self.context.options.mode)[1] self.context.server.address = spec.address if spec.scheme not in ("http", "tcp"): if not self.context.options.keep_host_header: self.context.server.sni = spec.address[0] self.child_layer = tls.ServerTLSLayer(self.context) else: self.child_layer = layer.NextLayer(self.context) err = yield from self.finish_start() if err: yield commands.CloseConnection(self.context.client)
def test_remote_speaks_no_tls(self, tctx): playbook = tutils.Playbook(tls.ServerTLSLayer(tctx)) tctx.server.state = ConnectionState.OPEN tctx.server.sni = "example.mitmproxy.org" # send ClientHello, receive random garbage back data = tutils.Placeholder(bytes) assert (playbook << tls.TlsStartHook(tutils.Placeholder( )) >> reply_tls_start( ) << commands.SendData(tctx.server, data) >> events.DataReceived( tctx.server, b"HTTP/1.1 404 Not Found\r\n" ) << commands.Log( "Server TLS handshake failed. The remote server does not speak TLS.", "warn") << commands.CloseConnection(tctx.server))
def start(self, _) -> layer.CommandGenerator[None]: if self.flow: yield TcpStartHook(self.flow) if not self.context.server.connected: err = yield commands.OpenConnection(self.context.server) if err: if self.flow: self.flow.error = flow.Error(str(err)) yield TcpErrorHook(self.flow) yield commands.CloseConnection(self.context.client) self._handle_event = self.done return self._handle_event = self.relay_messages
def test_receive_close(self, tctx: Context, layer_found: bool): """Test that we abort a client connection which has disconnected without any layer being found.""" nl = layer.NextLayer(tctx) playbook = tutils.Playbook(nl) assert ( playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl) >> events.ConnectionClosed(tctx.client)) if layer_found: nl.layer = tutils.RecordLayer(tctx) assert (playbook >> tutils.reply(to=-2)) assert isinstance(nl.layer.event_log[-1], events.ConnectionClosed) else: assert (playbook >> tutils.reply(to=-2) << commands.CloseConnection(tctx.client))
def start_tls(self) -> layer.CommandGenerator[None]: assert not self.tls tls_start = TlsData(self.conn, self.context) if self.conn == self.context.client: yield TlsStartClientHook(tls_start) else: yield TlsStartServerHook(tls_start) if not tls_start.ssl_conn: yield commands.Log( "No TLS context was provided, failing connection.", "error") yield commands.CloseConnection(self.conn) return assert tls_start.ssl_conn self.tls = tls_start.ssl_conn
def test_mitmproxy_ca_is_untrusted_immediate_disconnect( self, tctx: context.Context): """Test the scenario where the client doesn't trust the mitmproxy CA.""" playbook, client_layer, tssl_client = make_client_tls_layer( tctx, sni=b"wrong.host.mitmproxy.org") assert ( playbook >> events.DataReceived( tctx.client, tssl_client.bio_read()) << tls.TlsClienthelloHook( tutils.Placeholder()) >> tutils.reply() << tls.TlsStartHook(tutils.Placeholder()) >> reply_tls_start() << commands.SendData(tctx.client, tutils.Placeholder()) >> events.ConnectionClosed(tctx.client) << commands.Log( "Client TLS handshake failed. The client may not trust the proxy's certificate " "for wrong.host.mitmproxy.org (connection closed)", "warn") << commands.CloseConnection(tctx.client))
def send(self, event: HttpEvent) -> layer.CommandGenerator[None]: assert event.stream_id == self.stream_id if isinstance(event, ResponseHeaders): self.response = response = event.response if response.is_http2: response = response.copy() # Convert to an HTTP/1 response. response.http_version = "HTTP/1.1" # not everyone supports empty reason phrases, so we better make up one. response.reason = status_codes.RESPONSES.get( response.status_code, "") # Shall we set a Content-Length header here if there is none? # For now, let's try to modify as little as possible. raw = http1.assemble_response_head(response) yield commands.SendData(self.conn, raw) elif isinstance(event, ResponseData): assert self.response if "chunked" in self.response.headers.get("transfer-encoding", "").lower(): raw = b"%x\r\n%s\r\n" % (len(event.data), event.data) else: raw = event.data if raw: yield commands.SendData(self.conn, raw) elif isinstance(event, ResponseEndOfMessage): assert self.response if "chunked" in self.response.headers.get("transfer-encoding", "").lower(): yield commands.SendData(self.conn, b"0\r\n\r\n") yield from self.mark_done(response=True) elif isinstance(event, ResponseProtocolError): if not self.response and event.code != status_codes.NO_RESPONSE: resp = http.Response.make( event.code, format_error(event.code, event.message), http.Headers( Server=version.MITMPROXY, Connection="close", Content_Type="text/html", )) raw = http1.assemble_response(resp) yield commands.SendData(self.conn, raw) if self.conn.state & ConnectionState.CAN_WRITE: yield commands.CloseConnection(self.conn) else: raise AssertionError(f"Unexpected event: {event}")