SecureBlockLog inStart a pentest
Vulnerability Repository
CriticalInjection

Local File Inclusion (LFI)

User input is passed to a file include directive without validation, allowing attackers to include arbitrary server-side files and potentially execute their contents.

CVSS 9.0CWE CWE-98OWASP A03:2021 — Injection

Description

Local File Inclusion (LFI), classified under CWE-98 — Improper Control of Filename for Include/Require Statement in PHP Program, occurs when a web application uses user-controlled input to select which local file to include or execute. PHP applications are the most common affected platform — the language's include(), require(), include_once(), and require_once() directives are designed to load and execute files, and when the filename is derived from user input, an attacker can manipulate it to include arbitrary files from the server's file system.

Unlike path traversal (which typically reads files), LFI is particularly dangerous because the included file's contents are executed as server-side code if they contain PHP code — or interpreted as script in any language the runtime can execute. This makes LFI a critical-severity vulnerability (CVSS 9.0) that frequently leads to remote code execution through log poisoning, PHP session file injection, or /proc/self/environ exploitation.

LFI is classified under A03:2021 — Injection in the OWASP Top 10 because the root cause is injection of a filename that reaches a code execution directive. It frequently appears in applications that implement dynamic page loading, template selection, language switching, or plugin systems based on URL parameters.

How It Works

A PHP application uses a page parameter to load content:

<?php
$page = $_GET['page'];
include("pages/" . $page . ".php");
?>

An attacker supplies a path traversal sequence:

GET /index.php?page=../../../../etc/passwd%00 HTTP/1.1

The %00 null byte terminates the string before the .php extension is appended (in older PHP versions < 5.3.4). The resulting include path becomes pages/../../../../etc/passwd, and the file contents are dumped to the response.

Log poisoning for RCE is the classic LFI-to-RCE escalation. The attacker first injects PHP code into a server log file, then includes that log file via the LFI:

Step 1 — Poison the Apache access log by sending a request with PHP code in the User-Agent header:

GET / HTTP/1.1
Host: app.example.com
User-Agent: <?php system($_GET['cmd']); ?>

The server logs this to /var/log/apache2/access.log. Step 2 — Include the log file:

GET /index.php?page=../../../../var/log/apache2/access.log&cmd=id HTTP/1.1

The PHP runtime executes the injected code within the log entry, returning the output of id. The attacker now has remote code execution.

Other escalation paths include:

  • /proc/self/environ — contains environment variables sometimes including HTTP headers; if the attacker controls a header value, they can inject PHP code there.
  • PHP session files — located at /var/lib/php/sessions/sess_<PHPSESSID>. If the attacker can control session-stored data, they can inject PHP code into their own session file then include it.
  • PHP wrappersphp://filter/convert.base64-encode/resource=index.php bypasses .php extension restrictions and returns the base64-encoded source of PHP files, enabling source code disclosure without execution.

Impact

  • Remote code execution — log poisoning, session file injection, or /proc abuse converts LFI to full RCE.
  • Sensitive file disclosure/etc/passwd, /etc/shadow, SSH keys, application configuration files, and database credentials.
  • Source code disclosure — PHP wrappers allow reading the application's own source files, revealing further vulnerabilities and credentials.
  • Complete server compromise — RCE through LFI provides full shell access as the web server user.
  • Lateral movement — credentials extracted from config files enable access to databases, internal APIs, and adjacent services.
  • Log tampering — reading log files reveals user activity; writing to them (via poisoning) can corrupt audit trails.

Detection

  1. Test file inclusion parameters — supply ../../../../etc/passwd (with and without null byte, with and without .php extension appended) to any parameter that appears to select a page, template, or module.
  2. Test PHP wrappers — inject php://filter/convert.base64-encode/resource=index to attempt source code disclosure. Also test data://text/plain;base64,<base64_php_code> if allow_url_include is enabled.
  3. Use LFI wordlists — tools like ffuf and Burp Intruder with SecLists Fuzzing/LFI/LFI-Jhaddix.txt cover hundreds of platform-specific file paths.
  4. Test encoding bypasses — URL encoding (%2e%2e%2f), double encoding, and Unicode normalization can bypass naive string filters on ../.
  5. Attempt log poisoning — if LFI is confirmed, inject PHP code into User-Agent or other logged headers, then include the appropriate log file path to confirm RCE.
  6. Check for PHP session file inclusion — set your PHPSESSID cookie, inject PHP code into a session-stored value, then include /var/lib/php/sessions/sess_<your_id>.

Remediation

Never use user input directly in include/require directives. Use an allowlist that maps user-supplied keys to hard-coded file paths:

$allowed_pages = [
    "home"    => "pages/home.php",
    "about"   => "pages/about.php",
    "contact" => "pages/contact.php",
];

$page = $_GET['page'] ?? 'home';
if (!array_key_exists($page, $allowed_pages)) {
    $page = 'home';
}
include($allowed_pages[$page]);

Disable dangerous PHP configuration directives. In php.ini, set allow_url_include = Off and allow_url_fopen = Off to prevent remote file inclusion and limit wrapper abuse. Set open_basedir to restrict the file system paths PHP can access.

Disable PHP wrappers if not required. The php://, data://, expect://, and zip:// wrappers can be disabled via php.ini stream filter configuration.

Harden log files. Ensure log files are not readable by the web server process. Store logs outside the web root and restrict permissions. Rotate logs frequently to reduce the window for poisoned entries.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping