Browse Source

Support cross origin connect

pull/58/head
Sheng 6 years ago
parent
commit
713ae1e8f1
  1. 27
      tests/test_app.py
  2. 51
      webssh/handler.py
  3. 31
      webssh/static/js/main.js

27
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'])

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

31
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);
});

Loading…
Cancel
Save