Skip to content

Commit 6b70350

Browse files
committed
Reject open redirection in the console proxy
Our console proxies (novnc, serial, spice) run in a websockify server whose request handler inherits from the python standard SimpleHTTPRequestHandler. There is a known issue [1] in the SimpleHTTPRequestHandler which allows open redirects by way of URLs in the following format: http://vncproxy.my.domain.com//example.com/%2F.. which if visited, will redirect a user to example.com. We can intercept a request and reject requests that pass a redirection URL beginning with "//" by implementing the SimpleHTTPRequestHandler.send_head() method containing the vulnerability to reject such requests with a 400 Bad Request. This code is copied from a patch suggested in one of the issue comments [2]. Closes-Bug: #1927677 [1] https://bugs.python.org/issue32084 [2] https://bugs.python.org/issue32084#msg306545 Conflicts: nova/console/websocketproxy.py nova/tests/unit/console/test_websocketproxy.py NOTE(melwitt): The conflicts are because the following changes are not in Victoria: Ib2c406327fef2fb4868d8050fc476a7d17706e23 (Remove six.moves) I58b0382c86d4ef798572edb63d311e0e3e6937bb (Refactor and rename test_tcp_rst_no_compute_rpcapi) Change-Id: Ie36401c782f023d1d5f2623732619105dc2cfa24 (cherry picked from commit 781612b) (cherry picked from commit 4709256)
1 parent e954a56 commit 6b70350

File tree

3 files changed

+76
-0
lines changed

3 files changed

+76
-0
lines changed

nova/console/websocketproxy.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
'''
2020

2121
import copy
22+
from http import HTTPStatus
23+
import os
2224
import socket
2325
import sys
2426

@@ -281,6 +283,27 @@ def new_websocket_client(self):
281283
def socket(self, *args, **kwargs):
282284
return websockifyserver.WebSockifyServer.socket(*args, **kwargs)
283285

286+
def send_head(self):
287+
# This code is copied from this example patch:
288+
# https://bugs.python.org/issue32084#msg306545
289+
path = self.translate_path(self.path)
290+
if os.path.isdir(path):
291+
parts = urlparse.urlsplit(self.path)
292+
if not parts.path.endswith('/'):
293+
# redirect browser - doing basically what apache does
294+
new_parts = (parts[0], parts[1], parts[2] + '/',
295+
parts[3], parts[4])
296+
new_url = urlparse.urlunsplit(new_parts)
297+
298+
# Browsers interpret "Location: //uri" as an absolute URI
299+
# like "http://URI"
300+
if new_url.startswith('//'):
301+
self.send_error(HTTPStatus.BAD_REQUEST,
302+
"URI must not start with //")
303+
return None
304+
305+
return super(NovaProxyRequestHandler, self).send_head()
306+
284307

285308
class NovaWebSocketProxy(websockify.WebSocketProxy):
286309
def __init__(self, *args, **kwargs):

nova/tests/unit/console/test_websocketproxy.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,40 @@ def test_tcp_rst_no_compute_rpcapi(self):
626626
self.wh.server.top_new_client(conn, address)
627627
self.assertIsNone(self.wh._compute_rpcapi)
628628

629+
def test_reject_open_redirect(self):
630+
# This will test the behavior when an attempt is made to cause an open
631+
# redirect. It should be rejected.
632+
mock_req = mock.MagicMock()
633+
mock_req.makefile().readline.side_effect = [
634+
b'GET //example.com/%2F.. HTTP/1.1\r\n',
635+
b''
636+
]
637+
638+
# Collect the response data to verify at the end. The
639+
# SimpleHTTPRequestHandler writes the response data by calling the
640+
# request socket sendall() method.
641+
self.data = b''
642+
643+
def fake_sendall(data):
644+
self.data += data
645+
646+
mock_req.sendall.side_effect = fake_sendall
647+
648+
client_addr = ('8.8.8.8', 54321)
649+
mock_server = mock.MagicMock()
650+
# This specifies that the server will be able to handle requests other
651+
# than only websockets.
652+
mock_server.only_upgrade = False
653+
654+
# Constructing a handler will process the mock_req request passed in.
655+
websocketproxy.NovaProxyRequestHandler(
656+
mock_req, client_addr, mock_server)
657+
658+
# Verify no redirect happens and instead a 400 Bad Request is returned.
659+
self.data = self.data.decode()
660+
self.assertIn('Error code: 400', self.data)
661+
self.assertIn('Message: URI must not start with //', self.data)
662+
629663
@mock.patch('websockify.websocketproxy.select_ssl_version')
630664
def test_ssl_min_version_is_not_set(self, mock_select_ssl):
631665
websocketproxy.NovaWebSocketProxy()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
security:
3+
- |
4+
A vulnerability in the console proxies (novnc, serial, spice) that allowed
5+
open redirection has been `patched`_. The novnc, serial, and spice console
6+
proxies are implemented as websockify servers and the request handler
7+
inherits from the python standard SimpleHTTPRequestHandler. There is a
8+
`known issue`_ in the SimpleHTTPRequestHandler which allows open redirects
9+
by way of URLs in the following format::
10+
11+
http://vncproxy.my.domain.com//example.com/%2F..
12+
13+
which if visited, will redirect a user to example.com.
14+
15+
The novnc, serial, and spice console proxies will now reject requests that
16+
pass a redirection URL beginning with "//" with a 400 Bad Request.
17+
18+
.. _patched: https://bugs.launchpad.net/nova/+bug/1927677
19+
.. _known issue: https://bugs.python.org/issue32084

0 commit comments

Comments
 (0)