summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2023-08-24 23:49:50 -0600
committerMichael Merickel <michael@merickel.org>2023-08-24 23:49:50 -0600
commitb4e78bd14f7bbfce76399510a78b5346f9bd73e1 (patch)
treec6a3597b7d05ec644ba7093198e7d6a64e8b1c05
parent0919da5326ef65fb6569bc045ee0c0f033185f1c (diff)
parent6726314834d0de9e29c45dcb3d6f3ce9118a956d (diff)
downloadpyramid-b4e78bd14f7bbfce76399510a78b5346f9bd73e1.tar.gz
pyramid-b4e78bd14f7bbfce76399510a78b5346f9bd73e1.tar.bz2
pyramid-b4e78bd14f7bbfce76399510a78b5346f9bd73e1.zip
Merge branch 'tseaver-jp_exploit_fix'
-rw-r--r--CHANGES.rst13
-rw-r--r--src/pyramid/static.py10
-rw-r--r--tests/fixtures/index.html1
-rw-r--r--tests/pkgs/static_abspath_nulbyte/__init__.py9
-rw-r--r--tests/pkgs/static_assetspec_nulbyte/__init__.py6
-rw-r--r--tests/test_integration.py20
-rw-r--r--tests/test_static.py11
7 files changed, 65 insertions, 5 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 035162a14..46f7fbc18 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -14,9 +14,22 @@ Features
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.
- Pyramid is no longer tested on, nor supports Python 3.6
- Pyramid drops support for l*gettext() methods in the i18n module.
These have been deprecated in Python's gettext module since 3.8, and
diff --git a/src/pyramid/static.py b/src/pyramid/static.py
index 71dd715d7..100d17676 100644
--- a/src/pyramid/static.py
+++ b/src/pyramid/static.py
@@ -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 000000000..a37df5790
--- /dev/null
+++ b/tests/fixtures/index.html
@@ -0,0 +1 @@
+<h1>DON'T GO HERE</h1>
diff --git a/tests/pkgs/static_abspath_nulbyte/__init__.py b/tests/pkgs/static_abspath_nulbyte/__init__.py
new file mode 100644
index 000000000..2248522e9
--- /dev/null
+++ b/tests/pkgs/static_abspath_nulbyte/__init__.py
@@ -0,0 +1,9 @@
+import os
+
+
+def includeme(config):
+ here = here = os.path.dirname(__file__)
+ static = os.path.normpath(
+ os.path.join(here, '..', '..', 'fixtures', 'static')
+ )
+ config.add_static_view('/', static)
diff --git a/tests/pkgs/static_assetspec_nulbyte/__init__.py b/tests/pkgs/static_assetspec_nulbyte/__init__.py
new file mode 100644
index 000000000..d44b04e93
--- /dev/null
+++ b/tests/pkgs/static_assetspec_nulbyte/__init__.py
@@ -0,0 +1,6 @@
+def includeme(config):
+ config.add_static_view('/', 'tests:fixtures/static')
+ config.add_static_view('/sub', 'tests:fixtures/static/subdir')
+ config.override_asset(
+ 'tests:fixtures/static/subdir', 'tests:fixtures/static'
+ )
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 0b55872d2..63a7088e9 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -225,6 +225,26 @@ class TestStaticAppUsingAssetSpec(StaticAppBase, unittest.TestCase):
package = 'tests.pkgs.static_assetspec'
+class TestStaticAppUsingAbsPathNulByte(IntegrationBase, unittest.TestCase):
+ package = 'tests.pkgs.static_abspath_nulbyte'
+
+ def test_nulbyte_chroot(self):
+ super_w_null = '..\x00/'
+ self.testapp.get(f'/{super_w_null}', status=404)
+
+
+class TestStaticAppUsingAssetSpecNulByte(IntegrationBase, unittest.TestCase):
+ package = 'tests.pkgs.static_assetspec_nulbyte'
+
+ def test_nulbyte_chroot(self):
+ super_w_null = '..\x00/'
+ self.testapp.get(f'/{super_w_null}', status=404)
+
+ def test_nulbyte_chroot_assetspec_override(self):
+ super_w_null = '..\x00/'
+ self.testapp.get(f'/sub/{super_w_null}', status=404)
+
+
class TestStaticAppWithEncodings(IntegrationBase, unittest.TestCase):
package = 'tests.pkgs.static_encodings'
diff --git a/tests/test_static.py b/tests/test_static.py
index af487fa24..5b11d89a8 100644
--- a/tests/test_static.py
+++ b/tests/test_static.py
@@ -104,6 +104,17 @@ class Test_static_view_use_subpath_False(unittest.TestCase):
self.assertRaises(HTTPNotFound, inst, context, request)
+ def test_oob_nul_char(self):
+ import os
+
+ inst = self._makeOne(f'{os.getcwd()}/tests/fixtures/static')
+ super_w_null = '..\x00/'
+ request = self._makeRequest({'PATH_INFO': f'/{super_w_null}'})
+ context = DummyContext()
+ from pyramid.httpexceptions import HTTPNotFound
+
+ self.assertRaises(HTTPNotFound, inst, context, request)
+
def test_resource_doesnt_exist(self):
inst = self._makeOne('tests:fixtures/static')
request = self._makeRequest({'PATH_INFO': '/notthere'})