编程 php://filter 默认只让串 16 层过滤器:升级前先数一遍链长

2026-10-24 21:09:04

PHP 8.6 把 php://filter 默认限制在 16 层,stream 错误也改成结构化 API

参考:

PHP 8.6 在 stream 层有两处变化值得提前处理:php://filter 加了默认链长上限,stream 失败不再只留一条 warning。前者会直接影响现有代码能否打开流,后者影响排错方式。

php://filter 默认最多 16 层过滤器

新增 stream context 选项 filter.max_filter_count,作用于 php://filter URL。默认值为 16。设置后,如果该 URL 会添加超过配置数量的过滤器,打开流失败并给出 warning。负值关闭这项检查。

这是安全项,不是性能项。convert.iconv、base64、dechunk 这类过滤器可以层层串联,把一次普通的文件读取变成任意代码执行:攻击者控制 filter 链,就能让被包含的文件内容按自己的意图解码、重组。LFI 场景长期依赖这个手法——包含点只允许读文件,但长链能把读到的字节拼成可执行内容。限制链长,等于把这条利用路径的长度空间压到很小。

运维和开发侧要做的事:

  • 全仓库搜 php://filter,包括配置文件、模板、日志、上传与图片处理代码。
  • 统计每条链实际的过滤器层数,看是否接近或超过 16。
  • 重点确认框架、模板引擎、日志库是否在内部拼链,尤其是把用户输入拼进过滤器名或编码参数的地方。这类代码平时不出现 php://filter 字样,升级后才报 warning。
  • 确有合法长链需求时,显式把 filter.max_filter_count 调大,但要清楚:放开的正是上面那条攻击路径的长度。

新的 Stream 错误 API

原来的问题是 fwrite、fread、fsockopen 失败后只剩一条 warning,调用方拿不到结构化错误,只能解析错误文本或自己补状态。

PHP 8.6 新增:

  • StreamError、StreamException 类
  • StreamErrorStore、StreamErrorMode、StreamErrorCode 枚举
  • stream_last_errors()、stream_clear_errors() 函数
  • 通过 stream context 的 error_mode、error_store、error_handler 控制行为

开启 error_store 后读取错误列表,大致是这样:

$ctx = stream_context_create([
'stream' => [
// error_mode 的具体枚举值以官方文档为准
'error_mode'  => /* StreamErrorMode 的存储模式 */,
'error_store' => true,
],
]);

$fp = @fopen('/tmp/out.bin', 'wb', false, $ctx);
if ($fp === false) {
$errors = stream_last_errors($ctx); // 参数与返回结构以官方文档为准
var_dump($errors);
stream_clear_errors($ctx);
}

stream_last_errors() 的签名、返回结构、错误条目字段(错误码、消息、操作类型等)都按官方文档来,不要按上面的示例假设字段布局。StreamErrorCode 枚举给的是可判定的错误类别,比匹配 warning 字符串稳定。

其他 socket 选项与 TLS

socket context 新增:

  • so_reuseaddr:可禁用地址复用(SO_REUSEADDR),在 Windows 上显式使用 SO_EXCLUSIVEADDRUSE
  • so_keepalive、tcp_keepidle、tcp_keepintvl、tcp_keepcnt:设置 keepalive
  • so_linger:设置 linger

OpenSSL 部分,流支持 TLS session resumption,相关选项包括 session_data、session_new_cb、session_cache、session_cache_size、session_timeout、session_id_context、session_get_cb、session_remove_cb、num_tickets。另支持 TLS external PSK(psk_client_cb、psk_server_cb)与 TLS 1.3 early data 0-RTT(early_data、max_early_data、early_data_cb);0-RTT 的结果在 stream_get_meta_data() 的 crypto 条目 early_data 键里,值为 accepted、rejected 或 not_sent。

版本状态

PHP 8.6 计划 2026-11-19 发布,最新预发布为 8.6.0RC4(2026-10-21)。php.watch 的 RFC 摘要把 filter 链限制描述为默认 16 层、用于阻止长链把文件读取变成代码执行,与迁移文档一致。升级前建议先在预发布版本上跑一遍涉及 php://filter 的路径。

复制全文 生成海报 PHP 安全 stream

推荐文章

程序员茄子在线接单