From 713ae1e8f19a39507c33d444bb99e9414c8bbe99 Mon Sep 17 00:00:00 2001 From: Sheng Date: Sat, 19 Jan 2019 19:19:45 +0800 Subject: [PATCH] Support cross origin connect --- tests/test_app.py | 27 +++++++++++++++++++++ webssh/handler.py | 51 +++++++++++++++++++++++----------------- webssh/static/js/main.js | 31 ++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index 9074468..a3f016b 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -449,6 +449,7 @@ class OtherTestBase(AsyncHTTPTestCase): syshostfile = '' tdstream = '' maxconn = 20 + origin = 'same' body = { 'hostname': '127.0.0.1', 'port': '', @@ -467,6 +468,7 @@ class OtherTestBase(AsyncHTTPTestCase): options.syshostfile = self.syshostfile options.tdstream = self.tdstream options.maxconn = self.maxconn + options.origin = self.origin app = make_app(make_handlers(loop, options), get_app_settings(options)) return app @@ -716,3 +718,28 @@ class TestAppWithTooManyConnections(OtherTestBase): self.assertEqual(data['status'], 'Too many connections.') ws.close() + + +class TestAppWithCrossOriginConnect(OtherTestBase): + + origin = 'http://www.example.com' + + @tornado.testing.gen_test + def test_app_with_cross_orgin_connect(self): + url = self.get_url('/') + client = self.get_http_client() + body = urlencode(dict(self.body, username='foo', _origin='localhost')) + response = yield client.fetch(url, method='POST', body=body, + headers=self.headers) + data = json.loads(to_str(response.body)) + self.assertIsNone(data['id']) + self.assertIsNone(data['encoding']) + self.assertIn('Cross origin frame', data['status']) + + body = urlencode(dict(self.body, username='foo', _origin=self.origin)) + response = yield client.fetch(url, method='POST', body=body, + headers=self.headers) + data = json.loads(to_str(response.body)) + self.assertIsNotNone(data['id']) + self.assertIsNotNone(data['encoding']) + self.assertIsNone(data['status']) diff --git a/webssh/handler.py b/webssh/handler.py index 8d1d5d2..e2d4ee4 100644 --- a/webssh/handler.py +++ b/webssh/handler.py @@ -76,6 +76,28 @@ class MixinHandler(object): else: self.context = context + def check_origin(self, origin): + if self.origin_policy == '*': + return True + + parsed_origin = urlparse(origin) + netloc = parsed_origin.netloc.lower() + logging.debug('netloc: {}'.format(netloc)) + + host = self.request.headers.get('Host') + logging.debug('host: {}'.format(host)) + + if netloc == host: + return True + + if self.origin_policy == 'same': + return False + elif self.origin_policy == 'primary': + return is_same_primary_domain(netloc.rsplit(':', 1)[0], + host.rsplit(':', 1)[0]) + else: + return origin in self.origin_policy + def is_forbidden(self, context, hostname): ip = context.address[0] lst = context.trusted_downstream @@ -340,6 +362,13 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler): if len(clients.get(self.src_addr[0], {})) >= options.maxconn: raise tornado.web.HTTPError(403, 'Too many connections.') + origin = self.get_argument('_origin', u'') + if origin: + if not self.check_origin(origin): + raise tornado.web.HTTPError( + 403, 'Cross origin frame operation is not allowed.' + ) + future = Future() t = threading.Thread(target=self.ssh_connect_wrapped, args=(future,)) t.setDaemon(True) @@ -364,28 +393,6 @@ class WsockHandler(MixinHandler, tornado.websocket.WebSocketHandler): super(WsockHandler, self).initialize(loop) self.worker_ref = None - def check_origin(self, origin): - if self.origin_policy == '*': - return True - - parsed_origin = urlparse(origin) - netloc = parsed_origin.netloc.lower() - logging.debug('netloc: {}'.format(netloc)) - - host = self.request.headers.get('Host') - logging.debug('host: {}'.format(host)) - - if netloc == host: - return True - - if self.origin_policy == 'same': - return False - elif self.origin_policy == 'primary': - return is_same_primary_domain(netloc.rsplit(':', 1)[0], - host.rsplit(':', 1)[0]) - else: - return origin in self.origin_policy - def open(self): self.src_addr = self.get_client_addr() logging.info('Connected from {}:{}'.format(*self.src_addr)) diff --git a/webssh/static/js/main.js b/webssh/static/js/main.js index 43db88a..6482736 100644 --- a/webssh/static/js/main.js +++ b/webssh/static/js/main.js @@ -50,6 +50,7 @@ jQuery(function($){ messages = {1: 'This client is connecting ...', 2: 'This client is already connnected.'}, key_max_size = 16384, fields = ['hostname', 'port', 'username'], + event_origin, hostname_tester = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/; @@ -517,6 +518,9 @@ jQuery(function($){ } data._xsrf = _xsrf.value; + if (event_origin) { + data._origin = event_origin; + } $.ajax({ url: url, @@ -569,4 +573,31 @@ jQuery(function($){ connect(); }); + + function cross_origin_connect(event) + { + console.log(event.origin); + var prop = 'connect', + args; + + try { + args = JSON.parse(event.data); + } catch (SyntaxError) { + args = event.data.split('|'); + } + + if (!Array.isArray(args)) { + args = [args]; + } + + try { + event_origin = event.origin; + wssh[prop].apply(wssh, args); + } finally { + event_origin = undefined; + } + } + + window.addEventListener('message', cross_origin_connect, false); + });