Merge branch 'fully_transparent'

This commit is contained in:
Maximilian Hils 2016-09-22 01:58:08 -07:00
commit f59ae4a57f
7 changed files with 154 additions and 7 deletions

View File

@ -1,5 +1,6 @@
.. _transparent: .. _transparent:
====================
Transparent Proxying Transparent Proxying
==================== ====================
@ -20,5 +21,33 @@ destination of the TCP connection.
At the moment, mitmproxy supports transparent proxying on OSX Lion and above, At the moment, mitmproxy supports transparent proxying on OSX Lion and above,
and all current flavors of Linux. 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/ .. _iptables: http://www.netfilter.org/
.. _pf: https://en.wikipedia.org/wiki/PF_\(firewall\) .. _pf: https://en.wikipedia.org/wiki/PF_\(firewall\)

View File

@ -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;
}
}

View File

@ -255,6 +255,7 @@ def get_common_options(args):
listen_port = args.port, listen_port = args.port,
mode = mode, mode = mode,
no_upstream_cert = args.no_upstream_cert, no_upstream_cert = args.no_upstream_cert,
spoof_source_address = args.spoof_source_address,
rawtcp = args.rawtcp, rawtcp = args.rawtcp,
upstream_server = upstream_server, upstream_server = upstream_server,
upstream_auth = args.upstream_auth, upstream_auth = args.upstream_auth,
@ -474,6 +475,11 @@ def proxy_options(parser):
"Disabled by default. " "Disabled by default. "
"Default value will change in a future version." "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): def proxy_ssl_options(parser):

View File

@ -112,7 +112,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
Attributes: Attributes:
address: Remote address. Can be both a domain or an IP address. address: Remote address. Can be both a domain or an IP address.
ip_address: Resolved remote 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 ssl_established: True if TLS is established, False otherwise
cert: The certificate presented by the remote during the TLS handshake cert: The certificate presented by the remote during the TLS handshake
sni: Server Name Indication sent by the proxy 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 timestamp_end: Connection end timestamp
""" """
def __init__(self, address, source_address=None): def __init__(self, address, source_address=None, spoof_source_address=None):
tcp.TCPClient.__init__(self, address, source_address) tcp.TCPClient.__init__(self, address, source_address, spoof_source_address)
self.via = None self.via = None
self.timestamp_start = None self.timestamp_start = None

View File

@ -70,6 +70,7 @@ class Options(optmanager.OptManager):
mode = "regular", # type: str mode = "regular", # type: str
no_upstream_cert = False, # type: bool no_upstream_cert = False, # type: bool
rawtcp = False, # type: bool rawtcp = False, # type: bool
spoof_source_address = False, # type: bool
upstream_server = "", # type: str upstream_server = "", # type: str
upstream_auth = "", # type: str upstream_auth = "", # type: str
ssl_version_client="secure", # type: str ssl_version_client="secure", # type: str
@ -128,6 +129,7 @@ class Options(optmanager.OptManager):
self.mode = mode self.mode = mode
self.no_upstream_cert = no_upstream_cert self.no_upstream_cert = no_upstream_cert
self.rawtcp = rawtcp self.rawtcp = rawtcp
self.spoof_source_address = spoof_source_address
self.upstream_server = upstream_server self.upstream_server = upstream_server
self.upstream_auth = upstream_auth self.upstream_auth = upstream_auth
self.ssl_version_client = ssl_version_client self.ssl_version_client = ssl_version_client

View File

@ -114,7 +114,15 @@ class ServerConnectionMixin(object):
def __init__(self, server_address=None): def __init__(self, server_address=None):
super(ServerConnectionMixin, self).__init__() 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() self.__check_self_connect()
def __check_self_connect(self): def __check_self_connect(self):
@ -151,11 +159,15 @@ class ServerConnectionMixin(object):
""" """
self.log("serverdisconnect", "debug", [repr(self.server_conn.address)]) self.log("serverdisconnect", "debug", [repr(self.server_conn.address)])
address = self.server_conn.address address = self.server_conn.address
source_address = self.server_conn.source_address
self.server_conn.finish() self.server_conn.finish()
self.server_conn.close() self.server_conn.close()
self.channel.tell("serverdisconnect", self.server_conn) 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): def connect(self):
""" """

View File

@ -605,7 +605,7 @@ class ConnectionCloser(object):
class TCPClient(_Connection): 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) super(TCPClient, self).__init__(None)
self.address = address self.address = address
self.source_address = source_address self.source_address = source_address
@ -613,6 +613,7 @@ class TCPClient(_Connection):
self.server_certs = [] self.server_certs = []
self.ssl_verification_error = None # type: Optional[exceptions.InvalidCertificateException] self.ssl_verification_error = None # type: Optional[exceptions.InvalidCertificateException]
self.sni = None self.sni = None
self.spoof_source_address = spoof_source_address
@property @property
def address(self): def address(self):
@ -729,6 +730,16 @@ class TCPClient(_Connection):
def connect(self): def connect(self):
try: try:
connection = socket.socket(self.address.family, socket.SOCK_STREAM) 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: if self.source_address:
connection.bind(self.source_address()) connection.bind(self.source_address())
connection.connect(self.address()) connection.connect(self.address())