Skip to content

Commit 2e1c56b

Browse files
committed
Validate redirect target in TrailingSlashHandler
Fixes open redirect vulnerability GHSA-c7vm-f5p4-8fqh
1 parent d8308e1 commit 2e1c56b

File tree

2 files changed

+28
-4
lines changed

2 files changed

+28
-4
lines changed

notebook/base/handlers.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -854,13 +854,18 @@ def get(self):
854854

855855
class TrailingSlashHandler(web.RequestHandler):
856856
"""Simple redirect handler that strips trailing slashes
857-
857+
858858
This should be the first, highest priority handler.
859859
"""
860-
860+
861861
def get(self):
862-
self.redirect(self.request.uri.rstrip('/'))
863-
862+
path, *rest = self.request.uri.partition("?")
863+
# trim trailing *and* leading /
864+
# to avoid misinterpreting repeated '//'
865+
path = "/" + path.strip("/")
866+
new_uri = "".join([path, *rest])
867+
self.redirect(new_uri)
868+
864869
post = put = get
865870

866871

@@ -911,6 +916,7 @@ def get(self):
911916
url = sep.join([self._url, self.request.query])
912917
self.redirect(url, permanent=self._permanent)
913918

919+
914920
class PrometheusMetricsHandler(IPythonHandler):
915921
"""
916922
Return prometheus metrics for this notebook server

notebook/tests/test_paths.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
from nose.tools import assert_regex, assert_not_regex
44

55
from notebook.base.handlers import path_regex
6+
from notebook.utils import url_path_join
7+
from .launchnotebook import NotebookTestBase
68

79
# build regexps that tornado uses:
810
path_pat = re.compile('^' + '/x%s' % path_regex + '$')
911

12+
1013
def test_path_regex():
1114
for path in (
1215
'/x',
@@ -30,3 +33,18 @@ def test_path_regex_bad():
3033
'/y/x/foo',
3134
):
3235
assert_not_regex(path, path_pat)
36+
37+
38+
class RedirectTestCase(NotebookTestBase):
39+
def test_trailing_slash(self):
40+
for uri, expected in (
41+
("/notebooks/mynotebook/", "/notebooks/mynotebook"),
42+
("////foo///", "/foo"),
43+
("//example.com/", "/example.com"),
44+
("/has/param/?hasparam=true", "/has/param?hasparam=true"),
45+
):
46+
r = self.request("GET", uri, allow_redirects=False)
47+
print(uri, expected)
48+
assert r.status_code == 302
49+
assert "Location" in r.headers
50+
assert r.headers["Location"] == url_path_join(self.url_prefix, expected)

0 commit comments

Comments
 (0)