flawopen.com/Path Traversal/Python os.path.join()
Does Python os.path.join() prevent path traversal?
No. Think of the base directory as a room in a building: .. walks out through parent rooms, while an absolute path is a new street address that makes os.path.join() discard the room you started in. Resolve the final path and prove it remains under the allowed directory before opening it.
import os base_dir = "/srv/uploads" user_filename = "../../outside/demo.txt" path = os.path.join(base_dir, user_filename) # Relative traversal can leave the base. path = os.path.join(base_dir, "/outside/demo.txt") # An absolute segment discards base_dir.
import os
def resolve_under_base(base_dir, user_filename):
if not isinstance(user_filename, str) or not user_filename:
raise ValueError("Invalid filename")
base = os.path.realpath(base_dir)
candidate = os.path.realpath(
os.path.join(base, user_filename)
)
try:
inside = os.path.commonpath((base, candidate)) == base
except ValueError: # e.g. different Windows drives
inside = False
if not inside or candidate == base:
raise ValueError("Invalid filename")
return candidate
Two path-join behaviors to account for
.. components can move a relative path above its intended directory. Separately, Python documents that an absolute segment causes os.path.join() to discard earlier segments; on Windows, drive and rooted-path rules differ from POSIX. pathlib.Path(base) / user_input has the same absolute-segment behavior. Do not treat joining or normalization as an authorization check.
Why commonpath() is safer than string prefixes
Resolve both the trusted base and candidate before comparing them. os.path.commonpath() compares path components rather than raw character prefixes and raises ValueError for incompatible roots or drives; reject those cases. realpath() resolves symlinks that exist when checked, but this check does not prevent a concurrent symlink change between validation and file open. If an attacker can modify the directory tree, use platform-appropriate descriptor-based access and no-follow controls.
- ✓Reject empty or non-string path input before joining.
- ✓Resolve the trusted base and candidate, then require
commonpath((base, candidate)) == base; rejectValueErrorand the base directory itself when a file is required. - ✓Treat a containment check as separate from authorization; allow only the file types and operations the endpoint needs.
- ✓If untrusted users can change symlinks in the tree, prevent check/use races with platform-appropriate file-descriptor APIs.
FAQ
Yes. The / operator on Path discards earlier components when the next path is absolute. Resolve the result and verify it is within the permitted base.
No. It resolves symlinks present at validation time. Another process with write access can change a link before the later open. Restrict writes to the tree or use descriptor-relative operations with no-follow behavior where the platform supports them.