From ab738c2e6532d592c71d7b39c7143761e9c8b37c Mon Sep 17 00:00:00 2001 From: Dave McCowan Date: Mon, 26 Jan 2015 00:34:30 -0500 Subject: [PATCH] Websocket Proxy should verify Origin header If the Origin HTTP header passed in the WebSocket handshake does not match the host, this could indicate an attempt at a cross-site script attack. This commit adds a check to verify the origin matches the host. Closes-Bug: 1409142 --- nova/console/websocketproxy.py | 10 +++++++ nova/tests/console/test_websocketproxy.py | 46 +++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/nova/console/websocketproxy.py b/nova/console/websocketproxy.py index ef684f5..06f194f 100644 --- a/nova/console/websocketproxy.py +++ b/nova/console/websocketproxy.py @@ -26,6 +26,7 @@ import websockify from nova.consoleauth import rpcapi as consoleauth_rpcapi from nova import context +from nova import exception from nova.i18n import _ from nova.openstack.common import log as logging @@ -40,6 +41,15 @@ class NovaProxyRequestHandlerBase(object): from eventlet import hubs hubs.use_hub() + # Verify Origin header to prevent XSS attack + verify_host = self.headers.getheader('Host') + origin_url = self.headers.getheader('Origin') + origin_parsed = urlparse.urlparse(origin_url) + verify_origin = origin_parsed.netloc + if verify_host != verify_origin: + detail = _("Origin header does not match this host.") + raise exception.ValidationError(detail=detail) + # The nova expected behavior is to have token # passed to the method GET of the request query = urlparse.urlparse(self.path).query diff --git a/nova/tests/console/test_websocketproxy.py b/nova/tests/console/test_websocketproxy.py index 1e51a4d..81b0ac5 100644 --- a/nova/tests/console/test_websocketproxy.py +++ b/nova/tests/console/test_websocketproxy.py @@ -18,6 +18,7 @@ import mock from nova.console import websocketproxy +from nova import exception from nova import test @@ -32,6 +33,36 @@ class NovaProxyRequestHandlerBaseTestCase(test.TestCase): self.wh.do_proxy = mock.MagicMock() self.wh.headers = mock.MagicMock() + def _fake_getheader(self, header): + if header == 'cookie': + return 'token="123-456-789"' + elif header == 'Origin': + return 'https://example.net:6080' + elif header == 'Host': + return 'example.net:6080' + else: + return + + def _fake_getheader_bad_token(self, header): + if header == 'cookie': + return 'token="XXX"' + elif header == 'Origin': + return 'https://example.net:6080' + elif header == 'Host': + return 'example.net:6080' + else: + return + + def _fake_getheader_bad_origin(self, header): + if header == 'cookie': + return 'token="123-456-789"' + elif header == 'Origin': + return 'https://bad-origin-example.net:6080' + elif header == 'Host': + return 'example.net:6080' + else: + return + @mock.patch('nova.consoleauth.rpcapi.ConsoleAuthAPI.check_token') def test_new_websocket_client(self, check_token): check_token.return_value = { @@ -40,6 +71,7 @@ class NovaProxyRequestHandlerBaseTestCase(test.TestCase): } self.wh.socket.return_value = '' self.wh.path = "ws://127.0.0.1/?token=123-456-789" + self.wh.headers.getheader = self._fake_getheader self.wh.new_websocket_client() @@ -52,6 +84,7 @@ class NovaProxyRequestHandlerBaseTestCase(test.TestCase): check_token.return_value = False self.wh.path = "ws://127.0.0.1/?token=XXX" + self.wh.headers.getheader = self._fake_getheader self.assertRaises(Exception, self.wh.new_websocket_client) # noqa check_token.assert_called_with(mock.ANY, token="XXX") @@ -64,7 +97,7 @@ class NovaProxyRequestHandlerBaseTestCase(test.TestCase): } self.wh.socket.return_value = '' self.wh.path = "http://127.0.0.1/" - self.wh.headers.getheader.return_value = "token=123-456-789" + self.wh.headers.getheader = self._fake_getheader self.wh.new_websocket_client() @@ -77,7 +110,16 @@ class NovaProxyRequestHandlerBaseTestCase(test.TestCase): check_token.return_value = False self.wh.path = "http://127.0.0.1/" - self.wh.headers.getheader.return_value = "token=XXX" + self.wh.headers.getheader = self._fake_getheader_bad_token self.assertRaises(Exception, self.wh.new_websocket_client) # noqa check_token.assert_called_with(mock.ANY, token="XXX") + + @mock.patch('nova.consoleauth.rpcapi.ConsoleAuthAPI.check_token') + def test_new_websocket_client_novnc_header_invalid(self, check_token): + check_token.return_value = False + + self.wh.path = "http://127.0.0.1/" + self.wh.headers.getheader = self._fake_getheader_bad_origin + + self.assertRaises(exception.ValidationError, self.wh.new_websocket_client) # noqa -- 1.9.1