From 7b552d862de12536f35361bd6ee9748d710ce4c7 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Tue, 21 Jan 2014 12:55:52 -0600 Subject: [PATCH 1/9] Add Origin Checking. --- IPython/html/base/zmqhandlers.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 0d4c95af6..c651dbbfb 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -16,6 +16,11 @@ Authors: # Imports #----------------------------------------------------------------------------- +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + try: from http.cookies import SimpleCookie # Py 3 except ImportError: @@ -37,6 +42,29 @@ from .handlers import IPythonHandler #----------------------------------------------------------------------------- class ZMQStreamHandler(websocket.WebSocketHandler): + + def check_origin(self): + """Check origin from headers.""" + origin_header = self.request.headers["Origin"] + host = self.request.headers["Host"] + + parsed_origin = urlparse(origin_header) + origin = parsed_origin.netloc + + # Check to see that origin matches host directly, including ports + if origin != host: + self.log.critical("Cross Origin WebSocket Attempt.", exc_info=True) + raise web.HTTPError(404) + + + def _execute(self, transforms, *args, **kwargs): + """Wrap all calls to make sure origin gets checked.""" + + # Check to see that origin matches host directly, including ports + self.check_origin() + + # Pass on the rest of the handling by the WebSocketHandler + super(ZMQStreamHandler, self)._execute(transforms, *args, **kwargs) def clear_cookie(self, *args, **kwargs): """meaningless for websockets""" From 5800b1c625889f87ef728bfd1992bd31cec504b9 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Tue, 21 Jan 2014 13:23:09 -0600 Subject: [PATCH 2/9] Get rid of exc_info as there isn't an exception. --- IPython/html/base/zmqhandlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index c651dbbfb..e0b341d65 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -53,7 +53,7 @@ class ZMQStreamHandler(websocket.WebSocketHandler): # Check to see that origin matches host directly, including ports if origin != host: - self.log.critical("Cross Origin WebSocket Attempt.", exc_info=True) + self.log.critical("Cross Origin WebSocket Attempt.") raise web.HTTPError(404) From 104275ab73cf0c975278d5b1c7fbbd89e302c441 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Tue, 21 Jan 2014 14:47:58 -0600 Subject: [PATCH 3/9] Indicate Py3 vs. Py2 codepath. --- IPython/html/base/zmqhandlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index e0b341d65..46ac22d08 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -17,9 +17,9 @@ Authors: #----------------------------------------------------------------------------- try: - from urllib.parse import urlparse + from urllib.parse import urlparse # Py 3 except ImportError: - from urlparse import urlparse + from urlparse import urlparse # Py 2 try: from http.cookies import SimpleCookie # Py 3 From 345de4e674d7dcc21941f529cbc285c4a07e810e Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Tue, 21 Jan 2014 21:08:24 -0600 Subject: [PATCH 4/9] Use *args, **kwargs, log.warn --- IPython/html/base/zmqhandlers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 46ac22d08..362c58692 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -53,18 +53,18 @@ class ZMQStreamHandler(websocket.WebSocketHandler): # Check to see that origin matches host directly, including ports if origin != host: - self.log.critical("Cross Origin WebSocket Attempt.") + self.log.warn("Cross Origin WebSocket Attempt.") raise web.HTTPError(404) - def _execute(self, transforms, *args, **kwargs): + def _execute(self, *args, **kwargs): """Wrap all calls to make sure origin gets checked.""" # Check to see that origin matches host directly, including ports self.check_origin() # Pass on the rest of the handling by the WebSocketHandler - super(ZMQStreamHandler, self)._execute(transforms, *args, **kwargs) + super(ZMQStreamHandler, self)._execute(*args, **kwargs) def clear_cookie(self, *args, **kwargs): """meaningless for websockets""" From b12f002b3533a5f4c4bdc157b4ad948359be97ac Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 22 Jan 2014 18:16:06 -0600 Subject: [PATCH 5/9] Performing check only on open. --- IPython/html/base/zmqhandlers.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 362c58692..1b741ae9c 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -43,29 +43,17 @@ from .handlers import IPythonHandler class ZMQStreamHandler(websocket.WebSocketHandler): - def check_origin(self): - """Check origin from headers.""" - origin_header = self.request.headers["Origin"] - host = self.request.headers["Host"] + def is_cross_origin(self): + """Check to see that origin and host match in the headers.""" + origin_header = self.request.headers.get("Origin") + host = self.request.headers.get("Host") parsed_origin = urlparse(origin_header) origin = parsed_origin.netloc # Check to see that origin matches host directly, including ports - if origin != host: - self.log.warn("Cross Origin WebSocket Attempt.") - raise web.HTTPError(404) - - - def _execute(self, *args, **kwargs): - """Wrap all calls to make sure origin gets checked.""" - - # Check to see that origin matches host directly, including ports - self.check_origin() + return origin != host - # Pass on the rest of the handling by the WebSocketHandler - super(ZMQStreamHandler, self)._execute(*args, **kwargs) - def clear_cookie(self, *args, **kwargs): """meaningless for websockets""" pass @@ -114,6 +102,11 @@ class ZMQStreamHandler(websocket.WebSocketHandler): class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): def open(self, kernel_id): + # Check to see that origin matches host directly, including ports + if self.is_cross_origin(): + self.log.warn("Cross Origin WebSocket Attempt.") + raise web.HTTPError(404) + self.kernel_id = cast_unicode(kernel_id, 'ascii') self.session = Session(config=self.config) self.save_on_message = self.on_message @@ -142,4 +135,4 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): if self.get_current_user() is None: self.log.warn("Couldn't authenticate WebSocket connection") raise web.HTTPError(403) - self.on_message = self.save_on_message \ No newline at end of file + self.on_message = self.save_on_message From 60ab030e528f91ce643fa937bcfc41ea2017083d Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 22 Jan 2014 18:21:02 -0600 Subject: [PATCH 6/9] Verify that headers are set, explicitly --- IPython/html/base/zmqhandlers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 1b741ae9c..6f34c96bf 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -48,6 +48,9 @@ class ZMQStreamHandler(websocket.WebSocketHandler): origin_header = self.request.headers.get("Origin") host = self.request.headers.get("Host") + if(origin_header == None or host == None): + return True + parsed_origin = urlparse(origin_header) origin = parsed_origin.netloc From 7efc751d0eccee6f136011060e59a410eb1716c2 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 22 Jan 2014 18:26:15 -0600 Subject: [PATCH 7/9] Name change to same_origin --- IPython/html/base/zmqhandlers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 6f34c96bf..e8800b7d2 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -43,19 +43,20 @@ from .handlers import IPythonHandler class ZMQStreamHandler(websocket.WebSocketHandler): - def is_cross_origin(self): + def same_origin(self): """Check to see that origin and host match in the headers.""" origin_header = self.request.headers.get("Origin") host = self.request.headers.get("Host") + # If no header is provided, assume we can't verify origin if(origin_header == None or host == None): - return True + return False parsed_origin = urlparse(origin_header) origin = parsed_origin.netloc # Check to see that origin matches host directly, including ports - return origin != host + return origin == host def clear_cookie(self, *args, **kwargs): """meaningless for websockets""" @@ -106,7 +107,7 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): def open(self, kernel_id): # Check to see that origin matches host directly, including ports - if self.is_cross_origin(): + if not self.same_origin(): self.log.warn("Cross Origin WebSocket Attempt.") raise web.HTTPError(404) From e06f501cd6701f2ed6961c3c55a37dff3329cd9b Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 22 Jan 2014 21:12:08 -0600 Subject: [PATCH 8/9] s/==/is/ --- IPython/html/base/zmqhandlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index e8800b7d2..6c015bda3 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -49,7 +49,7 @@ class ZMQStreamHandler(websocket.WebSocketHandler): host = self.request.headers.get("Host") # If no header is provided, assume we can't verify origin - if(origin_header == None or host == None): + if(origin_header is None or host is None): return False parsed_origin = urlparse(origin_header) From ddc9340a6a92eb75b9bf64dc8f0811b3312550ec Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Fri, 24 Jan 2014 00:19:59 -0600 Subject: [PATCH 9/9] Handle variations of name for origin --- IPython/html/base/zmqhandlers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index 6c015bda3..99f432e09 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -45,7 +45,15 @@ class ZMQStreamHandler(websocket.WebSocketHandler): def same_origin(self): """Check to see that origin and host match in the headers.""" - origin_header = self.request.headers.get("Origin") + + # The difference between version 8 and 13 is that in 8 the + # client sends a "Sec-Websocket-Origin" header and in 13 it's + # simply "Origin". + if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"): + origin_header = self.request.headers.get("Sec-Websocket-Origin") + else: + origin_header = self.request.headers.get("Origin") + host = self.request.headers.get("Host") # If no header is provided, assume we can't verify origin