mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2025-02-03 08:46:40 +00:00
Merge branch 'fully_transparent'
This commit is contained in:
commit
f59ae4a57f
@ -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\)
|
||||||
|
87
examples/full_transparency_shim.c
Normal file
87
examples/full_transparency_shim.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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())
|
||||||
|
Loading…
Reference in New Issue
Block a user