always insert tls layer for inline script upgrades

This commit is contained in:
Maximilian Hils 2015-08-30 02:27:38 +02:00
parent dd7f50d64b
commit 1dd09a5509
9 changed files with 49 additions and 39 deletions

View File

@ -102,9 +102,9 @@ def parse_setheader(s):
return _parse_hook(s) return _parse_hook(s)
def parse_server_spec(url, allowed_schemes=("http", "https")): def parse_server_spec(url):
p = netlib.utils.parse_url(url) p = netlib.utils.parse_url(url)
if not p or not p[1] or p[0] not in allowed_schemes: if not p or not p[1] or p[0] not in ("http", "https"):
raise configargparse.ArgumentTypeError( raise configargparse.ArgumentTypeError(
"Invalid server specification: %s" % url "Invalid server specification: %s" % url
) )
@ -113,13 +113,6 @@ def parse_server_spec(url, allowed_schemes=("http", "https")):
return config.ServerSpec(scheme, address) return config.ServerSpec(scheme, address)
def parse_server_spec_special(url):
"""
Provides additional support for http2https and https2http schemes.
"""
return parse_server_spec(url, allowed_schemes=("http", "https", "http2https", "https2http"))
def get_common_options(options): def get_common_options(options):
stickycookie, stickyauth = None, None stickycookie, stickyauth = None, None
if options.stickycookie_filt: if options.stickycookie_filt:
@ -297,7 +290,7 @@ def proxy_modes(parser):
group.add_argument( group.add_argument(
"-R", "--reverse", "-R", "--reverse",
action="store", action="store",
type=parse_server_spec_special, type=parse_server_spec,
dest="reverse_proxy", dest="reverse_proxy",
default=None, default=None,
help=""" help="""

View File

@ -1,12 +1,11 @@
from __future__ import (absolute_import, print_function, division) from __future__ import (absolute_import, print_function, division)
from .layer import Layer, ServerConnectionMixin from .layer import Layer, ServerConnectionMixin
from .http import Http1Layer
class HttpProxy(Layer, ServerConnectionMixin): class HttpProxy(Layer, ServerConnectionMixin):
def __call__(self): def __call__(self):
layer = Http1Layer(self, "regular") layer = self.ctx.next_layer(self)
try: try:
layer() layer()
finally: finally:
@ -19,7 +18,7 @@ class HttpUpstreamProxy(Layer, ServerConnectionMixin):
super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address) super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address)
def __call__(self): def __call__(self):
layer = Http1Layer(self, "upstream") layer = self.ctx.next_layer(self)
try: try:
layer() layer()
finally: finally:

View File

@ -5,16 +5,12 @@ from .tls import TlsLayer
class ReverseProxy(Layer, ServerConnectionMixin): class ReverseProxy(Layer, ServerConnectionMixin):
def __init__(self, ctx, server_address, client_tls, server_tls): def __init__(self, ctx, server_address, server_tls):
super(ReverseProxy, self).__init__(ctx, server_address=server_address) super(ReverseProxy, self).__init__(ctx, server_address=server_address)
self._client_tls = client_tls self.server_tls = server_tls
self._server_tls = server_tls
def __call__(self): def __call__(self):
# Always use a TLS layer here; if someone changes the scheme, there needs to be a layer = self.ctx.next_layer(self)
# TLS layer underneath.
layer = TlsLayer(self, self._client_tls, self._server_tls)
try: try:
layer() layer()
finally: finally:

View File

@ -6,7 +6,9 @@ from netlib.http.http2 import HTTP2Protocol
from .rawtcp import RawTcpLayer from .rawtcp import RawTcpLayer
from .tls import TlsLayer, is_tls_record_magic from .tls import TlsLayer, is_tls_record_magic
from .http import Http1Layer, Http2Layer from .http import Http1Layer, Http2Layer
from .layer import ServerConnectionMixin
from .http_proxy import HttpProxy, HttpUpstreamProxy
from .reverse_proxy import ReverseProxy
class RootContext(object): class RootContext(object):
""" """
@ -34,18 +36,33 @@ class RootContext(object):
if self.config.check_ignore(top_layer.server_conn.address): if self.config.check_ignore(top_layer.server_conn.address):
return RawTcpLayer(top_layer, logging=False) return RawTcpLayer(top_layer, logging=False)
# 2. Check for TLS
# TLS ClientHello magic, works for SSLv3, TLSv1.0, TLSv1.1, TLSv1.2
# http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello
d = top_layer.client_conn.rfile.peek(3) d = top_layer.client_conn.rfile.peek(3)
if is_tls_record_magic(d): client_tls = is_tls_record_magic(d)
# 2. Always insert a TLS layer, even if there's neither client nor server tls.
# An inline script may upgrade from http to https,
# in which case we need some form of TLS layer.
if isinstance(top_layer, ReverseProxy):
return TlsLayer(top_layer, client_tls, top_layer.server_tls)
if isinstance(top_layer, ServerConnectionMixin):
return TlsLayer(top_layer, client_tls, client_tls)
# 3. In Http Proxy mode and Upstream Proxy mode, the next layer is fixed.
if isinstance(top_layer, TlsLayer):
if isinstance(top_layer.ctx, HttpProxy):
return Http1Layer(top_layer, "regular")
if isinstance(top_layer.ctx, HttpUpstreamProxy):
return Http1Layer(top_layer, "upstream")
# 4. Check for other TLS cases (e.g. after CONNECT).
if client_tls:
return TlsLayer(top_layer, True, True) return TlsLayer(top_layer, True, True)
# 3. Check for --tcp # 4. Check for --tcp
if self.config.check_tcp(top_layer.server_conn.address): if self.config.check_tcp(top_layer.server_conn.address):
return RawTcpLayer(top_layer) return RawTcpLayer(top_layer)
# 4. Check for TLS ALPN (HTTP1/HTTP2) # 5. Check for TLS ALPN (HTTP1/HTTP2)
if isinstance(top_layer, TlsLayer): if isinstance(top_layer, TlsLayer):
alpn = top_layer.client_conn.get_alpn_proto_negotiated() alpn = top_layer.client_conn.get_alpn_proto_negotiated()
if alpn == HTTP2Protocol.ALPN_PROTO_H2: if alpn == HTTP2Protocol.ALPN_PROTO_H2:
@ -53,7 +70,7 @@ class RootContext(object):
if alpn == HTTP1Protocol.ALPN_PROTO_HTTP1: if alpn == HTTP1Protocol.ALPN_PROTO_HTTP1:
return Http1Layer(top_layer, 'transparent') return Http1Layer(top_layer, 'transparent')
# 5. Assume HTTP1 by default # 6. Assume HTTP1 by default
return Http1Layer(top_layer, 'transparent') return Http1Layer(top_layer, 'transparent')
# In a future version, we want to implement TCP passthrough as the last fallback, # In a future version, we want to implement TCP passthrough as the last fallback,

View File

@ -18,6 +18,9 @@ def is_tls_record_magic(d):
False, otherwise. False, otherwise.
""" """
d = d[:3] d = d[:3]
# TLS ClientHello magic, works for SSLv3, TLSv1.0, TLSv1.1, TLSv1.2
# http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html#client-hello
return ( return (
len(d) == 3 and len(d) == 3 and
d[0] == '\x16' and d[0] == '\x16' and
@ -73,6 +76,16 @@ class TlsLayer(Layer):
layer = self.ctx.next_layer(self) layer = self.ctx.next_layer(self)
layer() layer()
def __repr__(self):
if self._client_tls and self._server_tls:
return "TlsLayer(client and server)"
elif self._client_tls:
return "TlsLayer(client)"
elif self._server_tls:
return "TlsLayer(server)"
else:
return "TlsLayer(inactive)"
def _get_client_hello(self): def _get_client_hello(self):
""" """
Peek into the socket and read all records that contain the initial client hello message. Peek into the socket and read all records that contain the initial client hello message.

View File

@ -24,6 +24,8 @@ class HostMatcher(object):
self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns] self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns]
def __call__(self, address): def __call__(self, address):
if not address:
return False
address = tcp.Address.wrap(address) address = tcp.Address.wrap(address)
host = "%s:%s" % (address.host, address.port) host = "%s:%s" % (address.host, address.port)
if any(rex.search(host) for rex in self.regexes): if any(rex.search(host) for rex in self.regexes):

View File

@ -86,12 +86,10 @@ class ConnectionHandler(object):
elif mode == "transparent": elif mode == "transparent":
return protocol2.TransparentProxy(root_context) return protocol2.TransparentProxy(root_context)
elif mode == "reverse": elif mode == "reverse":
client_tls = self.config.upstream_server.scheme.startswith("https") server_tls = self.config.upstream_server.scheme == "https"
server_tls = self.config.upstream_server.scheme.endswith("https")
return protocol2.ReverseProxy( return protocol2.ReverseProxy(
root_context, root_context,
self.config.upstream_server.address, self.config.upstream_server.address,
client_tls,
server_tls server_tls
) )
elif mode == "socks5": elif mode == "socks5":

View File

@ -43,10 +43,6 @@ def test_parse_server_spec():
"http://foo.com") == ("http", ("foo.com", 80)) "http://foo.com") == ("http", ("foo.com", 80))
assert cmdline.parse_server_spec( assert cmdline.parse_server_spec(
"https://foo.com") == ("https", ("foo.com", 443)) "https://foo.com") == ("https", ("foo.com", 443))
assert cmdline.parse_server_spec_special(
"https2http://foo.com") == ("https2http", ("foo.com", 80))
assert cmdline.parse_server_spec_special(
"http2https://foo.com") == ("http2https", ("foo.com", 443))
tutils.raises( tutils.raises(
"Invalid server specification", "Invalid server specification",
cmdline.parse_server_spec, cmdline.parse_server_spec,

View File

@ -490,10 +490,6 @@ class TestHttps2Http(tservers.ReverseProxTest):
assert p.request("get:'/p/200'").status_code == 200 assert p.request("get:'/p/200'").status_code == 200
assert all("Error in handle_sni" not in msg for msg in self.proxy.log) assert all("Error in handle_sni" not in msg for msg in self.proxy.log)
def test_http(self):
p = self.pathoc(ssl=False)
assert p.request("get:'/p/200'").status_code == 502
class TestTransparent(tservers.TransparentProxTest, CommonMixin, TcpMixin): class TestTransparent(tservers.TransparentProxTest, CommonMixin, TcpMixin):
ssl = False ssl = False