diff --git a/CHANGES.rst b/CHANGES.rst index e83c0e7d17..8dbf89b6f5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,28 @@ +.. _changes_2.0.2: + +2.0.2 (2023-08-24) +========== + +Bug Fixes +--------- + +- Removed support for null-bytes in the path when making a request for a file + against a static_view. Whille null-bytes are allowed by the HTTP + specification, due to the handling of null-bytes potentially leading to + security vulnerabilities it is no longer supported. + + This fixes a security vulnerability that is present due to a bug in Python + 3.11.0 through 3.11.4, thereby allowing the unintended disclosure of an + ``index.html`` one directory up from the static views path. + + Thanks to Masashi Yamane of LAC Co., Ltd for reporting this issue. + +Backward Incompatibilities +-------------------------- + +- Requests to a static_view are no longer allowed to contain a null-byte in any + part of the path segment. + .. _changes_2.0.1: 2.0.1 (2023-01-29) diff --git a/docs/whatsnew-2.0.rst b/docs/whatsnew-2.0.rst index fa2c12df79..3b383f9f04 100644 --- a/docs/whatsnew-2.0.rst +++ b/docs/whatsnew-2.0.rst @@ -16,6 +16,7 @@ Pyramid 2.0 was released on 2021-02-28. The following bug fix releases were made since then. Bug fix releases also include documentation improvements and other minor feature changes. - :ref:`changes_2.0.1` +- :ref:`changes_2.0.2` Feature Additions ----------------- diff --git a/src/pyramid/static.py b/src/pyramid/static.py index 5363c16716..100d17676e 100644 --- a/src/pyramid/static.py +++ b/src/pyramid/static.py @@ -138,11 +138,11 @@ def get_resource_name(self, request): # normalize asset spec or fs path into resource_path if self.package_name: # package resource - resource_path = '%s/%s' % (self.docroot.rstrip('/'), path) + resource_path = '{}/{}'.format(self.docroot.rstrip('/'), path) if resource_isdir(self.package_name, resource_path): if not request.path_url.endswith('/'): raise self.add_slash_redirect(request) - resource_path = '%s/%s' % ( + resource_path = '{}/{}'.format( resource_path.rstrip('/'), self.index, ) @@ -260,12 +260,12 @@ def _add_vary(response, option): response.vary = vary -_seps = {'/', os.sep} +_invalid_element_chars = {'/', os.sep, '\x00'} -def _contains_slash(item): - for sep in _seps: - if sep in item: +def _contains_invalid_element_char(item): + for invalid_element_char in _invalid_element_chars: + if invalid_element_char in item: return True @@ -279,7 +279,7 @@ def _secure_path(path_tuple): # unless someone screws up the traversal_path code # (request.subpath is computed via traversal_path too) return None - if any([_contains_slash(item) for item in path_tuple]): + if any([_contains_invalid_element_char(item) for item in path_tuple]): return None encoded = '/'.join(path_tuple) # will be unicode return encoded diff --git a/tests/fixtures/index.html b/tests/fixtures/index.html new file mode 100644 index 0000000000..a37df5790d --- /dev/null +++ b/tests/fixtures/index.html @@ -0,0 +1 @@ +