PHP 8.6 把 php://filter 过滤器链限制到 16 个:未设上限会发弃用提示,显式设置后超限直接打开失败
来源:PHP 8.6 Deprecated Features;RFC:limit-maximum-number-of-filter-chains。PHP 8.6 计划于 2026-11-19 GA。
php://filter 的语法形如 php://filter/read=convert.base64-encode|convert.iconv.UTF8.CSISO2022KR/resource=index.php,多个过滤器用 | 分隔。read= 与 write= 两侧各自独立计数。
超限的两种表现
超过 16 个过滤器时,行为取决于是否显式配置了 filter.max_filter_count 这个 stream context 选项:
- 未显式配置:发出
E_DEPRECATED弃用提示。流仍然可以打开,原有代码逻辑不会当场中断,因此这条提示容易混在日志里,被忽略。 - 显式设置过该选项:超限直接是错误。打开流失败并给出 warning,不再是弃用提示。
也就是说,默认行为是“能运行但会产生弃用提示”,而一旦你主动设置了这个选项,就是“直接拒绝”。升级前先确认业务代码里有没有人写过这个 context 选项。
三种写法对照
URL 里的过滤器链会被计数:
$data = file_get_contents(
'php://filter/read=convert.base64-encode|convert.iconv.UTF8.CSISO2022KR/resource=index.php'
);
用 stream_filter_append() / stream_filter_prepend() 挂过滤器,不受这个上限约束:
$fp = fopen($path, 'rb');
stream_filter_append($fp, 'convert.base64-encode', STREAM_FILTER_READ);
stream_filter_append($fp, 'convert.iconv.UTF8.CSISO2022KR', STREAM_FILTER_READ);
确实需要提高上限时,通过 context 选项传入。作用于单次调用:
$ctx = stream_context_create([
'filter' => ['max_filter_count' => 32],
]);
$data = file_get_contents('php://filter/.../resource=index.php', false, $ctx);
或者用 stream_context_set_default() 全局改掉默认值。注意这会作用于之后所有使用默认 context 的流操作,影响面比前一种做法大得多:
stream_context_set_default([
'filter' => ['max_filter_count' => 32],
]);
默认上限设为 16 的原因
超长 filter 链几乎没有正当用途,却是把本地文件包含、任意文件读取升级成 RCE 的常用手段:攻击者会堆一串转换器,用来制造报错或绕过校验。16 这个默认值让这类 payload 在默认配置下无法再直接使用,同时也留下弃用期,让存量代码有时间调整。
正常业务会用到几条
编码转换、大小写折叠、压缩解压嵌套,这些场景的过滤器数量通常只有个位数。如果一个 URL 里的过滤器真的超过 16 个,更该考虑的是改实现,比如合并转换步骤、换掉封装层,而不是直接调高上限。
排查与观测
grep -rn "php://filter"扫一遍现有用法,数|的个数,注意read=和write=是分开计数的。- 在
error_log里搜 deprecation,确认线上是否已有路径触发了这条提示。 - 这条 deprecation 也会出现在攻击日志里。把它当作检测信号:正常业务不该出现超长 filter 链,出现了就值得查看请求内容。
PHP 9
后续是移除该语法还是改为直接报错,以官方发布为准。