Merge pull request #7983 from minrk/contents-escape-root

contents: double check that os_path is always within root
This commit is contained in:
Thomas Kluyver 2015-03-12 15:20:51 -07:00
commit 484958d282
3 changed files with 49 additions and 4 deletions

View File

@ -96,8 +96,16 @@ class FileManagerMixin(object):
------- -------
path : string path : string
Native, absolute OS path to for a file. Native, absolute OS path to for a file.
Raises
------
404: if path is outside root
""" """
return to_os_path(path, self.root_dir) root = os.path.abspath(self.root_dir)
os_path = to_os_path(path, root)
if not (os.path.abspath(os_path) + os.path.sep).startswith(root):
raise HTTPError(404, "%s is outside root contents directory" % path)
return os_path
def _read_notebook(self, os_path, as_version=4): def _read_notebook(self, os_path, as_version=4):
"""Read a notebook from an os path.""" """Read a notebook from an os path."""

View File

@ -373,10 +373,11 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
if 'content' not in model and model['type'] != 'directory': if 'content' not in model and model['type'] != 'directory':
raise web.HTTPError(400, u'No file content provided') raise web.HTTPError(400, u'No file content provided')
self.run_pre_save_hook(model=model, path=path)
os_path = self._get_os_path(path) os_path = self._get_os_path(path)
self.log.debug("Saving %s", os_path) self.log.debug("Saving %s", os_path)
self.run_pre_save_hook(model=model, path=path)
try: try:
if model['type'] == 'notebook': if model['type'] == 'notebook':
nb = nbformat.from_dict(model['content']) nb = nbformat.from_dict(model['content'])

View File

@ -5,6 +5,7 @@ from __future__ import print_function
import os import os
import sys import sys
import time import time
from contextlib import contextmanager
from nose import SkipTest from nose import SkipTest
from tornado.web import HTTPError from tornado.web import HTTPError
@ -164,7 +165,17 @@ class TestContentsManager(TestCase):
def tearDown(self): def tearDown(self):
self._temp_dir.cleanup() self._temp_dir.cleanup()
@contextmanager
def assertRaisesHTTPError(self, status, msg=None):
msg = msg or "Should have raised HTTPError(%i)" % status
try:
yield
except HTTPError as e:
self.assertEqual(e.status_code, status)
else:
self.fail(msg)
def make_dir(self, api_path): def make_dir(self, api_path):
"""make a subdirectory at api_path """make a subdirectory at api_path
@ -461,4 +472,29 @@ class TestContentsManager(TestCase):
cm.mark_trusted_cells(nb, path) cm.mark_trusted_cells(nb, path)
cm.check_and_sign(nb, path) cm.check_and_sign(nb, path)
assert cm.notary.check_signature(nb) assert cm.notary.check_signature(nb)
def test_escape_root(self):
cm = self.contents_manager
# make foo, bar next to root
with open(os.path.join(cm.root_dir, '..', 'foo'), 'w') as f:
f.write('foo')
with open(os.path.join(cm.root_dir, '..', 'bar'), 'w') as f:
f.write('bar')
with self.assertRaisesHTTPError(404):
cm.get('..')
with self.assertRaisesHTTPError(404):
cm.get('foo/../../../bar')
with self.assertRaisesHTTPError(404):
cm.delete('../foo')
with self.assertRaisesHTTPError(404):
cm.rename('../foo', '../bar')
with self.assertRaisesHTTPError(404):
cm.save(model={
'type': 'file',
'content': u'',
'format': 'text',
}, path='../foo')