diff --git a/docs/transparent.rst b/docs/transparent.rst index eb77c76cb..71b485952 100644 --- a/docs/transparent.rst +++ b/docs/transparent.rst @@ -1,5 +1,6 @@ .. _transparent: +==================== Transparent Proxying ==================== @@ -20,5 +21,33 @@ destination of the TCP connection. At the moment, mitmproxy supports transparent proxying on OSX Lion and above, and all current flavors of Linux. +Fully transparent mode +====================== + +By default mitmproxy will use its own local ip address for its server-side connections. +In case this isn't desired, the --spoof-source-address argument can be used to +use the client's ip address for server-side connections. The following config is +required for this mode to work: + + CLIENT_NET=192.168.1.0/24 + TABLE_ID=100 + MARK=1 + + echo "$TABLE_ID mitmproxy" >> /etc/iproute2/rt_tables + iptables -t mangle -A PREROUTING -d $CLIENT_NET -j MARK --set-mark $MARK + iptables -t nat -A PREROUTING -p tcp -s $CLIENT_NET --match multiport --dports 80,443 -j REDIRECT --to-port 8080 + + ip rule add fwmark $MARK lookup $TABLE_ID + ip route add local $CLIENT_NET dev lo table $TABLE_ID + +This mode does require root privileges though. There's a wrapper in the examples directory +called 'mitmproxy_shim.c', which will enable you to use this mode with dropped priviliges. +It can be used as follows: + + gcc examples/mitmproxy_shim.c -o mitmproxy_shim -lcap + sudo chown root:root mitmproxy_shim + sudo chmod u+s mitmproxy_shim + ./mitmproxy_shim $(which mitmproxy) -T --spoof-source-address + .. _iptables: http://www.netfilter.org/ .. _pf: https://en.wikipedia.org/wiki/PF_\(firewall\) diff --git a/examples/full_transparency_shim.c b/examples/full_transparency_shim.c new file mode 100644 index 000000000..923eea760 --- /dev/null +++ b/examples/full_transparency_shim.c @@ -0,0 +1,87 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +/* This setuid wrapper can be used to run mitmproxy in full transparency mode, as a normal user. + * It will set the required capabilities (CAP_NET_RAW), drop privileges, and will then run argv[1] + * with the same capabilities. + * + * It can be compiled as follows: + * gcc examples/mitmproxy_shim.c -o mitmproxy_shim -lcap +*/ + +int set_caps(cap_t cap_struct, cap_value_t *cap_list, size_t bufsize) { + int cap_count = bufsize / sizeof(cap_list[0]); + + if (cap_set_flag(cap_struct, CAP_PERMITTED, cap_count, cap_list, CAP_SET) || + cap_set_flag(cap_struct, CAP_EFFECTIVE, cap_count, cap_list, CAP_SET) || + cap_set_flag(cap_struct, CAP_INHERITABLE, cap_count, cap_list, CAP_SET)) { + if (cap_count < 2) { + fprintf(stderr, "Cannot manipulate capability data structure as user: %s.\n", strerror(errno)); + } else { + fprintf(stderr, "Cannot manipulate capability data structure as root: %s.\n", strerror(errno)); + } + return -1; + } + + if (cap_count < 2) { + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0)) { + fprintf(stderr, "Failed to add CAP_NET_RAW to the ambient set: %s.\n", strerror(errno)); + return -2; + } + } + + if (cap_set_proc(cap_struct)) { + if (cap_count < 2) { + fprintf(stderr, "Cannot set capabilities as user: %s.\n", strerror(errno)); + } else { + fprintf(stderr, "Cannot set capabilities as root: %s.\n", strerror(errno)); + } + return -3; + } + + if (cap_count > 1) { + if (prctl(PR_SET_KEEPCAPS, 1L)) { + fprintf(stderr, "Cannot keep capabilities after dropping privileges: %s.\n", strerror(errno)); + return -4; + } + if (cap_clear(cap_struct)) { + fprintf(stderr, "Cannot clear capability data structure: %s.\n", strerror(errno)); + return -5; + } + } +} + +int main(int argc, char **argv, char **envp) { + cap_t cap_struct = cap_init(); + cap_value_t root_caps[2] = { CAP_NET_RAW, CAP_SETUID }; + cap_value_t user_caps[1] = { CAP_NET_RAW }; + uid_t user = getuid(); + int res; + + if (setresuid(0, 0, 0)) { + fprintf(stderr, "Cannot switch to root: %s.\n", strerror(errno)); + return 1; + } + + if (res = set_caps(cap_struct, root_caps, sizeof(root_caps))) + return res; + + if (setresuid(user, user, user)) { + fprintf(stderr, "Cannot drop root privileges: %s.\n", strerror(errno)); + return 2; + } + + if (res = set_caps(cap_struct, user_caps, sizeof(user_caps))) + return res; + + if (execve(argv[1], argv + 1, envp)) { + fprintf(stderr, "Failed to execute %s: %s\n", argv[1], strerror(errno)); + return 3; + } +} diff --git a/mitmproxy/cmdline.py b/mitmproxy/cmdline.py index fe55ad5a4..9fb4a561d 100644 --- a/mitmproxy/cmdline.py +++ b/mitmproxy/cmdline.py @@ -255,6 +255,7 @@ def get_common_options(args): listen_port = args.port, mode = mode, no_upstream_cert = args.no_upstream_cert, + spoof_source_address = args.spoof_source_address, rawtcp = args.rawtcp, upstream_server = upstream_server, upstream_auth = args.upstream_auth, @@ -474,6 +475,11 @@ def proxy_options(parser): "Disabled by default. " "Default value will change in a future version." ) + group.add_argument( + "--spoof-source-address", + action="store_true", dest="spoof_source_address", + help="Use the client's IP for server-side connections" + ) def proxy_ssl_options(parser): diff --git a/mitmproxy/models/connections.py b/mitmproxy/models/connections.py index 570e89a9a..a98711a15 100644 --- a/mitmproxy/models/connections.py +++ b/mitmproxy/models/connections.py @@ -112,7 +112,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): Attributes: address: Remote address. Can be both a domain or an IP address. ip_address: Resolved remote IP address. - source_address: Local IP address + source_address: Local IP address or client's source IP address. ssl_established: True if TLS is established, False otherwise cert: The certificate presented by the remote during the TLS handshake sni: Server Name Indication sent by the proxy during the TLS handshake @@ -123,8 +123,8 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject): timestamp_end: Connection end timestamp """ - def __init__(self, address, source_address=None): - tcp.TCPClient.__init__(self, address, source_address) + def __init__(self, address, source_address=None, spoof_source_address=None): + tcp.TCPClient.__init__(self, address, source_address, spoof_source_address) self.via = None self.timestamp_start = None diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 480e0de8e..ba4ed0c7b 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -70,6 +70,7 @@ class Options(optmanager.OptManager): mode = "regular", # type: str no_upstream_cert = False, # type: bool rawtcp = False, # type: bool + spoof_source_address = False, # type: bool upstream_server = "", # type: str upstream_auth = "", # type: str ssl_version_client="secure", # type: str @@ -128,6 +129,7 @@ class Options(optmanager.OptManager): self.mode = mode self.no_upstream_cert = no_upstream_cert self.rawtcp = rawtcp + self.spoof_source_address = spoof_source_address self.upstream_server = upstream_server self.upstream_auth = upstream_auth self.ssl_version_client = ssl_version_client diff --git a/mitmproxy/protocol/base.py b/mitmproxy/protocol/base.py index bf0cbbae7..da6e83563 100644 --- a/mitmproxy/protocol/base.py +++ b/mitmproxy/protocol/base.py @@ -114,7 +114,15 @@ class ServerConnectionMixin(object): def __init__(self, server_address=None): super(ServerConnectionMixin, self).__init__() - self.server_conn = models.ServerConnection(server_address, (self.config.options.listen_host, 0)) + + self.server_conn = None + if self.config.options.spoof_source_address: + self.server_conn = models.ServerConnection( + server_address, (self.ctx.client_conn.address.host, 0), True) + else: + self.server_conn = models.ServerConnection( + server_address, (self.config.options.listen_host, 0)) + self.__check_self_connect() def __check_self_connect(self): @@ -151,11 +159,15 @@ class ServerConnectionMixin(object): """ self.log("serverdisconnect", "debug", [repr(self.server_conn.address)]) address = self.server_conn.address - source_address = self.server_conn.source_address self.server_conn.finish() self.server_conn.close() self.channel.tell("serverdisconnect", self.server_conn) - self.server_conn = models.ServerConnection(address, (source_address.host, 0)) + + self.server_conn = models.ServerConnection( + address, + (self.server_conn.source_address.host, 0), + self.config.options.spoof_source_address + ) def connect(self): """ diff --git a/netlib/tcp.py b/netlib/tcp.py index e5c841655..eea104252 100644 --- a/netlib/tcp.py +++ b/netlib/tcp.py @@ -605,7 +605,7 @@ class ConnectionCloser(object): class TCPClient(_Connection): - def __init__(self, address, source_address=None): + def __init__(self, address, source_address=None, spoof_source_address=None): super(TCPClient, self).__init__(None) self.address = address self.source_address = source_address @@ -613,6 +613,7 @@ class TCPClient(_Connection): self.server_certs = [] self.ssl_verification_error = None # type: Optional[exceptions.InvalidCertificateException] self.sni = None + self.spoof_source_address = spoof_source_address @property def address(self): @@ -729,6 +730,16 @@ class TCPClient(_Connection): def connect(self): try: connection = socket.socket(self.address.family, socket.SOCK_STREAM) + + if self.spoof_source_address: + try: + # 19 is `IP_TRANSPARENT`, which is only available on Python 3.3+ on some OSes + if not connection.getsockopt(socket.SOL_IP, 19): + connection.setsockopt(socket.SOL_IP, 19, 1) + except socket.error as e: + raise exceptions.TcpException( + "Failed to spoof the source address: " + e.strerror + ) if self.source_address: connection.bind(self.source_address()) connection.connect(self.address())