From 5b41daa0006a130fc21e1c665f839eb511072b2e Mon Sep 17 00:00:00 2001 From: BitterPanda Date: Fri, 6 Feb 2026 13:50:25 +0100 Subject: [PATCH] create normalize_path: Also normalizes /./ into / --- .../detect_path_traversal_test.py | 26 +++++++++++++++++++ .../path_traversal/unsafe_path_start.py | 25 +++++++++++++----- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/aikido_zen/vulnerabilities/path_traversal/detect_path_traversal_test.py b/aikido_zen/vulnerabilities/path_traversal/detect_path_traversal_test.py index 60195d5c6..7afb4401c 100644 --- a/aikido_zen/vulnerabilities/path_traversal/detect_path_traversal_test.py +++ b/aikido_zen/vulnerabilities/path_traversal/detect_path_traversal_test.py @@ -104,3 +104,29 @@ def test_does_not_absolute_path_inside_another_folder(): def test_disable_checkPathStart(): assert detect_path_traversal("/etc/passwd", "/etc/passwd", False) is False + + +def test_current_directory_references(): + """/./ should be normalized to /""" + assert detect_path_traversal("/./etc/passwd", "/./etc") is True + assert detect_path_traversal("/etc/./passwd", "/etc") is True + assert detect_path_traversal("/etc/./passwd", "/etc/./") is True + assert detect_path_traversal("/./etc/passwd", "/./etc/passwd") is True + assert detect_path_traversal("/etc/./passwd", "/etc/./passwd") is True + assert detect_path_traversal("/./etc/./passwd", "/./etc/./passwd") is True + # Multiple /./ sequences + assert detect_path_traversal("/././etc/passwd", "/././etc") is True + assert detect_path_traversal("/etc/././passwd", "/etc/././passwd") is True + + +def test_path_normalization(): + """Paths with multiple slashes and /./ should be normalized and detected""" + assert detect_path_traversal("//etc//passwd", "/etc") is True + assert detect_path_traversal("/./etc/./passwd", "/etc") is True + assert detect_path_traversal("/././etc/passwd", "/etc") is True + # Paths without leading slash are not unsafe + assert detect_path_traversal("etc/passwd", "etc") is False + assert detect_path_traversal("", "") is False + # Combined slashes and dot: ///.///etc/passwd should normalize to /etc/passwd + assert detect_path_traversal("///.///etc/passwd", "///.///etc") is True + assert detect_path_traversal("///.///etc/passwd", "///.///etc/passwd") is True diff --git a/aikido_zen/vulnerabilities/path_traversal/unsafe_path_start.py b/aikido_zen/vulnerabilities/path_traversal/unsafe_path_start.py index 7e6868cd5..1f6e9d769 100644 --- a/aikido_zen/vulnerabilities/path_traversal/unsafe_path_start.py +++ b/aikido_zen/vulnerabilities/path_traversal/unsafe_path_start.py @@ -1,5 +1,9 @@ """Exports the function starts_with_unsafe_path""" +import regex as re + +CURRENT_DIR_PATTERN = re.compile(r"/(\./)+") + linux_root_folders = [ "/bin/", "/boot/", @@ -28,8 +32,8 @@ def starts_with_unsafe_path(file_path, user_input): """Check if the file path starts with any dangerous paths and the user input.""" - path_parsed = ensure_one_leading_slash(file_path.lower()) - input_parsed = ensure_one_leading_slash(user_input.lower()) + path_parsed = normalize_path(file_path) + input_parsed = normalize_path(user_input) for dangerous_start in dangerous_path_starts: if path_parsed.startswith(dangerous_start) and path_parsed.startswith( @@ -40,7 +44,16 @@ def starts_with_unsafe_path(file_path, user_input): return False -def ensure_one_leading_slash(path: str) -> str: - if path.startswith("/"): - return "/" + path.lstrip("/") - return path +def normalize_path(path: str) -> str: + """Normalizes a path by lowercasing, removing /./ and removing consecutive slashes""" + if not path: + return path + + normalized = path.lower() + + # Matches /./ or /././ or /./././ etc. (one or more ./ sequences after a /) + normalized = CURRENT_DIR_PATTERN.sub("/", normalized) + + # Merge consecutive slashes since these don't change where you are in the path. + normalized = re.sub(r"/+", "/", normalized) + return normalized