Skip to content

Commit

Permalink
Merge pull request #478 from jtpio/xsrf
Browse files Browse the repository at this point in the history
Tighten xsrf checks
  • Loading branch information
blink1073 committed Apr 16, 2021
2 parents efbe484 + 88e87f8 commit dc2a5b5
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 2 deletions.
64 changes: 63 additions & 1 deletion jupyter_server/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def content_security_policy(self):

def set_default_headers(self):
headers = {}
headers["X-Content-Type-Options"] = "nosniff"
headers.update(self.settings.get('headers', {}))

headers["Content-Security-Policy"] = self.content_security_policy
Expand Down Expand Up @@ -383,13 +384,69 @@ def check_origin(self, origin_to_satisfy_tornado=""):
)
return allow

def check_referer(self):
"""Check Referer for cross-site requests.
Disables requests to certain endpoints with
external or missing Referer.
If set, allow_origin settings are applied to the Referer
to whitelist specific cross-origin sites.
Used on GET for api endpoints and /files/
to block cross-site inclusion (XSSI).
"""
if self.allow_origin == "*" or self.skip_check_origin():
return True

host = self.request.headers.get("Host")
referer = self.request.headers.get("Referer")

if not host:
self.log.warning("Blocking request with no host")
return False
if not referer:
self.log.warning("Blocking request with no referer")
return False

referer_url = urlparse(referer)
referer_host = referer_url.netloc
if referer_host == host:
return True

# apply cross-origin checks to Referer:
origin = "{}://{}".format(referer_url.scheme, referer_url.netloc)
if self.allow_origin:
allow = self.allow_origin == origin
elif self.allow_origin_pat:
allow = bool(self.allow_origin_pat.match(origin))
else:
# No CORS settings, deny the request
allow = False

if not allow:
self.log.warning("Blocking Cross Origin request for %s. Referer: %s, Host: %s",
self.request.path, origin, host,
)
return allow

def check_xsrf_cookie(self):
"""Bypass xsrf cookie checks when token-authenticated"""
if self.token_authenticated or self.settings.get('disable_check_xsrf', False):
# Token-authenticated requests do not need additional XSRF-check
# Servers without authentication are vulnerable to XSRF
return
return super(JupyterHandler, self).check_xsrf_cookie()
try:
return super(JupyterHandler, self).check_xsrf_cookie()
except web.HTTPError as e:
if self.request.method in {'GET', 'HEAD'}:
# Consider Referer a sufficient cross-origin check for GET requests
if not self.check_referer():
referer = self.request.headers.get('Referer')
if referer:
msg = "Blocking Cross Origin request from {}.".format(referer)
else:
msg = "Blocking request from unknown origin"
raise web.HTTPError(403, msg)
else:
raise

def check_host(self):
"""Check the host header if remote access disallowed.
Expand Down Expand Up @@ -632,6 +689,11 @@ def content_security_policy(self):
return super(AuthenticatedFileHandler, self).content_security_policy + \
"; sandbox allow-scripts"

@web.authenticated
def head(self, path):
self.check_xsrf_cookie()
return super(AuthenticatedFileHandler, self).head(path)

@web.authenticated
def get(self, path):
if os.path.splitext(path)[1] == '.ipynb' or self.get_argument("download", False):
Expand Down
4 changes: 4 additions & 0 deletions jupyter_server/files/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ def content_security_policy(self):
@web.authenticated
def head(self, path):
self.get(path, include_body=False)
self.check_xsrf_cookie()
return self.get(path, include_body=False)

@web.authenticated
async def get(self, path, include_body=True):
# /files/ requests must originate from the same site
self.check_xsrf_cookie()
cm = self.contents_manager

if await ensure_async(cm.is_hidden(path)) and not cm.allow_hidden:
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/nbconvert/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class NbconvertFileHandler(JupyterHandler):

@web.authenticated
async def get(self, format, path):

self.check_xsrf_cookie()
exporter = get_exporter(format, config=self.config, log=self.log)

path = path.strip('/')
Expand Down

0 comments on commit dc2a5b5

Please sign in to comment.