Directory Traversal (Path Traversal)
Also known as: Path traversal, Directory traversal, Dot-dot-slash, Arbitrary file read
A flaw in which user-supplied input is used to build a filesystem path without confinement, so a request can reach files outside the intended directory; it is prevented by resolving the path and verifying it stays inside an allow-listed base directory.
How it works
Directory traversal, also called path traversal, is a flaw in how an application turns a request value into a file on disk. When a download or image endpoint joins a base folder with a name the user supplied, the developer assumes the result stays inside that folder. The operating system, however, interprets special path elements that move up the directory tree, so the final location can sit somewhere the feature was never meant to expose.
The root cause is trusting the string instead of checking the destination. The same weakness appears in download links, template or language selectors, log viewers, archive extraction and any parameter that ends up in a file API. The impact depends on what the service account can read, from other users' uploads to configuration files and credentials, and in some designs the ability to write files as well. That is why file permissions matter as much as the code.
The fix checks the resolved location, not the raw text. The application builds the candidate path, canonicalizes it so that every relative element and link is resolved, and then confirms the result is still inside an allow-listed base directory. Even better, the client never sends a path at all: it sends an identifier that the server maps to a known file.
Defence is layered. Canonicalize-and-confine is the primary control, identifier mapping removes the problem class for most features, a service account limited to the directories it needs caps the damage, and a web application firewall plus log monitoring catch attempts and misses. Stripping or blocking certain characters is fragile because encodings and nesting defeat it, so it should never be the main defence.
Walk through it
- 1Read the file-serving handler
- 2Name the missing control
- 3Read the signals, pick the fix
- Scope every file-handling path
- Confine, map and verify
A pre-release review flags the document download feature. It returns a file whose name comes from a request parameter. Read the handler as a reviewer and note how the file location is produced and what is checked before the file is read.
1// GET /download?file=...2const BASE_DIR = "/srv/portal/public-docs";3router.get("/download", requireSession, async (req, res) => {4 const name = String(req.query.file);5 const target = path.join(BASE_DIR, name);6 res.sendFile(target);7});Spot it
- Requests whose file or path parameters contain relative-path sequences, including encoded or double-encoded forms.
- WAF path-traversal rule hits against download, image, template or log-viewer endpoints.
- HTTP 200 responses on file-serving endpoints for names that do not match any published document.
- A burst of 404 or 403 responses from one source on a file endpoint, as someone probes for locations.
- File-access audit entries showing the service account reading outside its normal content directory, such as system or configuration files.
WAF
ts=2026-10-11T10:02:11Z rule=path-traversal action=log src=203.0.113.77 host=portal.fabrikam-docs.example path=/download param=file
ts=2026-10-11T10:02:12Z rule=path-traversal action=log src=203.0.113.77 host=portal.fabrikam-docs.example path=/download param=file note=double_encodedFile access audit
ts=2026-10-11T10:02:13Z user=portal_app event=read object=outside_content_root client_app=portal note=unexpected_for_this_account
ts=2026-10-11T10:02:41Z user=portal_app event=read object=application_config client_app=portal note=unexpected_for_this_accountsplSplunk: sources with repeated path-traversal WAF hits on a file endpoint
index=waf rule="path-traversal" path="/download"
| stats count by src
| where count > 20Tune the threshold to the endpoint's baseline, then check whether any of those sources also received 200 responses.
kqlKQL: repeated WAF hits for local file inclusion and traversal rules
AzureDiagnostics
| where Category == "ApplicationGatewayFirewallLog" and (ruleGroup_s has "LFI" or ruleGroup_s has "TRAVERSAL")
| summarize hits=count() by clientIp_s, requestUri_s
| where hits > 20Stop it
Canonicalize the path and verify it stays inside an allow-listed base directory
Build the candidate path, resolve it fully with the platform's safe path API so relative elements and links are collapsed, then confirm the result still begins with the approved base directory. Reject anything else, and run the check on the resolved path, never on the raw string.
Do not accept paths from clients: map identifiers to known files
Let the request carry an opaque id or a name from a fixed list, and have the server look up the real file. If the user never supplies a path, there is nothing to escape from.
Run with least-privilege file permissions and add monitoring as a backstop
Give the service account read access only to the content directory, keep secrets and configuration outside the web-readable tree, and alert on reads outside the expected root. A WAF and log alerts catch attempts but never replace the code fix.
Resolve the path, then confirm it is inside the base directory
Vulnerable
const target = path.join(BASE_DIR, name);
res.sendFile(target);Hardened
const base = path.resolve(BASE_DIR);
const target = path.resolve(base, name);
if (!target.startsWith(base + path.sep)) {
return res.status(400).send("Invalid file");
}
res.sendFile(target);The check runs on the resolved destination. For symbolic links, also resolve the real path before comparing.
Map an id to a known file instead of using a path
Vulnerable
return send_file(os.path.join(BASE_DIR, request.args["file"]))Hardened
DOCS = {"handbook": "handbook.pdf", "pricing": "pricing.pdf"}
name = DOCS.get(request.args.get("id", ""))
if name is None:
abort(404)
return send_from_directory(BASE_DIR, name)Only names in the fixed table can ever be served, and the safe helper keeps the result inside the directory.
- Never build file paths directly from request values; use identifiers mapped server-side to known files.
- Where a path must be accepted, resolve it with the platform API and verify it is inside the allow-listed base directory.
- Use safe helpers such as framework static-file functions that confine access to one directory.
- Keep secrets, configuration and source code outside the directories the web process serves.
- Run the service as a dedicated account with read-only access to the content directory only.
- Return generic errors, log the detail server-side, and alert on reads outside the expected root.
If it already happened
Tighten WAF rules on the affected endpoint, block the offending sources, and if exposure is likely disable the download feature while the code fix ships.
Replace every client-supplied path on the affected routes with identifier mapping or canonicalize-and-confine checks, and review file access and web logs to scope which files were returned.
Rotate any credentials, keys or tokens stored in files that may have been read, run regression tests that confirm out-of-folder requests are refused, and notify affected parties if regulated data was exposed.
Add a static-analysis rule and review checklist item for paths built from request values, move secrets out of web-readable locations, and keep the log detections above permanently.
Check yourself
1. What is the root cause of a directory traversal flaw?
2. Which control is the primary fix for path traversal?
3. Why is mapping an identifier to a known file stronger than validating a client-supplied path?