diff options
Diffstat (limited to 'libmproxy/protocol2')
-rw-r--r-- | libmproxy/protocol2/http.py | 112 | ||||
-rw-r--r-- | libmproxy/protocol2/http_proxy.py | 3 | ||||
-rw-r--r-- | libmproxy/protocol2/http_replay.py | 95 | ||||
-rw-r--r-- | libmproxy/protocol2/layer.py | 19 | ||||
-rw-r--r-- | libmproxy/protocol2/messages.py | 46 | ||||
-rw-r--r-- | libmproxy/protocol2/rawtcp.py | 13 | ||||
-rw-r--r-- | libmproxy/protocol2/reverse_proxy.py | 5 | ||||
-rw-r--r-- | libmproxy/protocol2/root_context.py | 5 | ||||
-rw-r--r-- | libmproxy/protocol2/socks_proxy.py | 54 | ||||
-rw-r--r-- | libmproxy/protocol2/tls.py | 22 | ||||
-rw-r--r-- | libmproxy/protocol2/transparent_proxy.py | 3 |
11 files changed, 183 insertions, 194 deletions
diff --git a/libmproxy/protocol2/http.py b/libmproxy/protocol2/http.py index a3f32926..a508ae8b 100644 --- a/libmproxy/protocol2/http.py +++ b/libmproxy/protocol2/http.py @@ -1,28 +1,20 @@ from __future__ import (absolute_import, print_function, division) -from .. import version -import threading -from ..exceptions import InvalidCredentials, HttpException, ProtocolException -from .layer import Layer -from libmproxy import utils -from libmproxy.controller import Channel -from libmproxy.protocol2.layer import Kill -from libmproxy.protocol import KILL, Error - -from libmproxy.protocol.http import HTTPFlow -from libmproxy.protocol.http_wrappers import HTTPResponse, HTTPRequest -from libmproxy.proxy import Log -from libmproxy.proxy.connection import ServerConnection from netlib import tcp -from netlib.http import status_codes, http1, http2, HttpErrorConnClosed, HttpError +from netlib.http import status_codes, http1, HttpErrorConnClosed, HttpError from netlib.http.semantics import CONTENT_MISSING from netlib import odict from netlib.tcp import NetLibError, Address from netlib.http.http1 import HTTP1Protocol from netlib.http.http2 import HTTP2Protocol - -# TODO: The HTTP2 layer is missing multiplexing, which requires a major rewrite. +from .. import version, utils +from ..exceptions import InvalidCredentials, HttpException, ProtocolException +from .layer import Layer +from ..proxy import Kill +from libmproxy.protocol import KILL, Error +from libmproxy.protocol.http import HTTPFlow +from libmproxy.protocol.http_wrappers import HTTPResponse, HTTPRequest class _HttpLayer(Layer): @@ -138,6 +130,7 @@ class Http1Layer(_StreamingHttpLayer): layer() +# TODO: The HTTP2 layer is missing multiplexing, which requires a major rewrite. class Http2Layer(_HttpLayer): def __init__(self, ctx, mode): super(Http2Layer, self).__init__(ctx) @@ -359,6 +352,9 @@ class HttpLayer(Layer): return except (HttpErrorConnClosed, NetLibError, HttpError, ProtocolException) as e: + if flow.request and not flow.response: + flow.error = Error(repr(e)) + self.channel.ask("error", flow) try: self.send_response(make_error_response( getattr(e, "code", 502), @@ -590,87 +586,3 @@ class HttpLayer(Layer): ]) )) raise InvalidCredentials("Proxy Authentication Required") - - -class RequestReplayThread(threading.Thread): - name = "RequestReplayThread" - - def __init__(self, config, flow, masterq, should_exit): - """ - masterqueue can be a queue or None, if no scripthooks should be - processed. - """ - self.config, self.flow = config, flow - if masterq: - self.channel = Channel(masterq, should_exit) - else: - self.channel = None - super(RequestReplayThread, self).__init__() - - def run(self): - r = self.flow.request - form_out_backup = r.form_out - try: - self.flow.response = None - - # If we have a channel, run script hooks. - if self.channel: - request_reply = self.channel.ask("request", self.flow) - if request_reply is None or request_reply == KILL: - raise Kill() - elif isinstance(request_reply, HTTPResponse): - self.flow.response = request_reply - - if not self.flow.response: - # In all modes, we directly connect to the server displayed - if self.config.mode == "upstream": - server_address = self.config.upstream_server.address - server = ServerConnection(server_address) - server.connect() - protocol = HTTP1Protocol(server) - if r.scheme == "https": - connect_request = make_connect_request((r.host, r.port)) - server.send(protocol.assemble(connect_request)) - resp = protocol.read_response("CONNECT") - if resp.code != 200: - raise HttpError(502, "Upstream server refuses CONNECT request") - server.establish_ssl( - self.config.clientcerts, - sni=self.flow.server_conn.sni - ) - r.form_out = "relative" - else: - r.form_out = "absolute" - else: - server_address = (r.host, r.port) - server = ServerConnection(server_address) - server.connect() - protocol = HTTP1Protocol(server) - if r.scheme == "https": - server.establish_ssl( - self.config.clientcerts, - sni=self.flow.server_conn.sni - ) - r.form_out = "relative" - - server.send(protocol.assemble(r)) - self.flow.server_conn = server - self.flow.response = HTTPResponse.from_protocol( - protocol, - r.method, - body_size_limit=self.config.body_size_limit, - ) - if self.channel: - response_reply = self.channel.ask("response", self.flow) - if response_reply is None or response_reply == KILL: - raise Kill() - except (HttpError, tcp.NetLibError) as v: - self.flow.error = Error(repr(v)) - if self.channel: - self.channel.ask("error", self.flow) - except Kill: - # KillSignal should only be raised if there's a channel in the - # first place. - self.channel.tell("log", Log("Connection killed", "info")) - finally: - r.form_out = form_out_backup diff --git a/libmproxy/protocol2/http_proxy.py b/libmproxy/protocol2/http_proxy.py index c24af6cf..b3389eb7 100644 --- a/libmproxy/protocol2/http_proxy.py +++ b/libmproxy/protocol2/http_proxy.py @@ -13,6 +13,7 @@ class HttpProxy(Layer, ServerConnectionMixin): if self.server_conn: self._disconnect() + class HttpUpstreamProxy(Layer, ServerConnectionMixin): def __init__(self, ctx, server_address): super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address) @@ -23,4 +24,4 @@ class HttpUpstreamProxy(Layer, ServerConnectionMixin): layer() finally: if self.server_conn: - self._disconnect()
\ No newline at end of file + self._disconnect() diff --git a/libmproxy/protocol2/http_replay.py b/libmproxy/protocol2/http_replay.py new file mode 100644 index 00000000..872ef9cd --- /dev/null +++ b/libmproxy/protocol2/http_replay.py @@ -0,0 +1,95 @@ +import threading +from netlib.http import HttpError +from netlib.http.http1 import HTTP1Protocol +from netlib.tcp import NetLibError + +from ..controller import Channel +from ..protocol import KILL, Error +from ..protocol.http_wrappers import HTTPResponse +from ..proxy import Log, Kill +from ..proxy.connection import ServerConnection +from .http import make_connect_request + + +class RequestReplayThread(threading.Thread): + name = "RequestReplayThread" + + def __init__(self, config, flow, masterq, should_exit): + """ + masterqueue can be a queue or None, if no scripthooks should be + processed. + """ + self.config, self.flow = config, flow + if masterq: + self.channel = Channel(masterq, should_exit) + else: + self.channel = None + super(RequestReplayThread, self).__init__() + + def run(self): + r = self.flow.request + form_out_backup = r.form_out + try: + self.flow.response = None + + # If we have a channel, run script hooks. + if self.channel: + request_reply = self.channel.ask("request", self.flow) + if request_reply is None or request_reply == KILL: + raise Kill() + elif isinstance(request_reply, HTTPResponse): + self.flow.response = request_reply + + if not self.flow.response: + # In all modes, we directly connect to the server displayed + if self.config.mode == "upstream": + server_address = self.config.upstream_server.address + server = ServerConnection(server_address) + server.connect() + protocol = HTTP1Protocol(server) + if r.scheme == "https": + connect_request = make_connect_request((r.host, r.port)) + server.send(protocol.assemble(connect_request)) + resp = protocol.read_response("CONNECT") + if resp.code != 200: + raise HttpError(502, "Upstream server refuses CONNECT request") + server.establish_ssl( + self.config.clientcerts, + sni=self.flow.server_conn.sni + ) + r.form_out = "relative" + else: + r.form_out = "absolute" + else: + server_address = (r.host, r.port) + server = ServerConnection(server_address) + server.connect() + protocol = HTTP1Protocol(server) + if r.scheme == "https": + server.establish_ssl( + self.config.clientcerts, + sni=self.flow.server_conn.sni + ) + r.form_out = "relative" + + server.send(protocol.assemble(r)) + self.flow.server_conn = server + self.flow.response = HTTPResponse.from_protocol( + protocol, + r.method, + body_size_limit=self.config.body_size_limit, + ) + if self.channel: + response_reply = self.channel.ask("response", self.flow) + if response_reply is None or response_reply == KILL: + raise Kill() + except (HttpError, NetLibError) as v: + self.flow.error = Error(repr(v)) + if self.channel: + self.channel.ask("error", self.flow) + except Kill: + # KillSignal should only be raised if there's a channel in the + # first place. + self.channel.tell("log", Log("Connection killed", "info")) + finally: + r.form_out = form_out_backup diff --git a/libmproxy/protocol2/layer.py b/libmproxy/protocol2/layer.py index f72320ff..2b47cc26 100644 --- a/libmproxy/protocol2/layer.py +++ b/libmproxy/protocol2/layer.py @@ -30,8 +30,6 @@ Further goals: inline scripts shall have a chance to handle everything locally. """ from __future__ import (absolute_import, print_function, division) -import Queue -import threading from netlib import tcp from ..proxy import Log from ..proxy.connection import ServerConnection @@ -80,10 +78,8 @@ class Layer(_LayerCodeCompletion): def log(self, msg, level, subs=()): full_msg = [ - "%s:%s: %s" % - (self.client_conn.address.host, - self.client_conn.address.port, - msg)] + "{}: {}".format(repr(self.client_conn.address), msg) + ] for i in subs: full_msg.append(" -> " + i) full_msg = "\n".join(full_msg) @@ -119,7 +115,7 @@ class ServerConnectionMixin(object): self.log("Set new server address: " + repr(address), "debug") self.server_conn.address = address else: - self.ctx.set_server(address, server_tls, sni, depth-1) + self.ctx.set_server(address, server_tls, sni, depth - 1) def _disconnect(self): """ @@ -138,10 +134,5 @@ class ServerConnectionMixin(object): try: self.server_conn.connect() except tcp.NetLibError as e: - raise ProtocolException("Server connection to '%s' failed: %s" % (self.server_conn.address, e), e) - - -class Kill(Exception): - """ - Kill a connection. - """
\ No newline at end of file + raise ProtocolException( + "Server connection to '%s' failed: %s" % (self.server_conn.address, e), e) diff --git a/libmproxy/protocol2/messages.py b/libmproxy/protocol2/messages.py deleted file mode 100644 index de049486..00000000 --- a/libmproxy/protocol2/messages.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -This module contains all valid messages layers can send to the underlying layers. -""" -from __future__ import (absolute_import, print_function, division) -from netlib.tcp import Address - - -class _Message(object): - def __eq__(self, other): - # Allow message == Connect checks. - if isinstance(self, other): - return True - return self is other - - def __ne__(self, other): - return not self.__eq__(other) - - -class Connect(_Message): - """ - Connect to the server - """ - - -class Reconnect(_Message): - """ - Re-establish the server connection - """ - - -class SetServer(_Message): - """ - Change the upstream server. - """ - - def __init__(self, address, server_tls, sni, depth=1): - self.address = Address.wrap(address) - self.server_tls = server_tls - self.sni = sni - - # upstream proxy scenario: you may want to change either the final target or the upstream proxy. - # We can express this neatly as the "nth-server-providing-layer" - # ServerConnection could get a `via` attribute. - self.depth = depth - - diff --git a/libmproxy/protocol2/rawtcp.py b/libmproxy/protocol2/rawtcp.py index e8e3cf65..b10217f1 100644 --- a/libmproxy/protocol2/rawtcp.py +++ b/libmproxy/protocol2/rawtcp.py @@ -4,10 +4,9 @@ import select from OpenSSL import SSL -from ..exceptions import ProtocolException from netlib.tcp import NetLibError from netlib.utils import cleanBin -from ..protocol.tcp import TCPHandler +from ..exceptions import ProtocolException from .layer import Layer @@ -31,6 +30,7 @@ class RawTcpLayer(Layer): while True: r, _, _ = select.select(conns, [], [], 10) for conn in r: + dst = server if conn == client else client size = conn.recv_into(buf, self.chunk_size) if not size: @@ -41,22 +41,21 @@ class RawTcpLayer(Layer): # Sockets will be cleaned up on a higher level. return else: - conn.shutdown(socket.SHUT_WR) + dst.shutdown(socket.SHUT_WR) if len(conns) == 0: return continue - dst = server if conn == client else client dst.sendall(buf[:size]) if self.logging: # log messages are prepended with the client address, # hence the "weird" direction string. if dst == server: - direction = "-> tcp -> {!r}".format(self.server_conn.address) + direction = "-> tcp -> {}".format(repr(self.server_conn.address)) else: - direction = "<- tcp <- {!r}".format(self.server_conn.address) + direction = "<- tcp <- {}".format(repr(self.server_conn.address)) data = cleanBin(buf[:size].tobytes()) self.log( "{}\r\n{}".format(direction, data), @@ -64,4 +63,4 @@ class RawTcpLayer(Layer): ) except (socket.error, NetLibError, SSL.Error) as e: - raise ProtocolException("TCP connection closed unexpectedly: {}".format(repr(e)), e)
\ No newline at end of file + raise ProtocolException("TCP connection closed unexpectedly: {}".format(repr(e)), e) diff --git a/libmproxy/protocol2/reverse_proxy.py b/libmproxy/protocol2/reverse_proxy.py index 76163c71..e959db86 100644 --- a/libmproxy/protocol2/reverse_proxy.py +++ b/libmproxy/protocol2/reverse_proxy.py @@ -5,17 +5,18 @@ from .tls import TlsLayer class ReverseProxy(Layer, ServerConnectionMixin): - def __init__(self, ctx, server_address, client_tls, server_tls): super(ReverseProxy, self).__init__(ctx, server_address=server_address) self._client_tls = client_tls self._server_tls = server_tls def __call__(self): + # Always use a TLS layer here; if someone changes the scheme, there needs to be a + # TLS layer underneath. layer = TlsLayer(self, self._client_tls, self._server_tls) try: layer() finally: if self.server_conn: - self._disconnect()
\ No newline at end of file + self._disconnect() diff --git a/libmproxy/protocol2/root_context.py b/libmproxy/protocol2/root_context.py index af0e7a37..4d69204f 100644 --- a/libmproxy/protocol2/root_context.py +++ b/libmproxy/protocol2/root_context.py @@ -32,7 +32,7 @@ class RootContext(object): # 1. Check for --ignore. if self.config.check_ignore(top_layer.server_conn.address): - return RawTcpLayer(top_layer) + return RawTcpLayer(top_layer, logging=False) # 2. Check for TLS # TLS ClientHello magic, works for SSLv3, TLSv1.0, TLSv1.1, TLSv1.2 @@ -62,7 +62,8 @@ class RootContext(object): # d = top_layer.client_conn.rfile.peek(3) # is_ascii = ( # len(d) == 3 and - # all(x in string.ascii_letters for x in d) # better be safe here and don't expect uppercase... + # # better be safe here and don't expect uppercase... + # all(x in string.ascii_letters for x in d) # ) # # TODO: This could block if there are not enough bytes available? # d = top_layer.client_conn.rfile.peek(len(HTTP2Protocol.CLIENT_CONNECTION_PREFACE)) diff --git a/libmproxy/protocol2/socks_proxy.py b/libmproxy/protocol2/socks_proxy.py index 91935d24..525520e8 100644 --- a/libmproxy/protocol2/socks_proxy.py +++ b/libmproxy/protocol2/socks_proxy.py @@ -1,27 +1,59 @@ from __future__ import (absolute_import, print_function, division) -from ..exceptions import ProtocolException -from ..proxy import ProxyError, Socks5ProxyMode +from netlib import socks +from netlib.tcp import NetLibError +from ..exceptions import Socks5Exception from .layer import Layer, ServerConnectionMixin class Socks5Proxy(Layer, ServerConnectionMixin): def __call__(self): try: - s5mode = Socks5ProxyMode([]) - address = s5mode.get_upstream_server(self.client_conn)[2:] - except ProxyError as e: - # TODO: Unmonkeypatch - raise ProtocolException(str(e), e) + # Parse Client Greeting + client_greet = socks.ClientGreeting.from_file(self.client_conn.rfile, fail_early=True) + client_greet.assert_socks5() + if socks.METHOD.NO_AUTHENTICATION_REQUIRED not in client_greet.methods: + raise socks.SocksError( + socks.METHOD.NO_ACCEPTABLE_METHODS, + "mitmproxy only supports SOCKS without authentication" + ) - self.server_conn.address = address + # Send Server Greeting + server_greet = socks.ServerGreeting( + socks.VERSION.SOCKS5, + socks.METHOD.NO_AUTHENTICATION_REQUIRED + ) + server_greet.to_file(self.client_conn.wfile) + self.client_conn.wfile.flush() - # TODO: Kill event + # Parse Connect Request + connect_request = socks.Message.from_file(self.client_conn.rfile) + connect_request.assert_socks5() + if connect_request.msg != socks.CMD.CONNECT: + raise socks.SocksError( + socks.REP.COMMAND_NOT_SUPPORTED, + "mitmproxy only supports SOCKS5 CONNECT." + ) - layer = self.ctx.next_layer(self) + # We always connect lazily, but we need to pretend to the client that we connected. + connect_reply = socks.Message( + socks.VERSION.SOCKS5, + socks.REP.SUCCEEDED, + connect_request.atyp, + # dummy value, we don't have an upstream connection yet. + connect_request.addr + ) + connect_reply.to_file(self.client_conn.wfile) + self.client_conn.wfile.flush() + + except (socks.SocksError, NetLibError) as e: + raise Socks5Exception("SOCKS5 mode failure: %s" % repr(e), e) + self.server_conn.address = connect_request.addr + + layer = self.ctx.next_layer(self) try: layer() finally: if self.server_conn: - self._disconnect()
\ No newline at end of file + self._disconnect() diff --git a/libmproxy/protocol2/tls.py b/libmproxy/protocol2/tls.py index 850bf5dc..0c02b0ea 100644 --- a/libmproxy/protocol2/tls.py +++ b/libmproxy/protocol2/tls.py @@ -1,11 +1,11 @@ from __future__ import (absolute_import, print_function, division) import struct -from construct import ConstructError -from netlib import tcp -import netlib.http.http2 +from construct import ConstructError +from netlib.tcp import NetLibError, NetLibInvalidCertificateError +from netlib.http.http1 import HTTP1Protocol from ..contrib.tls._constructs import ClientHello from ..exceptions import ProtocolException from .layer import Layer @@ -161,7 +161,7 @@ class TlsLayer(Layer): """ # This gets triggered if we haven't established an upstream connection yet. - default_alpn = netlib.http.http1.HTTP1Protocol.ALPN_PROTO_HTTP1 + default_alpn = HTTP1Protocol.ALPN_PROTO_HTTP1 # alpn_preference = netlib.http.http2.HTTP2Protocol.ALPN_PROTO_H2 if self.alpn_for_client_connection in options: @@ -203,7 +203,7 @@ class TlsLayer(Layer): chain_file=chain_file, alpn_select_callback=self.__alpn_select_callback, ) - except tcp.NetLibError as e: + except NetLibError as e: raise ProtocolException("Cannot establish TLS with client: %s" % repr(e), e) def _establish_tls_with_server(self): @@ -236,7 +236,7 @@ class TlsLayer(Layer): (tls_cert_err['depth'], tls_cert_err['errno']), "error") self.log("Ignoring server verification error, continuing with connection", "error") - except tcp.NetLibInvalidCertificateError as e: + except NetLibInvalidCertificateError as e: tls_cert_err = self.server_conn.ssl_verification_error self.log( "TLS verification failed for upstream server at depth %s with error: %s" % @@ -244,7 +244,7 @@ class TlsLayer(Layer): "error") self.log("Aborting connection attempt", "error") raise ProtocolException("Cannot establish TLS with server: %s" % repr(e), e) - except tcp.NetLibError as e: + except NetLibError as e: raise ProtocolException("Cannot establish TLS with server: %s" % repr(e), e) self.log("ALPN selected by server: %s" % self.alpn_for_client_connection, "debug") @@ -253,8 +253,12 @@ class TlsLayer(Layer): host = self.server_conn.address.host sans = set() # Incorporate upstream certificate - if self.server_conn and self.server_conn.tls_established and ( - not self.config.no_upstream_cert): + use_upstream_cert = ( + self.server_conn and + self.server_conn.tls_established and + (not self.config.no_upstream_cert) + ) + if use_upstream_cert: upstream_cert = self.server_conn.cert sans.update(upstream_cert.altnames) if upstream_cert.cn: diff --git a/libmproxy/protocol2/transparent_proxy.py b/libmproxy/protocol2/transparent_proxy.py index 9263dbde..e6ebf115 100644 --- a/libmproxy/protocol2/transparent_proxy.py +++ b/libmproxy/protocol2/transparent_proxy.py @@ -6,7 +6,6 @@ from .layer import Layer, ServerConnectionMixin class TransparentProxy(Layer, ServerConnectionMixin): - def __init__(self, ctx): super(TransparentProxy, self).__init__(ctx) self.resolver = platform.resolver() @@ -22,4 +21,4 @@ class TransparentProxy(Layer, ServerConnectionMixin): layer() finally: if self.server_conn: - self._disconnect()
\ No newline at end of file + self._disconnect() |