diff --git a/CHANGES.rst b/CHANGES.rst index f55d192c2..f709fb5cd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,9 @@ Unreleased - Fix how ``max_form_memory_size`` is applied when parsing large non-file fields. :ghsa:`q34m-jh98-gwm2` +- ``safe_join`` catches certain paths on Windows that were not caught by + ``ntpath.isabs`` on Python < 3.11. :ghsa:`f9vj-2wh5-fj8j` + Version 3.0.5 ------------- diff --git a/src/werkzeug/security.py b/src/werkzeug/security.py index 9999509d1..997597990 100644 --- a/src/werkzeug/security.py +++ b/src/werkzeug/security.py @@ -151,6 +151,8 @@ def safe_join(directory: str, *pathnames: str) -> str | None: if ( any(sep in filename for sep in _os_alt_seps) or os.path.isabs(filename) + # ntpath.isabs doesn't catch this on Python < 3.11 + or filename.startswith("/") or filename == ".." or filename.startswith("../") ): diff --git a/tests/test_security.py b/tests/test_security.py index 6fad089a7..3ce741a99 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -1,5 +1,4 @@ import os -import posixpath import sys import pytest @@ -47,11 +46,17 @@ def test_invalid_method(): generate_password_hash("secret", "sha256") -def test_safe_join(): - assert safe_join("foo", "bar/baz") == posixpath.join("foo", "bar/baz") - assert safe_join("foo", "../bar/baz") is None - if os.name == "nt": - assert safe_join("foo", "foo\\bar") is None +@pytest.mark.parametrize( + ("path", "expect"), + [ + ("b/c", "a/b/c"), + ("../b/c", None), + ("b\\c", None if os.name == "nt" else "a/b\\c"), + ("//b/c", None), + ], +) +def test_safe_join(path, expect): + assert safe_join("a", path) == expect def test_safe_join_os_sep():