T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- scripts/audit.py:27
- Finding
- Symlink Traversal Allows Reads Outside the Audit Target<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audit.py:27-35` **Vulnerability Type**: Symlink traversal and out-of-scope file access **Risk Level**: Medium ### Vulnerable Code ```python for root, dirs, files in os.walk(target_path): for file in files: # Skip hidden files or specific extensions if needed if file.startswith('.') or file.endswith(('.pyc', '.skill', '.zip')): continue file_path = os.path.join(root, file) try: with open(file_path, "r", encoding="utf-8", errors="ignore") as f: content = f.read() ``` ### Technical Analysis The scanner recursively enumerates a user-supplied directory and opens each discovered file without verifying whether it is a symbolic link or whether its resolved path remains inside the requested audit root. Although `os.walk()` does not follow symlinked directories by default, symlinked files can still appear in the `files` collection. Python's `open()` follows such links. Consequently, an untrusted Skill can include a file symlink pointing to any file readable by the user running the auditor. Reading files outside the selected directory exceeds the minimum privileges needed for static analysis of that directory. The file contents are not printed or transmitted directly, which limits immediate disclosure, but pattern matches can reveal properties of external files and alter the generated risk report. Links to special files may also block or disrupt the audit. ### Attack Path 1. An attacker creates a Skill directory containing a non-hidden file symlink to a sensitive local file, such as an SSH private key or application credential file. 2. A victim runs `python3 scripts/audit.py <attacker-controlled-directory>`. 3. `os.walk()` lists the symlink as a file. 4. `open(file_path, ...)` follows the link and reads the external target with the victim's filesystem privileges. 5. Content from the out-of-scope file is tested again ...[truncated 722 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Reject symbolic links before opening candidates, using `os.path.islink()` or `os.stat(..., follow_symlinks=False)`. - Resolve the audit root and each candidate with `os.path.realpath()`, then verify that the candidate remains beneath the resolved root with `os.path.commonpath()`. - Process only regular files and reject devices, sockets, FIFOs, and other special file types. - Handle race conditions by opening files without following symlinks where the operating system supports `O_NOFOLLOW`, then validate the opened descriptor with `fstat()`. - Add file-size and read-time limits to reduce denial-of-service risk. Example boundary validation: ```python root_real = os.path.realpath(target_path) candidate_real = os.path.realpath(file_path) if os.path.commonpath([root_real, candidate_real]) != root_real: continue if os.path.islink(file_path) or not os.path.isfile(file_path): continue ``` Descriptor-based checks should be preferred when protection against time-of-check/time-of-use races is required. ]]>
