This RFC proposes to introduce a limit on the number of filters that can be chained within a php://filter URL. The primary goal is to mitigate a class of security vulnerabilities where Local File Inclusion (LFI) is elevated to Remote Code Execution (RCE) via complex, generated filter chains.
Currently, an attacker can use a long sequence of convert.iconv.* and convert.base64 filters to craft arbitrary content (like a PHP shell) out of any file on the disk, or even from an empty resource. By limiting the chain length to a reasonable number (e.g., 16), we can effectively disable these automated exploitation tools while maintaining support for legitimate use cases.
PHP filters are a way of transforming streams, for example through base64 encoding. By using many filters, it is possible to transform any content, such as an empty resource, into any other content, such as a webshell. This is mainly useful in these situations:
include $_GET['a'], this makes it possible to include arbitrary code instead of just files on the disk of the server.file($_GET['a']), it makes it possible to determine the contents of the given file, even if the application does not display the content. An attacker can extract information about the file by triggering content-dependant errors in the filter chain.These are well documented actual attacks, but filter chains are sufficiently powerful that they are likely helpful in exploiting other vulnerabilities.
The proposal seeks to limit the number of individual filter components allowed in a single `php://filter` string. This limit applies specifically to the parsing of php://filter URLs (e.g., via fopen, include, file_get_contents). When the limit is exceeded, PHP should give a deprecation warning, and later an error.
The limit will default to 16, and can be changed using the stream context option filter.max_filter_count. There will not be a PHP INI setting for this. The limit is sufficiently high that legitimate users are unlikely to run into this limit.
Using many filters will still be possible using stream_filter_append / prepend.
Still works:
var_dump(file_get_contents("php://filter/string.toupper|string.rot13/resource=data://text/plain,hello")); // string(5) "URYYB"
Deprecation warning:
file_get_contents("php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...[48 more].../resource=php://temp"); // Deprecated: Using more than 16 filters in a php://filter URL is deprecated, set this limit using the stream context option max_filter_count, or use stream_filter_append in ...
Error (when filter.max_filter_count is set, or in later PHP version):
var_dump(file_get_contents("php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...[48 more].../resource=php://temp")); // Warning: file_get_contents(...): Failed to open stream: too many filters in ... // bool(false)
A survey of open-source code (Sourcegraph) indicates that the vast majority of php://filter usages involve 1 filter, and occasionally 2. Legitimate use cases for 5 or more filters were not found outside of CTF challenges and exploit payloads.
Here are some numbers for malicious use:
For the last two, improvement is still possible. I.e. attackers can come up with better attacks that use fewer filters.
This is a backward incompatible change for any application that legitimately chains more filters than the defined limit.
There is a clear upgrade path for users who want to use many filters on a stream: configure the stream context option filter.max_filter_count, or use stream_filter_append / prepend.
filter.max_filter_count is set.Most developers will notice no change. Security scanners and WAFs may be simplified as they can rely on the engine's built-in protection.
Minimal to none, as this logic resides within the core PHP stream wrapper for php://.
No specific impact to CLI, FPM, or others.
dechunk.Reception is generally positive, but people disagree on the amount of filters that should be allowed and what exactly should happen when that limit is exceeded.
About the limit:
What should happen when the limit is reached?
Implement limiting the number of Filter Chains in php://filter?
Primary Vote requiring a 2/3 majority to accept the RFC:
There's already an existing merge request, and I am willing to do some work on this.
After the RFC is implemented, this section should contain:
Keep this updated with features that were discussed on the mail lists.