rfc:limit-maximum-number-of-filter-chains

PHP RFC: Limit maximum number of filter chains

Introduction

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., 5 or 10), we can effectively disable these automated exploitation tools while maintaining support for legitimate use cases.

Proposal

The proposal seeks to limit the number of individual filter components allowed in a single `php://filter` string, configured with an INI setting such as `filter.max_chain_depth`. 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 emit a Warning and the stream open operation should return false.

Examples

Still works:

var_dump(file_get_contents("php://filter/string.toupper|string.rot13/resource=data://text/plain,hello"));
// string(5) "URYYB"

Fails:

var_dump(file_get_contents("php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...[48 more].../resource=php://temp"));
// Warning: file_get_contents(...): filter chain depth of 50 exceeds the limit of 32
// bool(false)

Backward Incompatible Changes

This is a backward incompatible change for any application that legitimately chains more filters than the defined limit.

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.

A malicious payload typically takes more than 100 filters, so there is a large margin where malicious use is blocked but legitimate use is not.

Proposed PHP Version(s)

  • PHP 8.6

RFC Impact

To the Ecosystem

Most developers will notice no change. Security scanners and WAFs may be simplified as they can rely on the engine's built-in protection.

To Existing Extensions

Minimal to none, as this logic resides within the core PHP stream wrapper for php://.

To SAPIs

No specific impact to CLI, FPM, or others.

Open Issues

  • Exact Limit: Should the default be 5, 10, or 20? (Most exploits require 50-100+).
  • What should the INI name be? filter.max_chain_depth
  • How should this be introduced and tightened? E.g. start with high hard limit, or low limit and give a deprecation warning instead of an error?
  • Should exceeding the limit throw a ValueError (consistent with modern PHP 8 APIs) or a Warning (consistent with legacy stream handling)?

Future Scope

  • Deprecating the use of strings for complex filter definitions in favor of an Object-Oriented API for stream manipulation.
  • Considering a default allow_url_include = Off and eventually moving toward disabling allow_url_fopen by default in future major versions.

Voting Choices

Implement limiting the number of Filter Chains in php://filter?

Primary Vote requiring a 2/3 majority to accept the RFC:

Implement $feature as outlined in the RFC?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.

Patches and Tests

There's already an existing merge request, and I am willing to do some work on this.

https://github.com/php/php-src/pull/16699

Implementation

After the RFC is implemented, this section should contain:

  1. the version(s) it was merged into
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature

References

Rejected Features

Keep this updated with features that were discussed on the mail lists.

Changelog

  • 0.1 2026-05-05 Initial draft
rfc/limit-maximum-number-of-filter-chains.txt · Last modified: by sjoerd