Safeguard
Application Security•

CWE-98 PHP File Inclusion: How It Happens and How to Fix It

PHP remote file inclusion lets attackers pick the file your include statement loads. How CWE-98 works, what it gives them, and how to close it.

Safeguard Security Team
4 min read

CWE-98, Improper Control of Filename for Include/Require Statement in PHP Program (PHP Remote File Inclusion), covers PHP applications that take input from an upstream component and use it in require, include or similar functions without restricting it.

It is a PHP specific variant, and MITRE lists web servers and web based applications as the context. It is one of the older bug classes on the web, and it still teaches a lesson that applies to any language: letting a user choose which code file to load is letting the user choose your code.

How it happens

A site wants one front page with several sections, so it loads the section from a parameter.

<?php
// Vulnerable: the page name comes straight from the URL
$page = $_GET["page"];
include($page . ".php");

A request like ?page=about works as designed. But MITRE explains two ways it goes wrong. In certain versions and configurations of PHP, the attacker can supply a URL to a remote location, and the product fetches code from there and executes it. In other cases, combined with path traversal, the attacker names a local file that may contain executable statements that PHP will parse.

The second route matters because it still works when remote inclusion is disabled. An attacker may first get PHP code into a local file, for instance through an upload or a log, and then include it.

MITRE's observed examples add a third pattern: modification of an assumed-immutable configuration variable in an include file, allowing file inclusion via direct request. The affected entries are CVE-2004-0285, CVE-2004-0030, CVE-2004-0068 and CVE-2005-2157. The shared mistake is trusting a variable because it was "set earlier" in an include file. If that file can be requested directly, the attacker sets the variable first.

What an attacker gains

MITRE lists confidentiality, integrity and availability impact through execution of unauthorized code or commands. The attacker may specify arbitrary code to be executed from a remote location. Alternatively, ordinary program behavior may insert PHP code into local files, which can then be included. Either way the result is code running with the web application's privileges.

How to find it

MITRE notes that manual white-box analysis is very effective here, since a program usually has a relatively small number of include or require statements. List them all and read each one. For every statement, ask where the filename comes from.

Automated static analysis that models data flow can also detect external control of filenames. MITRE cautions that such tools might not recognize when proper input validation is present, so expect some noise and review the results.

Click through it at your own pace, or open it full screen.

How to fix it

MITRE's mitigations in plain steps:

  1. Use a vetted library or framework that avoids the weakness or makes it easier to avoid.
  2. Map fixed values to files. When the set of acceptable files is known, create a mapping from fixed input values, such as numeric IDs, to the actual filenames, and reject all other inputs. For example, ID 1 maps to one file and ID 2 to another.
  3. Repeat client side checks on the server. Attackers can bypass client side checks by changing values after the check, or by removing the check from the client entirely.
  4. Run in a sandbox or jail such as chroot, AppArmor or SELinux. MITRE rates this as limited: it can reduce the scope of an attack, but the rest of the application may still be compromised.

The mapping approach is simple and strong.

<?php
// Fixed: the user chooses a key, never a filename
$pages = [
    "home"  => "pages/home.php",
    "about" => "pages/about.php",
    "help"  => "pages/help.php",
];
$key = $_GET["page"] ?? "home";
if (!array_key_exists($key, $pages)) {
    http_response_code(404);
    exit;
}
include($pages[$key]);

Nothing from the request reaches the include as a path. An unknown key gets a 404.

Dependencies deserve a look too, since a third party PHP library can carry this bug. The free in-browser scan at gold.safeguard.sh/scan needs no login and keeps your file on your machine.

Quick check

Why does this still matter if remote URL includes are disabled?

MITRE notes that with path traversal the attacker can name a local file containing PHP statements, and normal program behavior may place attacker code into local files.

Why is a lookup table better than filtering the filename?

The user never supplies a path at all. Filters can miss variants, while a table rejects everything that is not on it.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.