Skip to content

Commit

Permalink
Merge pull request #1612 from pallets/shared-data
Browse files Browse the repository at this point in the history
SharedDataMiddleware uses safe_join
  • Loading branch information
davidism authored Jul 15, 2019
2 parents 8afe4eb + acc999e commit a8d26bf
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 20 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Unreleased
reloader to fail. :issue:`1607`
- Work around an issue where the reloader couldn't introspect a
setuptools script installed as an egg. :issue:`1600`
- ``SharedDataMiddleware`` safely handles paths with Windows drive
names. :issue:`1589`


Version 0.15.4
Expand Down
17 changes: 5 additions & 12 deletions src/werkzeug/middleware/shared_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from ..filesystem import get_filesystem_encoding
from ..http import http_date
from ..http import is_resource_modified
from ..security import safe_join
from ..wsgi import get_path_info
from ..wsgi import wrap_file

Expand Down Expand Up @@ -149,7 +150,7 @@ def loader(path):
if path is None:
return None, None

path = posixpath.join(package_path, path)
path = safe_join(package_path, path)

if not provider.has_resource(path):
return None, None
Expand All @@ -170,7 +171,7 @@ def loader(path):
def get_directory_loader(self, directory):
def loader(path):
if path is not None:
path = os.path.join(directory, path)
path = safe_join(directory, path)
else:
path = directory

Expand All @@ -192,19 +193,11 @@ def generate_etag(self, mtime, file_size, real_filename):
)

def __call__(self, environ, start_response):
cleaned_path = get_path_info(environ)
path = get_path_info(environ)

if PY2:
cleaned_path = cleaned_path.encode(get_filesystem_encoding())
path = path.encode(get_filesystem_encoding())

# sanitize the path for non unix systems
cleaned_path = cleaned_path.strip("/")

for sep in os.sep, os.altsep:
if sep and sep != "/":
cleaned_path = cleaned_path.replace(sep, "/")

path = "/" + "/".join(x for x in cleaned_path.split("/") if x and x != "..")
file_loader = None

for search_path, loader in self.exports:
Expand Down
24 changes: 16 additions & 8 deletions src/werkzeug/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,20 +222,28 @@ def check_password_hash(pwhash, password):


def safe_join(directory, *pathnames):
"""Safely join `directory` and one or more untrusted `pathnames`. If this
cannot be done, this function returns ``None``.
"""Safely join zero or more untrusted path components to a base
directory to avoid escaping the base directory.
:param directory: the base directory.
:param pathnames: the untrusted pathnames relative to that directory.
:param directory: The trusted base directory.
:param pathnames: The untrusted path components relative to the
base directory.
:return: A safe path, otherwise ``None``.
"""
parts = [directory]

for filename in pathnames:
if filename != "":
filename = posixpath.normpath(filename)
for sep in _os_alt_seps:
if sep in filename:
return None
if os.path.isabs(filename) or filename == ".." or filename.startswith("../"):

if (
any(sep in filename for sep in _os_alt_seps)
or os.path.isabs(filename)
or filename == ".."
or filename.startswith("../")
):
return None

parts.append(filename)

return posixpath.join(*parts)

0 comments on commit a8d26bf

Please sign in to comment.