Directory Traversal Attack Patterns
A directory traversal attack uses ../ sequences in URL parameters to escape the application’s intended directory and read or include files elsewhere on the filesystem. It is the underlying technique behind most local file inclusion (LFI) exploits, including PHP pearcmd RCE and similar framework attack chains.
../ probe is the simplest, oldest, still-effective web attack. Drop it into any URL parameter that maps to a file path, and an unprotected application will happily return /etc/passwd, source files, config files, or — when combined with a file-include vulnerability — give the attacker full RCE. Despite being on every security checklist for 20 years, it remains in active exploitation today because the underlying string-concatenation pattern keeps reappearing in new code.
What does a directory traversal attempt look like?
The simplest form is a query parameter containing ../ sequences. Real example, captured from a Dstl8 customer’s production logs:
GET /index.php?lang=../../../../../../../tmp/index1 HTTP/1.1
Seven levels of ../ followed by a target path. The attacker is gambling that:
- The
langparameter is used in a file path concatenation on the server - That concatenation isn’t validated against traversal
- The path resolves to a real file (here,
/tmp/index1) the application can read
If all three are true, the response body contains the file’s contents. If not, you get a 404, 403, or 400. Either way, the attempt appears in your access logs and is easy to spot once you know the signature.
Common variants you’ll see in logs
| Variant | Encoded form | Why attackers use it |
|---|---|---|
| Plain | ../../../etc/passwd | Baseline — works against unprotected apps |
| URL-encoded | %2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd | Bypasses naive filters that only block literal .. |
| Double-encoded | %252e%252e%252fetc%252fpasswd | Bypasses filters that decode once before checking |
| UTF-8 overlong | %c0%ae%c0%ae%c0%afetc%c0%afpasswd | Bypasses filters that don’t normalize UTF-8 properly |
| Null byte injection | ../../etc/passwd%00.png | (Older PHP) Truncates path at null byte, bypassing extension checks |
| Backslash variant | ..\..\..\windows\system32 | Windows hosts; some path normalizers don’t catch this |
A good WAF blocks all of these. A bad WAF blocks only the first.
Why does directory traversal work?
The vulnerability is almost always the same pattern in application code:
// Vulnerable pseudocode
$lang = $_GET['lang'];
$path = '/var/www/lang/' . $lang . '.php';
include($path);
The developer’s intent: include /var/www/lang/en.php when ?lang=en. What actually happens with ?lang=../../../../../../../tmp/index1:
- String concatenation yields
/var/www/lang/../../../../../../../tmp/index1.php - The OS file system resolves
../by walking up directories - Resolved path:
/tmp/index1.php(or further up, depending on how many../) - The application opens and processes that file as if it were a legitimate language file
The fix is straightforward in theory — validate the input or use safer path-resolution APIs. In practice, this anti-pattern reappears in new code constantly because:
- It “works” in dev. The vulnerability isn’t visible during normal testing — only when someone actively probes it
- Frameworks don’t always help. Many web frameworks expose raw path-handling APIs alongside safe ones; developers use whichever they reach first
- Code review misses it. A line like
include('lang/' . $lang . '.php')doesn’t look obviously dangerous to a reviewer scanning quickly
How severe is it?
Severity depends entirely on what the attacker can do with the file access:
| Outcome | Severity | Typical follow-up attack |
|---|---|---|
| Read sensitive files (passwd, config, source) | Major | Credential exfiltration, source code analysis for further attacks |
Read .env with DB credentials, API keys | Critical | Lateral movement into databases, third-party services |
Combined with include() → LFI → RCE | Critical | Full code execution (e.g., PHP pearcmd, ThinkPHP) |
| Blocked (404/403) | Minor (signal only) | Note for perimeter-monitoring; no immediate action |
The “blocked” outcome is by far the most common — your server doesn’t have the requested file, your WAF strips the traversal, or your application validates the path. But blocked attempts still matter as reconnaissance signal: they indicate active scanning against your IP space.
How to debug and remediate
1. Check the HTTP response code
| Response | Meaning | Action |
|---|---|---|
| 404 / 403 / 400 | Attack failed | Log for awareness |
| 200 with file contents visible | Active compromise | Incident response — isolate host immediately |
| 500 | Application crashed processing the input | Investigate — may indicate partial success |
2. Audit the vulnerable parameter
Look at which URL parameter contained the ../ sequence. Common offenders:
lang,locale,languagepage,file,view,templatepath,dir,includeimage,img,document,doc
Any parameter that maps to a file path in your application code is a candidate. Audit every place these are used.
3. Fix at the code layer
The robust fix is always one of:
- Whitelist allowed values — if only specific languages are valid, check against an explicit list:
if ($lang in ['en', 'es', 'de']) - Use safe path-resolution APIs that reject traversal — most languages have these (Python:
pathlib.Path.resolve()+ check parent; PHP:realpath()+ base directory comparison) - Never concatenate user input into file paths — use indirect references (a database ID maps to a file path, not the file name itself)
4. Add WAF rules
Block at the perimeter for defense in depth:
- URL containing literal
..in any parameter - URL containing URL-encoded variants (
%2e%2e,%2E%2E) - URL containing double-encoded variants (
%252e%252e) - Parameter values starting with
/followed by sensitive directory names (etc/,usr/,var/,tmp/)
5. Apply principle of least privilege
Even if traversal succeeds, limit what’s reachable:
- Run the web application under a user with no read access to system directories (
/etc,/usr/local/lib,/var/log) - Use container filesystem isolation — the application can only see its own container’s filesystem
- Use chroot or systemd’s
RootDirectory=for additional isolation on bare metal
6. Disable URL-based file inclusion in PHP
allow_url_include = Off
allow_url_fopen = Off
These don’t stop directory traversal directly but prevent the most dangerous chained exploits.
How Dstl8 detects this
Dstl8 surfaces directory traversal as part of a grouped security incident with the other attack families typically batched by the same scanners. Here’s an actual detection from a Dstl8 customer’s production logs (anonymized):
Dstl8 doesn’t fire a separate alert for each attack type — it groups directory traversal, pearcmd, and ThinkPHP probes from the same scanner session into a single incident with the full attack panorama and confirmed blocking status.
Related patterns
References
- OWASP: Path Traversal
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- OWASP Top 10: A01 — Broken Access Control
- PortSwigger Web Security Academy: File path traversal labs
See real attack patterns in your own logs.
Dstl8 surfaces directory traversal, LFI, and RCE attempts the moment they hit your perimeter — grouped into one incident with each exploit family named, not 30 separate alerts.














