Skip to content

CRITICAL: Authentication Bypass in Script Locking Mechanism via Path Normalization Inconsistency #248

Description

@basantnema31

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:

  1. check_lock(rel_path, provided_pass) is called using the exact string provided by the user.
  2. 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

  1. Create a script named secret.sh in the utils category.
  2. Lock the script using the UI (which saves utils/secret.sh into locks.json).
  3. Attempt to run the script normally without a password; it will fail with a 401 Unauthorized error.
  4. Send a POST request to /api/scripts/run to execute the script using path traversal:
    {
        "path": "utils/../utils/secret.sh",
        "password": ""
    }
  5. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions