Merge pull request #638 from iroiro123/http-transparent

HTTP Transparent Proxy
This commit is contained in:
Aldo Cortesi 2015-06-23 09:40:25 +12:00
commit e2069d52a8
8 changed files with 199 additions and 5 deletions

View File

@ -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",

View File

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

View File

@ -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
)

View File

@ -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"

View File

@ -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")

View File

@ -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):

View File

@ -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):

View File

@ -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.