Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
 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""))
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
 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}")
Ejemplo n.º 9
0
    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}")
Ejemplo n.º 10
0
    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())
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
    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}")
Ejemplo n.º 13
0
    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
Ejemplo n.º 14
0
    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}")
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
    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
Ejemplo n.º 17
0
 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}")
Ejemplo n.º 18
0
 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}")
Ejemplo n.º 19
0
    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
        )
Ejemplo n.º 20
0
 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
Ejemplo n.º 21
0
    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)
Ejemplo n.º 22
0
    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))
Ejemplo n.º 23
0
    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()
Ejemplo n.º 24
0
    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)
Ejemplo n.º 25
0
    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))
Ejemplo n.º 26
0
    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
Ejemplo n.º 27
0
 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))
Ejemplo n.º 28
0
    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
Ejemplo n.º 29
0
    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))
Ejemplo n.º 30
0
    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}")