aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2016-09-22 01:58:08 -0700
committerMaximilian Hils <git@maximilianhils.com>2016-09-22 01:58:08 -0700
commitf59ae4a57f65fa76812c3a29965df8a4b35448e9 (patch)
treeb7372482e9a40fe68f1faa70a2e7905076c8f960
parent0dbb7033ee1a6e238752381ce99439ef7d38b208 (diff)
parente5b79a6d728584cceb918ffbf73c54ec55e948b5 (diff)
downloadmitmproxy-f59ae4a57f65fa76812c3a29965df8a4b35448e9.tar.gz
mitmproxy-f59ae4a57f65fa76812c3a29965df8a4b35448e9.tar.bz2
mitmproxy-f59ae4a57f65fa76812c3a29965df8a4b35448e9.zip
Merge branch 'fully_transparent'
-rw-r--r--docs/transparent.rst29
-rw-r--r--examples/full_transparency_shim.c87
-rw-r--r--mitmproxy/cmdline.py6
-rw-r--r--mitmproxy/models/connections.py6
-rw-r--r--mitmproxy/options.py2
-rw-r--r--mitmproxy/protocol/base.py18
-rw-r--r--netlib/tcp.py13
7 files changed, 154 insertions, 7 deletions
diff --git a/docs/transparent.rst b/docs/transparent.rst
index eb77c76c..71b48595 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 00000000..923eea76
--- /dev/null
+++ b/examples/full_transparency_shim.c
@@ -0,0 +1,87 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/capability.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* 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 fe55ad5a..9fb4a561 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 570e89a9..a98711a1 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 480e0de8..ba4ed0c7 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 bf0cbbae..da6e8356 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 e5c84165..eea10425 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())