def test_simple(self, tctx): nl = layer.NextLayer(tctx, ask_on_start=True) nl.debug = " " playbook = tutils.Playbook(nl, hooks=True) assert ( playbook << layer.NextLayerHook(nl) >> tutils.reply() >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl) >> tutils.reply() >> events.DataReceived(tctx.client, b"bar") << layer.NextLayerHook(nl) ) assert nl.data_client() == b"foobar" assert nl.data_server() == b"" nl.layer = tutils.EchoLayer(tctx) assert ( playbook >> tutils.reply() << commands.SendData(tctx.client, b"foo") << commands.SendData(tctx.client, b"bar") )
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.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() 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 test_disconnect_while_intercept(tctx): """Test a server disconnect while a request is intercepted.""" tctx.options.connection_strategy = "eager" server1 = Placeholder(Server) server2 = Placeholder(Server) flow = Placeholder(HTTPFlow) assert ( Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) >> DataReceived(tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n") << http.HttpConnectHook(Placeholder(HTTPFlow)) >> reply() << OpenConnection(server1) >> reply(None) << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) << http.HttpRequestHook(flow) >> ConnectionClosed(server1) << CloseConnection(server1) >> reply(to=-3) << OpenConnection(server2) >> reply(None) << SendData(server2, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(server2, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n") ) assert server1() != server2() assert flow().server_conn == server2()
def test_proxy_chain(tctx, strategy): server = Placeholder(Server) tctx.options.connection_strategy = strategy playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) playbook >> DataReceived(tctx.client, b"CONNECT proxy:8080 HTTP/1.1\r\n\r\n") if strategy == "eager": playbook << OpenConnection(server) playbook >> reply(None) playbook << SendData(tctx.client, b"HTTP/1.1 200 Connection established\r\n\r\n") playbook >> DataReceived(tctx.client, b"CONNECT second-proxy:8080 HTTP/1.1\r\n\r\n") playbook << layer.NextLayerHook(Placeholder()) playbook >> reply_next_layer( lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) playbook << SendData( tctx.client, b"HTTP/1.1 502 Bad Gateway\r\n" b"content-length: 198\r\n" b"\r\n" b"mitmproxy received an HTTP CONNECT request even though it is not running in regular/upstream mode. " b"This usually indicates a misconfiguration, please see the mitmproxy mode documentation for details." ) assert playbook
def test_https_proxy(strategy, tctx): """Test a CONNECT request, followed by a HTTP GET /""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) tctx.options.connection_strategy = strategy (playbook >> DataReceived(tctx.client, b"CONNECT example.proxy:80 HTTP/1.1\r\n\r\n") << http.HttpConnectHook(Placeholder()) >> reply()) if strategy == "eager": (playbook << OpenConnection(server) >> reply(None)) (playbook << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') >> DataReceived(tctx.client, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) << http.HttpRequestHeadersHook(flow) >> reply() << http.HttpRequestHook(flow) >> reply()) if strategy == "lazy": (playbook << OpenConnection(server) >> reply(None)) (playbook << SendData( server, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived( server, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!") << http.HttpResponseHeadersHook(flow) >> reply() << http.HttpResponseHook(flow) >> reply() << SendData( tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!")) assert playbook
def test_func_references(self, tctx: Context): nl = layer.NextLayer(tctx) playbook = tutils.Playbook(nl) assert (playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl)) nl.layer = tutils.EchoLayer(tctx) handle = nl.handle_event assert (playbook >> tutils.reply() << commands.SendData( tctx.client, b"foo")) sd, = handle(events.DataReceived(tctx.client, b"bar")) assert isinstance(sd, commands.SendData)
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 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 test_http_proxy_tcp(tctx, mode, close_first): """Test TCP over HTTP CONNECT.""" server = Placeholder(Server) if mode == "upstream": tctx.options.mode = "upstream:http://proxy:8080" toplayer = http.HttpLayer(tctx, HTTPMode.upstream) else: tctx.options.mode = "regular" toplayer = http.HttpLayer(tctx, HTTPMode.regular) playbook = Playbook(toplayer, hooks=False) assert ( playbook >> DataReceived(tctx.client, b"CONNECT example:443 HTTP/1.1\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 Connection established\r\n\r\n") >> DataReceived(tctx.client, b"this is not http") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: TCPLayer(ctx, ignore=True)) << OpenConnection(server) ) playbook >> reply(None) if mode == "upstream": playbook << SendData(server, b"CONNECT example:443 HTTP/1.1\r\n\r\n") playbook >> DataReceived(server, b"HTTP/1.1 200 Connection established\r\n\r\n") assert ( playbook << SendData(server, b"this is not http") >> DataReceived(server, b"true that") << SendData(tctx.client, b"true that") ) if mode == "regular": assert server().address == ("example", 443) else: assert server().address == ("proxy", 8080) if close_first == "client": a, b = tctx.client, server else: a, b = server, tctx.client assert ( playbook >> ConnectionClosed(a) << CloseConnection(b) >> ConnectionClosed(b) << CloseConnection(a) )
def test_redirect(strategy, https_server, https_client, tctx, monkeypatch): """Test redirects between http:// and https:// in regular proxy mode.""" server = Placeholder(Server) flow = Placeholder(HTTPFlow) tctx.options.connection_strategy = strategy p = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False) if https_server: monkeypatch.setattr(tls, "ServerTLSLayer", tls.MockTLSLayer) def redirect(flow: HTTPFlow): if https_server: flow.request.url = "https://redirected.site/" else: flow.request.url = "http://redirected.site/" if https_client: p >> DataReceived(tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n") if strategy == "eager": p << OpenConnection(Placeholder()) p >> reply(None) p << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') p >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") p << layer.NextLayerHook(Placeholder()) p >> reply_next_layer( lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) else: p >> DataReceived( tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") p << http.HttpRequestHook(flow) p >> reply(side_effect=redirect) p << OpenConnection(server) p >> reply(None) p << SendData(server, b"GET / HTTP/1.1\r\nHost: redirected.site\r\n\r\n") p >> DataReceived( server, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!") p << SendData( tctx.client, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!") assert p if https_server: assert server().address == ("redirected.site", 443) else: assert server().address == ("redirected.site", 80)
def test_http_proxy_tcp(tctx, mode, close_first): """Test TCP over HTTP CONNECT.""" server = Placeholder(Server) f = Placeholder(TCPFlow) tctx.options.connection_strategy = "lazy" if mode == "upstream": tctx.options.mode = "upstream:http://proxy:8080" toplayer = http.HttpLayer(tctx, HTTPMode.upstream) else: tctx.options.mode = "regular" toplayer = http.HttpLayer(tctx, HTTPMode.regular) playbook = Playbook(toplayer, hooks=False) assert (playbook >> DataReceived( tctx.client, b"CONNECT example:443 HTTP/1.1\r\n\r\n") << SendData( tctx.client, b"HTTP/1.1 200 Connection established\r\n\r\n") >> DataReceived(tctx.client, b"this is not http") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: TCPLayer(ctx, ignore=False)) << TcpStartHook(f) >> reply() << OpenConnection(server)) playbook >> reply(None) if mode == "upstream": playbook << SendData(server, b"CONNECT example:443 HTTP/1.1\r\n\r\n") playbook >> DataReceived( server, b"HTTP/1.1 200 Connection established\r\n\r\n") assert (playbook << SendData(server, b"this is not http") >> DataReceived( server, b"true that") << SendData(tctx.client, b"true that")) if mode == "regular": assert server().address == ("example", 443) else: assert server().address == ("proxy", 8080) assert (playbook >> TcpMessageInjected( f, TCPMessage(False, b"fake news from your friendly man-in-the-middle")) << SendData(tctx.client, b"fake news from your friendly man-in-the-middle")) if close_first == "client": a, b = tctx.client, server else: a, b = server, tctx.client assert (playbook >> ConnectionClosed(a) << CloseConnection(b) >> ConnectionClosed(b) << CloseConnection(a))
def test_late_hook_reply(self, tctx: Context): """ Properly handle case where we receive an additional event while we are waiting for a reply from the proxy core. """ nl = layer.NextLayer(tctx) playbook = tutils.Playbook(nl) assert (playbook >> events.DataReceived(tctx.client, b"foo") << layer.NextLayerHook(nl) >> events.DataReceived( tctx.client, b"bar")) assert nl.data_client() == b"foo" # "bar" is paused. nl.layer = tutils.EchoLayer(tctx) assert (playbook >> tutils.reply(to=-2) << commands.SendData( tctx.client, b"foo") << commands.SendData(tctx.client, b"bar"))
def test_kill_flow(tctx, when): """Test that we properly kill flows if instructed to do so""" server = Placeholder(Server) connect_flow = Placeholder(HTTPFlow) flow = Placeholder(HTTPFlow) def kill(flow: HTTPFlow): # Can't use flow.kill() here because that currently still depends on a reply object. flow.error = Error(Error.KILLED_MESSAGE) def assert_kill(err_hook: bool = True): playbook >> reply(side_effect=kill) if err_hook: playbook << http.HttpErrorHook(flow) playbook >> reply() playbook << CloseConnection(tctx.client) assert playbook playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular)) assert (playbook >> DataReceived(tctx.client, b"CONNECT example.com:80 HTTP/1.1\r\n\r\n") << http.HttpConnectHook(connect_flow)) if when == "http_connect": return assert_kill(False) assert (playbook >> reply() << SendData(tctx.client, b'HTTP/1.1 200 Connection established\r\n\r\n') >> DataReceived(tctx.client, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) << http.HttpRequestHeadersHook(flow)) if when == "requestheaders": return assert_kill() assert (playbook >> reply() << http.HttpRequestHook(flow)) if when == "request": return assert_kill() if when == "script-response-responseheaders": assert (playbook >> reply(side_effect=lambda f: setattr(f, "response", Response.make())) << http.HttpResponseHeadersHook(flow)) return assert_kill() assert (playbook >> reply() << OpenConnection(server) >> reply(None) << SendData(server, b"GET /foo?hello=1 HTTP/1.1\r\nHost: example.com\r\n\r\n") >> DataReceived(server, b"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World") << http.HttpResponseHeadersHook(flow)) if when == "responseheaders": return assert_kill() if when == "response": assert (playbook >> reply() >> DataReceived(server, b"!") << http.HttpResponseHook(flow)) return assert_kill(False) elif when == "error": assert (playbook >> reply() >> ConnectionClosed(server) << CloseConnection(server) << http.HttpErrorHook(flow)) return assert_kill(False) else: raise AssertionError
def test_upstream_proxy(tctx, redirect, scheme): """Test that an upstream HTTP proxy is used.""" server = Placeholder(Server) server2 = Placeholder(Server) flow = Placeholder(HTTPFlow) tctx.options.mode = "upstream:http://proxy:8080" playbook = Playbook(http.HttpLayer(tctx, HTTPMode.upstream), hooks=False) if scheme == "http": assert ( playbook >> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") << OpenConnection(server) >> reply(None) << SendData(server, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n") ) else: assert ( playbook >> DataReceived(tctx.client, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n") << SendData(tctx.client, b"HTTP/1.1 200 Connection established\r\n\r\n") >> DataReceived(tctx.client, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") << layer.NextLayerHook(Placeholder()) >> reply_next_layer(lambda ctx: http.HttpLayer(ctx, HTTPMode.transparent)) << OpenConnection(server) >> reply(None) << SendData(server, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n") >> DataReceived(server, b"HTTP/1.1 200 Connection established\r\n\r\n") << SendData(server, b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") ) playbook >> DataReceived(server, b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n") playbook << SendData(tctx.client, b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n") assert playbook assert server().address == ("proxy", 8080) if scheme == "http": playbook >> DataReceived(tctx.client, b"GET http://example.com/two HTTP/1.1\r\nHost: example.com\r\n\r\n") else: playbook >> DataReceived(tctx.client, b"GET /two HTTP/1.1\r\nHost: example.com\r\n\r\n") assert (playbook << http.HttpRequestHook(flow)) if redirect == "change-destination": flow().request.host = "other-server" flow().request.host_header = "example.com" elif redirect == "change-proxy": flow().server_conn.via = ServerSpec("http", address=("other-proxy", 1234)) playbook >> reply() if redirect: # Protocol-wise we wouldn't need to open a new connection for plain http host redirects, # but we disregard this edge case to simplify implementation. playbook << OpenConnection(server2) playbook >> reply(None) else: server2 = server if scheme == "http": if redirect == "change-destination": playbook << SendData(server2, b"GET http://other-server/two HTTP/1.1\r\nHost: example.com\r\n\r\n") else: playbook << SendData(server2, b"GET http://example.com/two HTTP/1.1\r\nHost: example.com\r\n\r\n") else: if redirect == "change-destination": playbook << SendData(server2, b"CONNECT other-server:443 HTTP/1.1\r\n\r\n") playbook >> DataReceived(server2, b"HTTP/1.1 200 Connection established\r\n\r\n") elif redirect == "change-proxy": playbook << SendData(server2, b"CONNECT example.com:443 HTTP/1.1\r\n\r\n") playbook >> DataReceived(server2, b"HTTP/1.1 200 Connection established\r\n\r\n") playbook << SendData(server2, b"GET /two HTTP/1.1\r\nHost: example.com\r\n\r\n") playbook >> DataReceived(server2, b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n") playbook << SendData(tctx.client, b"HTTP/1.1 418 OK\r\nContent-Length: 0\r\n\r\n") assert playbook if redirect == "change-proxy": assert server2().address == ("other-proxy", 1234) else: assert server2().address == ("proxy", 8080) assert ( playbook >> ConnectionClosed(tctx.client) << CloseConnection(tctx.client) )