PHP Filter Chains: Turning Local File Inclusion into Remote Code Execution
Local file inclusion (LFI) is often treated as a read-only bug. An attacker can read /etc/passwd, maybe source code, and that is the end of the story. PHP filter chains changed that calculus. By abusing php://filter with a carefully constructed sequence of convert.iconv transformations, an attacker can synthesize arbitrary bytes inside a stream — including a valid PHP payload — without uploading a file or writing to disk. Combined with a file-inclusion sink, the result is remote code execution.
1. The LFI Sink
A typical vulnerable pattern passes a request parameter directly to include or require:
<?php
// vulnerable.php
$page = $_GET['page'] ?? 'home';
include(__DIR__ . '/pages/' . $page . '.php');
An attacker who controls page can traverse directories (../../etc/passwd) or, if URL wrappers are enabled, supply a wrapper such as php://filter. The suffix .php is appended, but wrappers and path normalization can still produce useful behavior depending on the code. The cleanest demonstration uses an include without a suffix:
<?php
include($_GET['file']);
2. What php://filter Does
php://filter applies stream filters to a resource. A simple example reads a file and base64-decodes it:
php://filter/convert.base64-decode/resource=/var/www/html/config.php
Filters can be chained. Each filter transforms the stream before the next one receives it. The convert.iconv.* filters convert between character encodings, and some encodings have stateful escape sequences that produce specific bytes. By chaining them, researchers found they could prepend controlled data to a stream.
3. The Synacktiv Technique
Synacktiv published a generator that builds a filter chain producing an arbitrary prefix. The chain starts with a base64-encoded payload and uses a sequence of iconv conversions to emit the desired bytes at the beginning of the stream. The generated URL looks like this (truncated):
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...|convert.base64-decode/resource=php://temp
The generator automates the search for a chain that yields the exact payload. In a lab, the workflow is:
# Install the generator in an isolated lab
git clone https://github.com/synacktiv/php_filter_chain_generator
python3 php_filter_chain_generator/php_filter_chain_generator.py \
--chain '<?php system($_GET["c"]); ?>'
The tool prints a php://filter/... URL. Passing that URL to the vulnerable include causes PHP to compile and execute the synthesized payload. The payload never touches disk, which defeats file-upload restrictions, extension checks, and many integrity monitors.
Use this technique only in authorized assessments and isolated labs. The generator and the underlying bug are powerful; running them against systems you do not own is illegal.
4. CVE-2024-2961 and the glibc Connection
In 2024, Ambionics published research on a glibc iconv vulnerability tracked as CVE-2024-2961. The flaw is an out-of-bounds write in the handling of the ISO-2022-CN-EXT character set, affecting glibc versions before 2.40. It can be triggered through PHP's iconv filters and, in the right conditions, used to corrupt memory and achieve code execution.
The significance for LFI is that the filter-chain technique does not always need a clean “synthesize a PHP tag” primitive. A memory-corruption bug in the conversion library provides another path from a file-read primitive to code execution. The exact exploit depends on the PHP version, the glibc build, and the allocator state, which is why the research was notable: it turned a class of “read-only” bugs into a reliable RCE chain on many distributions.
PHP 8.5 was released on 20 November 2025. Organizations should verify their PHP and glibc versions together; upgrading one without the other may leave the vulnerable conversion path reachable. The durable fix is to remove the include sink, not merely to hope the filter chain fails.
5. Why This Bypasses Common Defenses
- No upload required. The payload is generated in memory, so upload directories, MIME checks, and antivirus scans never see it.
- No writable directory required. The attacker does not need to drop a webshell.
- Signature-based WAF rules struggle. The URL is a long chain of legitimate filter names; blocking every variation is difficult without blocking legitimate use.
- The payload can be small. A short PHP tag is enough to call
system,assert, or a custom function.
6. Detection and Hardening
6.1 Remove dynamic includes
Never pass user input directly to include, require, include_once, or require_once. Use an allowlist map:
<?php
$pages = [
'home' => __DIR__ . '/pages/home.php',
'about' => __DIR__ . '/pages/about.php',
'contact' => __DIR__ . '/pages/contact.php',
];
$key = $_GET['page'] ?? 'home';
if (!array_key_exists($key, $pages)) {
http_response_code(404);
exit;
}
include $pages[$key];
This eliminates the sink entirely. No filter chain, encoding trick, or traversal sequence can select a file outside the map.
6.2 Harden the PHP configuration
- Set
allow_url_include = Offandallow_url_fopen = Offunless explicitly required. - Use
open_basedirto restrict filesystem access to the application directory. - Disable unused stream wrappers where the runtime permits it.
- Run PHP as a low-privilege user with no shell access and no write permission outside the upload directory.
6.3 Patch the conversion stack
Keep glibc at 2.40 or later on affected distributions, and keep PHP current. Track CVE-2024-2961 in your vulnerability management system even if the application does not use iconv directly; PHP's filter system can reach it.
6.4 Detect the technique
Log request parameters containing php://filter, repeated convert.iconv tokens, or unusually long query strings. Alert on include errors that reveal wrapper usage. A WAF rule can block obvious chains, but it is a mitigation, not a fix.
# Example detection grep for web server logs (review before deploying)
grep -E 'php://filter|convert\.iconv' /var/log/nginx/access.log | head
7. Conclusion
PHP filter chains collapse the distinction between “file read” and “code execution.” The include sink is the vulnerability; the filter chain is the delivery mechanism. CVE-2024-2961 showed that even the conversion library can become an exploitation primitive. The correct response is not to blacklist a particular payload string but to eliminate dynamic includes, constrain the filesystem, patch the runtime, and monitor for wrapper abuse. If an application can be made to include a path chosen by the user, assume that an attacker can eventually turn that path into code.