编程 PHP 8.6 把 php://filter 过滤器链限制到 16 个:未设上限会发弃用提示,显式设置后超限直接打开失败

2026-11-07 21:05:38

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

后续是移除该语法还是改为直接报错,以官方发布为准。

复制全文 生成海报 PHP 安全 文件流

推荐文章

程序员茄子在线接单