[PHP-DEV] Hardening PHP against filter chain attacks

I am working on some things to harden PHP against filter chain attacks:

Regards,

Sjoerd Langkemper

On Sat, 9 May 2026, Sjoerd Langkemper wrote:

I am working on some things to harden PHP against filter chain attacks:
• PHP RFC: Limit maximum number of filter chains <PHP: rfc:limit-maximum-number-of-filter-chains;
• Dechunk incorrectly truncates string when it starts with a hex character <Issues · php/php-src · GitHub;

Filter chains use php://filter/ URLs with many filters, which are
useful in several attacks, described in the RFC. I propose to limit
the number of filters, and make the dechunk filter less useful for
attacks. Please let me know what you think about this.

I think this makes sense. I'll reiterate my reply to a private email
about the questions under the "Open Issues" section:

Your RFC says that most actual use is 1 or 2, with exploits requiring
100+. Setting the default limit to say 16 should allow for wacky use,
but not the exploity variant.

I would also likely find it better if there was no INI setting. If you
*really* must use more than 16, then you can always recompile PHP.

To answer your questions from your RFC:

Exact Limit: Should the default be 5, 10, or 20? (Most exploits
require 50-100+).

As I said, I'm more confortable with a higher number and a really low
one.

What should the INI name be? filter.max_chain_depth

I prefer no new INI setting, but perhaps 'filter.max_chain_length'. It's
not so much a depth, but a length.

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?

I wouldn't want to make this to complicated, and just have a hard limit
that gives an error.

Should exceeding the limit throw a ValueError (consistent with modern
PHP 8 APIs) or a Warning (consistent with legacy stream handling)?

I think this should be thrown a ValueError.

cheers,
Derick

--
https://derickrethans.nl | https://xdebug.org | https://dram.io

Author of Xdebug. Like it? Consider supporting me: Xdebug: Support

mastodon: @derickr@phpc.social @xdebug@phpc.social

Your RFC says that most actual use is 1 or 2, with exploits
requiring 100+. Setting the default limit to say 16 should allow for
wacky use, but not the exploity variant.

I would also likely find it better if there was no INI setting. If
you *really* must use more than 16, then you can always recompile
PHP.

Some types of exploits using filters chain need way less than 100:

- PHP filter chains: file read from error-based oracle
- Challenges_2022_Public/web/minimal-php/solve/solution.py at main · DownUnderCTF/Challenges_2022_Public · GitHub
- GitHub - ambionics/cnext-exploits: Exploits for CNEXT (CVE-2024-2961), a buffer overflow in the glibc's iconv() · GitHub

Even GitHub - synacktiv/php_filter_chain_generator · GitHub, so generate the string `<?php phpinfo(); ?>`
is about 100, and could likely be golfed to be below this threshold.
And even those using 100+ chained filters can likely use multiple smaller chains instead.

Limiting to 100 will block the low-hanging fruits, but it unlikely to solve the issue once and for all.
Making it a configurable ini setting (with a sane default of 5?) would provide admins a way to tailor
it to their application.

I fail to come up with a legitimate usecase that would use more than 2 filters in the first place to be honest.

What should the INI name be? filter.max_chain_depth

I prefer no new INI setting, but perhaps 'filter.max_chain_length'.
It's not so much a depth, but a length.

+1 for filter.max_chain_length

I made some updates to the PHP RFC: Limit maximum number of filter chains.

Any more opinions on this? I am currently thinking about these specifics:

  • Set a limit of at most 16 filters in a php://filter URL. This is quite a high limit and won’t prevent all attacks, but also has a negligable chance of breaking legimitate functionality.
  • Start with raising a deprecation warning, and in a later version give an actual error. This is technically a BC break, and it can’t hurt to follow the proper path for this.
  • Hardcode the limit, don’t provide a INI setting. I think it is unlikely that people want to change this limit. I think it is acceptable to require recompilation to change the limit.
  • Raise a warning and return false, instead of throwing an exception. This is how stream functions currently work. It is not pretty, but it is consistent.
    Regards,

Sjoerd Langkemper

Hi,

On Tue, May 19, 2026 at 1:02 PM Sjoerd Langkemper <sjoerd-php@linuxonly.nl> wrote:

I made some updates to the PHP RFC: Limit maximum number of filter chains.

Any more opinions on this? I am currently thinking about these specifics:

  • Set a limit of at most 16 filters in a php://filter URL. This is quite a high limit and won’t prevent all attacks, but also has a negligable chance of breaking legimitate functionality.

This sounds reasonable as a default.

  • Start with raising a deprecation warning, and in a later version give an actual error. This is technically a BC break, and it can’t hurt to follow the proper path for this.

This might be a bit safer in terms of BC.

  • Hardcode the limit, don’t provide a INI setting. I think it is unlikely that people want to change this limit. I think it is acceptable to require recompilation to change the limit.

There should be some way how to change the limit. Might be worth to explore if it could be through stream context option.

  • Raise a warning and return false, instead of throwing an exception. This is how stream functions currently work. It is not pretty, but it is consistent.

It should use the new stream errors that got just approved and will get soon merged.

Kind regards,

Jakub

On Thu, May 21, 2026, at 22:59, Jakub Zelenka wrote:

Might be worth to explore if it could be through stream context option.

Yes, this is an excellent idea. I like this better than a PHP INI setting. I changed the PR and the RFC to include a stream context option.

https://wiki.php.net/rfc/limit-maximum-number-of-filter-chains
https://github.com/php/php-src/pull/22110

Regards,

Sjoerd Langkemper

I am starting the vote on this RFC next Friday, 5 June 2026. The RFC suggests limiting the maximum number of filters in php://filter URLs, for security reasons. The latest change is that the maximum number of filters can be configured through a stream context option.

RFC: https://wiki.php.net/rfc/limit-maximum-number-of-filter-chains
Discussion thread: https://news-web.php.net/php.internals/130813
Pull request: https://github.com/php/php-src/pull/22110

Regards,

Sjoerd Langkemper

Hi

Am 2026-06-02 14:18, schrieb Sjoerd Langkemper:

I am starting the vote on this RFC next Friday, 5 June 2026. The RFC suggests limiting the maximum number of filters in php://filter URLs, for security reasons. The latest change is that the maximum number of filters can be configured through a stream context option.

RFC: PHP: rfc:limit-maximum-number-of-filter-chains
Discussion thread: php.internals: Hardening PHP against filter chain attacks
Pull request: Limit maximum number of filter chains by Sjord · Pull Request #22110 · php/php-src · GitHub

Can you please clean out the “Open Issues” and add an example as to how the `filter.max_filter_count` context option would be used (i.e. what folks would need to write if they wanted to modify the default value) and possibly also one showing how `stream_filter_append()` can be used to programmatically add filters to avoid the limit? Please also fix the title of the voting widget, it says “Implement $feature as outlined in the RFC?”. For completeness it would also be helpful if the “Backward Incompatible Changes” section would mention if the new context option is forward compatible (i.e. whether it's silently ignored in existing PHP versions).

I'm generally in favor of the proposal, but want to see the examples to make an educated decision. These will also be helpful for the upgrading guide if / when the RFC is accepted.

Best regards
Tim Düsterhus

Thank you for your feedback. I made the changes you requested to the RFC. Changing the global limit with the context option looks like this:

stream_context_set_default([‘filter’ => [‘max_filter_count’ => 123]]);

And setting this (or any other non-existing option) has no effect on PHP versions that don’t support it.

Regards,

Sjoerd Langkemper

Hi

Am 2026-06-02 16:13, schrieb Sjoerd Langkemper:

Thank you for your feedback. I made the changes you requested to the RFC. Changing the global limit with the context option looks like this:

stream_context_set_default(['filter' => ['max_filter_count' => 123]]);

And setting this (or any other non-existing option) has no effect on PHP versions that don't support it.

Thank you, this is helpful. It previously wasn't clear to me from the RFC that the `.` in the context option name was meant to indicate nesting.

I've given the RFC another read and I don't have further comments. The proposal makes sense and 16 seems to be a reasonable default.

Best regards
Tim Düsterhus

Please vote on the RFC “Limit maximum number of filter chains”. A single yes/no vote is requested to approve of the limit on filter chains as proposed in the RFC. I opened the vote just now and it closes in three weeks on 26 June 2026.

RFC: https://wiki.php.net/rfc/limit-maximum-number-of-filter-chains
Discussion: https://news-web.php.net/php.internals/130813

Regards,

Sjoerd Langkemper

Hey Sjoerd,

···

Marco Pivetta

https://mastodon.social/@ocramius

https://ocramius.github.io/