Most developers learn to look for ../ in file paths. Fewer look for the plain, boring path that starts with a slash. CWE-36, Absolute Path Traversal, is what happens when code builds a path from external input that should stay inside one directory, but does not neutralize an absolute path such as /etc/passwd or C:\Windows\win.ini. The input skips the restricted directory entirely and lands wherever the attacker points it.
It hits anything that takes a filename from outside: download endpoints, upload handlers, archive extractors, template loaders, and clients that write files named by a server. MITRE lists it as not language specific, and the observed examples cover web applications, FTP clients, ZIP tools and AI/ML tooling.
How it happens
The usual pattern is a base directory plus a user supplied name. The developer expects the name to be something like report.pdf and joins the two. The trouble is that many path APIs treat an absolute second argument as a replacement for the first, not a suffix.
Python is the clearest case. os.path.join("/srv/app/uploads", "/etc/passwd") returns /etc/passwd. The base directory is thrown away. CVE-2022-31503 is described in the MITRE record as a Python package that built filenames with an unsafe os.path.join call on untrusted input, allowing absolute path traversal for exactly this reason.
Here is the vulnerable shape:
# Vulnerable: the name is trusted and joined naively
import os
from flask import request, send_file
UPLOADS = "/srv/app/uploads"
@app.route("/download")
def download():
name = request.args["file"]
return send_file(os.path.join(UPLOADS, name))
Request ?file=/etc/passwd and the join returns the absolute path. No .. appears anywhere, so a filter that only hunts for dot dot slash never fires.
The same flaw shows up on the write side. CVE-2024-0520 is recorded as a product for managing datasets for AI model training and evaluation that allowed both relative (CWE-23) and absolute (CWE-36) path traversal to overwrite files via the Content-Disposition header. The filename arrived in a header, and nobody checked what it resolved to. Older entries in the record show the same idea: multiple FTP clients that write arbitrary files via absolute paths in server responses (CVE-2002-1345), and a ZIP extractor that allowed full paths (CVE-2001-1269).
What an attacker gains
The consequences in the record split four ways.
- Read files. The attacker reads files the application was never meant to expose. MITRE gives the example of reading a password file to impersonate users.
- Modify files. The attacker creates or overwrites programs, libraries or data. If the file backs a security mechanism, the mechanism can be bypassed. Appending a new account to a credentials file is the example MITRE uses.
- Execute code. Overwriting a file that gets executed, such as a program or library, turns a file write into code execution.
- Deny service. Overwriting or deleting critical files can stop the product from working at all.
How to find it
Static analysis is the detection method MITRE lists: SAST tools can model data flow from an input such as a request parameter to a file API such as open or send_file. Read the results with an eye for sinks that take a path and sources you do not control, including headers, archive entry names and values returned by a remote server.
Simple manual checks help too.
- Search the code for every call that opens, reads, writes, deletes or serves a file.
- For each one, trace where the path or filename comes from.
- Try an absolute path in a test environment against each endpoint that takes a filename, on both Unix style and Windows style forms.
- Check dependencies. A path handling bug in a library you ship is your bug. Reachability analysis tells you whether your code actually calls the affected function, which helps you rank the list.
How to fix it
MITRE's guidance centers on input validation, and the specifics for filenames are worth copying.
- Prefer an allowlist. Accept only known good names, or a strict character set, and reject the rest. Do not rely on stripping bad characters, which is a denylist and tends to be incomplete.
- Exclude directory separators. MITRE says to exclude
/to avoid CWE-36, and, if feasible, allow only a single.in the filename to avoid CWE-23. - Limit file extensions to a known list.
- Better still, do not let users name files. Map an ID to a stored path on the server side.
Then add a check on the resolved path, because validation of the raw string alone is easy to get wrong:
# Fixed: reject absolute input, resolve, and confirm containment
import os
from flask import request, send_file, abort
BASE = os.path.realpath("/srv/app/uploads")
@app.route("/download")
def download():
name = request.args["file"]
if os.path.isabs(name) or os.path.basename(name) != name:
abort(400)
path = os.path.realpath(os.path.join(BASE, name))
if os.path.commonpath([BASE, path]) != BASE:
abort(400)
return send_file(path)
Run the process with least privilege so that a bug that slips through can only touch a small part of the file system. If your code extracts archives, apply the same containment check to every entry name before writing.
Try it yourself
Run a code scan against a small repository and watch how a data flow finding is traced from source to sink.
Click through it at your own pace, or open it full screen.
You can also check the libraries in your project with the free in-browser dependency scan at gold.safeguard.sh/scan. No login is needed, the file stays on your machine, and it covers up to 100 dependencies.
Quick check
Why does a filter for dot dot slash not stop CWE-36?
Because the attack does not use dot dot slash. A path that starts with a slash, or a drive letter on Windows, points straight at the target. Some APIs, such as Python's os.path.join, discard the base directory when the second part is absolute.
Is checking the string enough?
No. Resolve the final path and confirm it is still inside the base directory. Validation of the raw string and a check of the resolved path work best together.