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_EXCLUSIVEADDRUSEso_keepalive、tcp_keepidle、tcp_keepintvl、tcp_keepcnt:设置 keepaliveso_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 的路径。