T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/sync.py:181
- Finding
- Unvalidated State Paths Permit Filesystem Access Outside Synchronization Roots<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sync.py:124-132, 181-188, 269-278` **Vulnerability Type**: Path traversal through untrusted synchronization state **Risk Level**: High ### Vulnerable Code ```python def move_to_trash(root, rel_path, log_file): src = os.path.join(root, rel_path) if not os.path.exists(src): return dst = os.path.join(root, ".trash", rel_path + f".{int(time.time())}") os.makedirs(os.path.dirname(dst), exist_ok=True) shutil.move(src, dst) log(log_file, "TRASH", rel_path) ``` ```python all_files = set(local_now) | set(remote_now) | set(local_old) | set(remote_old) for f in all_files: try: l = local_now.get(f) r = remote_now.get(f) lo = local_old.get(f) ro = remote_old.get(f) local_path = os.path.join(local_root, f) remote_path = os.path.join(remote_root, f) ``` ```python if l and not r: os.makedirs(os.path.dirname(remote_path), exist_ok=True) shutil.copy2(local_path, remote_path) remote_now[f] = l.copy() log(log_file, "INFO", f"COPY NEW → remote {f}") stats["copy"] += 1 continue if r and not l: os.makedirs(os.path.dirname(local_path), exist_ok=True) shutil.copy2(remote_path, local_path) ``` ### Technical Analysis The keys loaded from `.sync_state.json` are used as filesystem paths without validation. `load_state()` accepts the persisted `files` object, and its keys are subsequently added to `all_files`, even when those keys were not generated by the current filesystem scan. A key can contain an absolute path or parent-directory traversal components such as `../../target`. When the key is passed to `os.path.join`, an absolute final component causes Python to discard the preceding root. Traversal components can similarly resolve outside the intended synchronization directory. These paths reach sensitive filesystem operations including `shutil.move`, `shutil.copy2`, and `os.makedirs`. No canonicali ...[truncated 1305 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Treat all `.sync_state.json` keys as untrusted input. - Reject absolute paths, empty paths, NUL characters, and any path containing a `..` component. - Resolve each candidate path before use and verify containment with `os.path.commonpath`. ```python def safe_path(root, rel_path): if not isinstance(rel_path, str) or not rel_path: raise ValueError("Invalid relative path") if os.path.isabs(rel_path): raise ValueError("Absolute paths are not allowed") root_real = os.path.realpath(root) candidate = os.path.realpath(os.path.join(root_real, rel_path)) if os.path.commonpath([root_real, candidate]) != root_real: raise ValueError("Path escapes synchronization root") return candidate ``` - Apply the containment check separately for every source and destination root before calling `open`, `os.stat`, `os.makedirs`, `shutil.copy2`, or `shutil.move`. - Prefer deriving path keys exclusively from the current live scan. Persisted state should be treated as metadata associated with already validated paths. - Validate the complete state schema, including key and value types, before processing it. - If an invalid state entry is encountered, stop safely and report the error rather than silently processing or discarding it. ]]>
