Title
CRITICAL: Authentication Bypass in Script Locking Mechanism via Path Normalization Inconsistency
Description
BashManager implements a script locking feature designed to prevent unauthorized viewing, modification, deletion, and execution of specific bash scripts. The core of this mechanism is the check_lock(rel_path, provided_pass) function, which verifies if a script is locked by checking if its path exists as a key in the locks.json dictionary.
However, a critical vulnerability exists because the application checks the lock state using the raw, unnormalized user input (rel_path), but later accesses the file using a normalized path.
For example, when saving or deleting a script (/api/scripts/content, /api/scripts/delete, /api/scripts/run), the workflow is:
check_lock(rel_path, provided_pass) is called using the exact string provided by the user.
- If
check_lock passes, the application normalizes the path using validate_safe_path(), which calls Path(...).resolve().
An attacker can exploit this by injecting directory traversal sequences (e.g., ../) that logically point to the locked script but have a different string representation.
If a script is locked as category/secret_script.sh, an attacker can request an operation on category/../category/secret_script.sh.
Because the literal string "category/../category/secret_script.sh" is not in locks.json, check_lock() assumes the file is not locked and grants access. Immediately after, validate_safe_path() normalizes the path to /path/to/scripts/category/secret_script.sh.
This inconsistency completely defeats the locking mechanism, allowing attackers to unconditionally view, overwrite, delete, or execute any locked script.
Proof of Concept
- Create a script named
secret.sh in the utils category.
- Lock the script using the UI (which saves
utils/secret.sh into locks.json).
- Attempt to run the script normally without a password; it will fail with a 401 Unauthorized error.
- Send a POST request to
/api/scripts/run to execute the script using path traversal:
{
"path": "utils/../utils/secret.sh",
"password": ""
}
- The lock check is bypassed, the path is normalized, and the locked script successfully executes. This bypass works identically for
/api/scripts/delete and /api/scripts/content.
Recommended Fix
Normalize the rel_path before checking it against the locks database.
In app.py, update endpoints that utilize paths so that they normalize the path first, then use the normalized relative path for the check_lock function.
Alternatively, modify check_lock to normalize the requested path:
def check_lock(rel_path: str, provided_pass: str) -> bool:
# Normalize the path to remove any ../ sequences to match the locks database correctly
normalized_path = os.path.normpath(rel_path.lstrip('/'))
locks = load_locks()
if normalized_path in locks:
if not provided_pass:
return False
# ... validation logic ...
return True
/assign
Title
CRITICAL: Authentication Bypass in Script Locking Mechanism via Path Normalization Inconsistency
Description
BashManager implements a script locking feature designed to prevent unauthorized viewing, modification, deletion, and execution of specific bash scripts. The core of this mechanism is the
check_lock(rel_path, provided_pass)function, which verifies if a script is locked by checking if its path exists as a key in thelocks.jsondictionary.However, a critical vulnerability exists because the application checks the lock state using the raw, unnormalized user input (
rel_path), but later accesses the file using a normalized path.For example, when saving or deleting a script (
/api/scripts/content,/api/scripts/delete,/api/scripts/run), the workflow is:check_lock(rel_path, provided_pass)is called using the exact string provided by the user.check_lockpasses, the application normalizes the path usingvalidate_safe_path(), which callsPath(...).resolve().An attacker can exploit this by injecting directory traversal sequences (e.g.,
../) that logically point to the locked script but have a different string representation.If a script is locked as
category/secret_script.sh, an attacker can request an operation oncategory/../category/secret_script.sh.Because the literal string
"category/../category/secret_script.sh"is not inlocks.json,check_lock()assumes the file is not locked and grants access. Immediately after,validate_safe_path()normalizes the path to/path/to/scripts/category/secret_script.sh.This inconsistency completely defeats the locking mechanism, allowing attackers to unconditionally view, overwrite, delete, or execute any locked script.
Proof of Concept
secret.shin theutilscategory.utils/secret.shintolocks.json)./api/scripts/runto execute the script using path traversal:{ "path": "utils/../utils/secret.sh", "password": "" }/api/scripts/deleteand/api/scripts/content.Recommended Fix
Normalize the
rel_pathbefore checking it against the locks database.In
app.py, update endpoints that utilize paths so that they normalize the path first, then use the normalized relative path for thecheck_lockfunction.Alternatively, modify
check_lockto normalize the requested path:/assign