Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SharedDataMiddleware uses safe_join #1612

Merged
merged 1 commit into from Jul 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
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
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
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)