VYPR
Low severityNVD Advisory· Published Feb 19, 2020· Updated Aug 6, 2024

CVE-2015-9543

CVE-2015-9543

Description

An issue was discovered in OpenStack Nova before 18.2.4, 19.x before 19.1.0, and 20.x before 20.1.0. It can leak consoleauth tokens into log files. An attacker with read access to the service's logs may obtain tokens used for console access. All Nova setups using novncproxy are affected. This is related to NovaProxyRequestHandlerBase.new_websocket_client in console/websocketproxy.py.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OpenStack Nova consoles proxy leaks consoleauth tokens into logs, enabling attackers with log read access to obtain VNC console tokens.

Vulnerability

An issue in OpenStack Nova's console proxy (novncproxy) causes console authentication tokens to be written into log files at INFO level [1]. The flaw resides in NovaProxyRequestHandlerBase.new_websocket_client in console/websocketproxy.py.

Exploitation

An attacker with read access to the service's logs can extract these tokens [2]. The tokens are logged both by nova-consoleauth and nova-novncproxy [3]. No additional authentication is required beyond log access.

Impact

With a valid token, an attacker can access the corresponding instance's VNC console, potentially leading to data exposure or denial of service via console actions (e.g., Ctrl+Alt+Del) [3].

Mitigation

The issue is fixed in Nova versions 18.2.4, 19.1.0, and 20.1.0 [1]. Patches mask the token in log messages [4]. Users should upgrade or apply the relevant patches.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
NovaPyPI
< 18.2.418.2.4
NovaPyPI
>= 19.0.0, < 19.1.019.1.0
NovaPyPI
>= 20.0.0, < 20.1.020.1.0

Affected products

2

Patches

3
08f1f914cc21

Mask the token used to allow access to consoles

https://github.com/openstack/novaBalazs GibizerAug 23, 2019via ghsa
4 files changed · +32 7
  • nova/consoleauth/manager.py+4 5 modified
    @@ -100,9 +100,8 @@ def authorize_console(self, context, token, console_type, host, port,
     
             self.mc_instance.set(instance_uuid.encode('UTF-8'),
                                  jsonutils.dumps(tokens))
    -
    -        LOG.info("Received Token: %(token)s, %(token_dict)s",
    -                 {'token': token, 'token_dict': token_dict})
    +        token_dict['token'] = '***'
    +        LOG.info("Received Token: %(token_dict)s", {'token_dict': token_dict})
     
         def _validate_token(self, context, token):
             instance_uuid = token['instance_uuid']
    @@ -130,8 +129,8 @@ def _validate_token(self, context, token):
         def check_token(self, context, token):
             token_str = self.mc.get(token.encode('UTF-8'))
             token_valid = (token_str is not None)
    -        LOG.info("Checking Token: %(token)s, %(token_valid)s",
    -                 {'token': token, 'token_valid': token_valid})
    +        LOG.info("Checking that token is known: %(token_valid)s",
    +                 {'token_valid': token_valid})
             if token_valid:
                 token = jsonutils.loads(token_str)
                 if self._validate_token(context, token):
    
  • nova/console/websocketproxy.py+5 1 modified
    @@ -18,6 +18,7 @@
     Leverages websockify.py by Joel Martin
     '''
     
    +import copy
     import socket
     import sys
     
    @@ -248,7 +249,10 @@ def new_websocket_client(self):
                     detail = _("Origin header protocol does not match this host.")
                     raise exception.ValidationError(detail=detail)
     
    -        self.msg(_('connect info: %s'), str(connect_info))
    +        sanitized_info = copy.copy(connect_info)
    +        sanitized_info['token'] = '***'
    +        self.msg(_('connect info: %s'), sanitized_info)
    +
             host = connect_info['host']
             port = int(connect_info['port'])
     
    
  • nova/tests/unit/consoleauth/test_consoleauth.py+20 1 modified
    @@ -88,6 +88,17 @@ def fake_validate_console_port(self, ctxt, instance,
             self.stub_out(self.rpcapi + 'validate_console_port',
                           fake_validate_console_port)
     
    +    @mock.patch('nova.consoleauth.manager.LOG.info')
    +    def test_authorize_does_not_log_token_secrete(self, mock_info):
    +        self.manager_api.authorize_console(
    +            self.context, 'secret', 'novnc', '127.0.0.1', '8080', 'host',
    +            self.instance_uuid)
    +
    +        mock_info.assert_called_once_with(
    +            'Received Token: %(token_dict)s', test.MatchType(dict))
    +        self.assertEqual(
    +            '***', mock_info.mock_calls[0][1][1]['token_dict']['token'])
    +
         @mock.patch('nova.objects.instance.Instance.get_by_uuid')
         def test_multiple_tokens_for_instance(self, mock_get):
             mock_get.return_value = None
    @@ -139,8 +150,9 @@ def test_delete_tokens_for_instance_no_tokens(self):
                 mock_delete.assert_called_once_with(
                     self.instance_uuid.encode('UTF-8'))
     
    +    @mock.patch('nova.consoleauth.manager.LOG.info')
         @mock.patch('nova.objects.instance.Instance.get_by_uuid')
    -    def test_wrong_token_has_port(self, mock_get):
    +    def test_wrong_token_has_port(self, mock_get, mock_log):
             mock_get.return_value = None
     
             token = u'mytok'
    @@ -151,6 +163,13 @@ def test_wrong_token_has_port(self, mock_get):
                                             '127.0.0.1', '8080', 'host',
                                             instance_uuid=self.instance_uuid)
             self.assertIsNone(self.manager_api.check_token(self.context, token))
    +        mock_log.assert_has_calls([
    +            mock.call(
    +                'Received Token: %(token_dict)s', mock.ANY),
    +            mock.call(
    +                'Checking that token is known: %(token_valid)s',
    +                {'token_valid': True}),
    +        ])
     
         def test_delete_expired_tokens(self):
             self.useFixture(test.TimeOverride())
    
  • nova/tests/unit/console/test_websocketproxy.py+3 0 modified
    @@ -295,6 +295,9 @@ def test_new_websocket_client(self, validate, check_port):
             validate.assert_called_with(mock.ANY, "123-456-789")
             self.wh.socket.assert_called_with('node1', 10000, connect=True)
             self.wh.do_proxy.assert_called_with('<socket>')
    +        # ensure that token is masked when logged
    +        connection_info = self.wh.msg.mock_calls[0][1][1]
    +        self.assertEqual('***', connection_info['token'])
     
         @mock.patch('nova.console.websocketproxy.NovaProxyRequestHandlerBase.'
                     '_check_console_port')
    
26d4047e17eb

Mask the token used to allow access to consoles

https://github.com/openstack/novaBalazs GibizerAug 23, 2019via ghsa
2 files changed · +8 1
  • nova/console/websocketproxy.py+5 1 modified
    @@ -18,6 +18,7 @@
     Leverages websockify.py by Joel Martin
     '''
     
    +import copy
     import socket
     import sys
     
    @@ -220,7 +221,10 @@ def new_websocket_client(self):
                     detail = _("Origin header protocol does not match this host.")
                     raise exception.ValidationError(detail=detail)
     
    -        self.msg(_('connect info: %s'), str(connect_info))
    +        sanitized_info = copy.copy(connect_info)
    +        sanitized_info.token = '***'
    +        self.msg(_('connect info: %s'), sanitized_info)
    +
             host = connect_info.host
             port = connect_info.port
     
    
  • nova/tests/unit/console/test_websocketproxy.py+3 0 modified
    @@ -219,6 +219,9 @@ def test_new_websocket_client(self, validate, check_port):
             validate.assert_called_with(mock.ANY, "123-456-789")
             self.wh.socket.assert_called_with('node1', 10000, connect=True)
             self.wh.do_proxy.assert_called_with('<socket>')
    +        # ensure that token is masked when logged
    +        connection_info = self.wh.msg.mock_calls[0][1][1]
    +        self.assertEqual('***', connection_info.token)
     
         @mock.patch('nova.console.websocketproxy.NovaProxyRequestHandlerBase.'
                     '_check_console_port')
    
d8fbf04f325f

Mask the token used to allow access to consoles

https://github.com/openstack/novaBalazs GibizerAug 23, 2019via ghsa
4 files changed · +32 7
  • nova/consoleauth/manager.py+4 5 modified
    @@ -100,9 +100,8 @@ def authorize_console(self, context, token, console_type, host, port,
     
             self.mc_instance.set(instance_uuid.encode('UTF-8'),
                                  jsonutils.dumps(tokens))
    -
    -        LOG.info("Received Token: %(token)s, %(token_dict)s",
    -                 {'token': token, 'token_dict': token_dict})
    +        token_dict['token'] = '***'
    +        LOG.info("Received Token: %(token_dict)s", {'token_dict': token_dict})
     
         def _validate_token(self, context, token):
             instance_uuid = token['instance_uuid']
    @@ -130,8 +129,8 @@ def _validate_token(self, context, token):
         def check_token(self, context, token):
             token_str = self.mc.get(token.encode('UTF-8'))
             token_valid = (token_str is not None)
    -        LOG.info("Checking Token: %(token)s, %(token_valid)s",
    -                 {'token': token, 'token_valid': token_valid})
    +        LOG.info("Checking that token is known: %(token_valid)s",
    +                 {'token_valid': token_valid})
             if token_valid:
                 token = jsonutils.loads(token_str)
                 if self._validate_token(context, token):
    
  • nova/console/websocketproxy.py+5 1 modified
    @@ -18,6 +18,7 @@
     Leverages websockify.py by Joel Martin
     '''
     
    +import copy
     import socket
     import sys
     
    @@ -248,7 +249,10 @@ def new_websocket_client(self):
                     detail = _("Origin header protocol does not match this host.")
                     raise exception.ValidationError(detail=detail)
     
    -        self.msg(_('connect info: %s'), str(connect_info))
    +        sanitized_info = copy.copy(connect_info)
    +        sanitized_info['token'] = '***'
    +        self.msg(_('connect info: %s'), sanitized_info)
    +
             host = connect_info['host']
             port = int(connect_info['port'])
     
    
  • nova/tests/unit/consoleauth/test_consoleauth.py+20 1 modified
    @@ -88,6 +88,17 @@ def fake_validate_console_port(self, ctxt, instance,
             self.stub_out(self.rpcapi + 'validate_console_port',
                           fake_validate_console_port)
     
    +    @mock.patch('nova.consoleauth.manager.LOG.info')
    +    def test_authorize_does_not_log_token_secrete(self, mock_info):
    +        self.manager_api.authorize_console(
    +            self.context, 'secret', 'novnc', '127.0.0.1', '8080', 'host',
    +            self.instance_uuid)
    +
    +        mock_info.assert_called_once_with(
    +            'Received Token: %(token_dict)s', test.MatchType(dict))
    +        self.assertEqual(
    +            '***', mock_info.mock_calls[0][1][1]['token_dict']['token'])
    +
         @mock.patch('nova.objects.instance.Instance.get_by_uuid')
         def test_multiple_tokens_for_instance(self, mock_get):
             mock_get.return_value = None
    @@ -139,8 +150,9 @@ def test_delete_tokens_for_instance_no_tokens(self):
                 mock_delete.assert_called_once_with(
                     self.instance_uuid.encode('UTF-8'))
     
    +    @mock.patch('nova.consoleauth.manager.LOG.info')
         @mock.patch('nova.objects.instance.Instance.get_by_uuid')
    -    def test_wrong_token_has_port(self, mock_get):
    +    def test_wrong_token_has_port(self, mock_get, mock_log):
             mock_get.return_value = None
     
             token = u'mytok'
    @@ -151,6 +163,13 @@ def test_wrong_token_has_port(self, mock_get):
                                             '127.0.0.1', '8080', 'host',
                                             instance_uuid=self.instance_uuid)
             self.assertIsNone(self.manager_api.check_token(self.context, token))
    +        mock_log.assert_has_calls([
    +            mock.call(
    +                'Received Token: %(token_dict)s', mock.ANY),
    +            mock.call(
    +                'Checking that token is known: %(token_valid)s',
    +                {'token_valid': True}),
    +        ])
     
         def test_delete_expired_tokens(self):
             self.useFixture(test.TimeOverride())
    
  • nova/tests/unit/console/test_websocketproxy.py+3 0 modified
    @@ -295,6 +295,9 @@ def test_new_websocket_client(self, validate, check_port):
             validate.assert_called_with(mock.ANY, "123-456-789")
             self.wh.socket.assert_called_with('node1', 10000, connect=True)
             self.wh.do_proxy.assert_called_with('<socket>')
    +        # ensure that token is masked when logged
    +        connection_info = self.wh.msg.mock_calls[0][1][1]
    +        self.assertEqual('***', connection_info['token'])
     
         @mock.patch('nova.console.websocketproxy.NovaProxyRequestHandlerBase.'
                     '_check_console_port')
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.