Merge pull request #638 from iroiro123/http-transparent
HTTP Transparent Proxy
This commit is contained in:
commit
e2069d52a8
|
@ -370,6 +370,21 @@ def common_options(parser):
|
|||
default=None,
|
||||
help="Forward all requests to upstream proxy server: http://host[:port]"
|
||||
)
|
||||
group.add_argument(
|
||||
"--spoof",
|
||||
action="store_true", dest="spoof_mode", default=False,
|
||||
help="Use Host header to connect to HTTP servers."
|
||||
)
|
||||
group.add_argument(
|
||||
"--ssl-spoof",
|
||||
action="store_true", dest="ssl_spoof_mode", default=False,
|
||||
help="Use TLS SNI to connect to HTTPS servers."
|
||||
)
|
||||
group.add_argument(
|
||||
"--spoofed-port",
|
||||
action="store", dest="spoofed_ssl_port", type=int, default=443,
|
||||
help="Port number of upstream HTTPS servers in SSL spoof mode."
|
||||
)
|
||||
|
||||
group = parser.add_argument_group(
|
||||
"Advanced Proxy Options",
|
||||
|
|
|
@ -1328,8 +1328,34 @@ class HTTPHandler(ProtocolHandler):
|
|||
# value at flow.server_conn
|
||||
self.c.set_server_address((request.host, request.port))
|
||||
flow.server_conn = self.c.server_conn
|
||||
|
||||
|
||||
elif request.form_in == "relative":
|
||||
if self.c.config.mode == "spoof":
|
||||
# Host header
|
||||
h = request.pretty_host(hostheader=True)
|
||||
if h is None:
|
||||
raise http.HttpError(
|
||||
400,
|
||||
"Invalid request: No host information"
|
||||
)
|
||||
p = http.parse_url("http://" + h)
|
||||
request.scheme = p[0]
|
||||
request.host = p[1]
|
||||
request.port = p[2]
|
||||
self.c.set_server_address((request.host, request.port))
|
||||
flow.server_conn = self.c.server_conn
|
||||
|
||||
if self.c.config.mode == "sslspoof":
|
||||
# SNI is processed in server.py
|
||||
if not (flow.server_conn and flow.server_conn.ssl_established):
|
||||
print ":::::::::::::::"
|
||||
raise http.HttpError(
|
||||
400,
|
||||
"Invalid request: No host information"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
raise http.HttpError(
|
||||
400, "Invalid HTTP request form (expected: %s, got: %s)" % (
|
||||
self.expected_form_in, request.form_in
|
||||
|
|
|
@ -4,7 +4,7 @@ import re
|
|||
from OpenSSL import SSL
|
||||
from netlib import http_auth, certutils, tcp
|
||||
from .. import utils, platform, version
|
||||
from .primitives import RegularProxyMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode, Socks5ProxyMode
|
||||
from .primitives import RegularProxyMode, SpoofMode, SSLSpoofMode, TransparentProxyMode, UpstreamProxyMode, ReverseProxyMode, Socks5ProxyMode
|
||||
|
||||
TRANSPARENT_SSL_PORTS = [443, 8443]
|
||||
CONF_BASENAME = "mitmproxy"
|
||||
|
@ -51,7 +51,8 @@ class ProxyConfig:
|
|||
certforward=False,
|
||||
ssl_version_client="secure",
|
||||
ssl_version_server="secure",
|
||||
ssl_ports=TRANSPARENT_SSL_PORTS
|
||||
ssl_ports=TRANSPARENT_SSL_PORTS,
|
||||
spoofed_ssl_port=None
|
||||
):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
@ -70,6 +71,10 @@ class ProxyConfig:
|
|||
self.mode = ReverseProxyMode(upstream_server)
|
||||
elif mode == "upstream":
|
||||
self.mode = UpstreamProxyMode(upstream_server)
|
||||
elif mode == "spoof":
|
||||
self.mode = SpoofMode()
|
||||
elif mode == "sslspoof":
|
||||
self.mode = SSLSpoofMode(spoofed_ssl_port)
|
||||
else:
|
||||
self.mode = RegularProxyMode()
|
||||
|
||||
|
@ -126,7 +131,7 @@ def process_proxy_options(parser, options):
|
|||
body_size_limit = utils.parse_size(options.body_size_limit)
|
||||
|
||||
c = 0
|
||||
mode, upstream_server = None, None
|
||||
mode, upstream_server, spoofed_ssl_port = None, None, None
|
||||
if options.transparent_proxy:
|
||||
c += 1
|
||||
if not platform.resolver:
|
||||
|
@ -144,6 +149,13 @@ def process_proxy_options(parser, options):
|
|||
c += 1
|
||||
mode = "upstream"
|
||||
upstream_server = options.upstream_proxy
|
||||
if options.spoof_mode:
|
||||
c += 1
|
||||
mode = "spoof"
|
||||
if options.ssl_spoof_mode:
|
||||
c += 1
|
||||
mode = "sslspoof"
|
||||
spoofed_ssl_port = options.spoofed_ssl_port
|
||||
if c > 1:
|
||||
return parser.error(
|
||||
"Transparent, SOCKS5, reverse and upstream proxy mode "
|
||||
|
@ -213,7 +225,8 @@ def process_proxy_options(parser, options):
|
|||
certforward=options.certforward,
|
||||
ssl_version_client=options.ssl_version_client,
|
||||
ssl_version_server=options.ssl_version_server,
|
||||
ssl_ports=ssl_ports
|
||||
ssl_ports=ssl_ports,
|
||||
spoofed_ssl_port=spoofed_ssl_port
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -51,6 +51,33 @@ class RegularProxyMode(ProxyMode):
|
|||
return None
|
||||
|
||||
|
||||
class SpoofMode(ProxyMode):
|
||||
http_form_in = "relative"
|
||||
http_form_out = "relative"
|
||||
|
||||
def get_upstream_server(self, client_conn):
|
||||
return None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return "spoof"
|
||||
|
||||
|
||||
class SSLSpoofMode(ProxyMode):
|
||||
http_form_in = "relative"
|
||||
http_form_out = "relative"
|
||||
|
||||
def __init__(self, sslport):
|
||||
self.sslport = sslport
|
||||
|
||||
def get_upstream_server(self, client_conn):
|
||||
return None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return "sslspoof"
|
||||
|
||||
|
||||
class TransparentProxyMode(ProxyMode):
|
||||
http_form_in = "relative"
|
||||
http_form_out = "relative"
|
||||
|
|
|
@ -117,6 +117,16 @@ class ConnectionHandler:
|
|||
self.server_conn.address(),
|
||||
"info")
|
||||
self.conntype = "tcp"
|
||||
|
||||
elif not self.server_conn and self.config.mode == "sslspoof":
|
||||
port = self.config.mode.sslport
|
||||
self.set_server_address(("-", port))
|
||||
self.establish_ssl(client=True)
|
||||
host = self.client_conn.connection.get_servername()
|
||||
if host:
|
||||
self.set_server_address((host, port))
|
||||
self.establish_server_connection()
|
||||
self.establish_ssl(server=True, sni=host)
|
||||
|
||||
# Delegate handling to the protocol handler
|
||||
protocol_handler(
|
||||
|
@ -308,6 +318,9 @@ class ConnectionHandler:
|
|||
host = upstream_cert.cn.decode("utf8").encode("idna")
|
||||
if self.server_conn.sni:
|
||||
sans.append(self.server_conn.sni)
|
||||
# for ssl spoof mode
|
||||
if hasattr(self.client_conn, "sni"):
|
||||
sans.append(self.client_conn.sni)
|
||||
|
||||
ret = self.config.certstore.get_cert(host, sans)
|
||||
if not ret:
|
||||
|
@ -325,6 +338,8 @@ class ConnectionHandler:
|
|||
if not sn:
|
||||
return
|
||||
sni = sn.decode("utf8").encode("idna")
|
||||
# for ssl spoof mode
|
||||
self.client_conn.sni = sni
|
||||
|
||||
if sni != self.server_conn.sni:
|
||||
self.log("SNI received: %s" % sni, "debug")
|
||||
|
|
|
@ -90,6 +90,12 @@ class TestProcessProxyOptions:
|
|||
self.assert_err("expected one argument", "-U")
|
||||
self.assert_err("Invalid server specification", "-U", "upstream")
|
||||
|
||||
self.assert_noerr("--spoof")
|
||||
self.assert_noerr("--ssl-spoof")
|
||||
|
||||
self.assert_noerr("--spoofed-port", "443")
|
||||
self.assert_err("expected one argument", "--spoofed-port")
|
||||
|
||||
self.assert_err("mutually exclusive", "-R", "http://localhost", "-T")
|
||||
|
||||
def test_client_certs(self):
|
||||
|
|
|
@ -368,6 +368,55 @@ class TestReverse(tservers.ReverseProxTest, CommonMixin, TcpMixin):
|
|||
reverse = True
|
||||
|
||||
|
||||
class TestSpoof(tservers.SpoofModeTest):
|
||||
def test_http(self):
|
||||
alist = (
|
||||
("localhost", self.server.port),
|
||||
("127.0.0.1", self.server.port)
|
||||
)
|
||||
for a in alist:
|
||||
self.server.clear_log()
|
||||
p = self.pathoc()
|
||||
f = p.request("get:/p/304:h'Host'='%s:%s'" % a)
|
||||
assert self.server.last_log()
|
||||
assert f.status_code == 304
|
||||
l = self.master.state.view[-1]
|
||||
assert l.server_conn.address
|
||||
assert l.server_conn.address.host == a[0]
|
||||
assert l.server_conn.address.port == a[1]
|
||||
|
||||
def test_http_without_host(self):
|
||||
p = self.pathoc()
|
||||
f = p.request("get:/p/304:r")
|
||||
assert f.status_code == 400
|
||||
|
||||
|
||||
class TestSSLSpoof(tservers.SSLSpoofModeTest):
|
||||
def test_https(self):
|
||||
alist = (
|
||||
("localhost", self.server.port),
|
||||
("127.0.0.1", self.server.port)
|
||||
)
|
||||
for a in alist:
|
||||
self.server.clear_log()
|
||||
self.config.mode.sslport = a[1]
|
||||
p = self.pathoc(sni=a[0])
|
||||
f = p.request("get:/p/304")
|
||||
assert self.server.last_log()
|
||||
assert f.status_code == 304
|
||||
l = self.master.state.view[-1]
|
||||
assert l.server_conn.address
|
||||
assert l.server_conn.address.host == a[0]
|
||||
assert l.server_conn.address.port == a[1]
|
||||
|
||||
def test_https_without_sni(self):
|
||||
a = ("localhost", self.server.port)
|
||||
self.config.mode.sslport = a[1]
|
||||
p = self.pathoc(sni=None)
|
||||
f = p.request("get:/p/304")
|
||||
assert f.status_code == 400
|
||||
|
||||
|
||||
class TestHttps2Http(tservers.ReverseProxTest):
|
||||
@classmethod
|
||||
def get_proxy_config(cls):
|
||||
|
|
|
@ -270,6 +270,49 @@ class ReverseProxTest(ProxTestBase):
|
|||
return p.request(q)
|
||||
|
||||
|
||||
class SpoofModeTest(ProxTestBase):
|
||||
ssl = None
|
||||
|
||||
@classmethod
|
||||
def get_proxy_config(cls):
|
||||
d = ProxTestBase.get_proxy_config()
|
||||
d["upstream_server"] = None
|
||||
d["mode"] = "spoof"
|
||||
return d
|
||||
|
||||
def pathoc(self, sni=None):
|
||||
"""
|
||||
Returns a connected Pathoc instance.
|
||||
"""
|
||||
p = libpathod.pathoc.Pathoc(
|
||||
("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
|
||||
)
|
||||
p.connect()
|
||||
return p
|
||||
|
||||
|
||||
class SSLSpoofModeTest(ProxTestBase):
|
||||
ssl = True
|
||||
|
||||
@classmethod
|
||||
def get_proxy_config(cls):
|
||||
d = ProxTestBase.get_proxy_config()
|
||||
d["upstream_server"] = None
|
||||
d["mode"] = "sslspoof"
|
||||
d["spoofed_ssl_port"] = 443
|
||||
return d
|
||||
|
||||
def pathoc(self, sni=None):
|
||||
"""
|
||||
Returns a connected Pathoc instance.
|
||||
"""
|
||||
p = libpathod.pathoc.Pathoc(
|
||||
("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
|
||||
)
|
||||
p.connect()
|
||||
return p
|
||||
|
||||
|
||||
class ChainProxTest(ProxTestBase):
|
||||
"""
|
||||
Chain three instances of mitmproxy in a row to test upstream mode.
|
||||
|
|
Loading…
Reference in New Issue