上个月帮一个朋友排查线上问题,遇到个挺有意思的 case。他们的系统有个功能:根据用户传入的参数动态加载模板文件。代码长这样:

$template = $_GET['tpl'];
include('/var/www/templates/' . $template . '.php');

看着挺正常对吧?但问题是,有人传了个 ../../../../etc/passwd 进来,直接把服务器密码文件读走了。更骚的是,还有人传了 php://filter/convert.base64-encode/resource=../config/database.php,数据库配置源码全给扒光。

朋友一脸懵逼:php:// 这是个啥?怎么还能这么玩?

其实,这是 PHP 伪协议在作祟。哪怕到了 2026 年,PHP 版本已经迭代多次,但这套机制依然坚挺,且在新版本中细节更加丰富。很多开发者只把它当成“读取文件”的另类写法,却忽略了它背后巨大的能量与风险。今天我们就从实战角度,把 PHP 伪协议掰开揉碎了讲讲,既包括它的核心用法,也涵盖那些让你防不胜防的安全坑点。

伪协议到底是什么?

先别被“伪协议”这三个字唬住。说白了,它就是 PHP 定义的一套“特殊路径标识”,让你能用 fopen()file_get_contents()include() 这些原本只操作本地文件的函数,去访问不只是普通文件的东西。

比如你想读一个文件:

// 普通读文件
$content = file_get_contents('/etc/passwd');

// 用伪协议读,效果一样
$content = file_get_contents('file:///etc/passwd');

file:// 是最基础的伪协议,默认甚至可以省略。但 PHP 内置了十几种伪协议,有的能读压缩包里的文件,有的能直接处理 POST 数据,有的能对文件内容做实时过滤。理解它们,不仅能帮你写出更灵活的代码,更能让你在审查代码时一眼看出潜在漏洞。

核心伪协议详解与实战

file://:最老实本分的那个

file:// 没啥花活,就是访问本地文件系统。

// Linux
file_get_contents('file:///etc/passwd');

// Windows
file_get_contents('file://C:/Windows/win.ini');

这个协议默认开启,而且不受 allow_url_fopenallow_url_include 的影响——就算这两项配置全关了,file:// 照样能用。这也是为什么很多以为关了远程包含就万事大吉的开发者的盲区:攻击者依然可以利用它读取本地敏感文件。

php://filter:真正的“瑞士军刀”

这是最常用也最危险的协议,没有之一。它允许你在读取文件的同时,对内容应用一个或多个过滤器。

// 把文件内容 base64 编码后输出
file_get_contents('php://filter/read=convert.base64-encode/resource=secret.php');

// 多个过滤器链式调用:先转 base64,再转 rot13
file_get_contents('php://filter/read=convert.base64-encode|string.rot13/resource=secret.php');

为什么需要这个?最常见的场景是:通过文件包含漏洞读取 PHP 源码时,直接 include PHP 文件会被解析执行,你看不到源码。但加上 base64 过滤器,输出的就是编码后的内容,不会执行,解码就能看到源码。

在 2026 年的今天,php://filter 依然是最实用的调试工具之一。有时候线上环境不方便开 Xdebug,但你又想看某个配置文件的原始内容,就可以临时写个脚本用 filter 读一下。

支持的过滤器类型非常多:

  • 字符串过滤器string.rot13string.toupperstring.tolower
  • 转换过滤器convert.base64-encode/decodeconvert.quoted-printable-encode/decode
  • 压缩过滤器zlib.deflatebzip2.compress
  • 字符集转换convert.iconv.GBK/UTF-8(处理老系统乱码神器)

php://input:接收原始 POST 数据

这个协议用来读取 HTTP 请求的原始 body。

// 不管 Content-Type 是啥,都能拿到原始数据
$rawData = file_get_contents('php://input');
$jsonData = json_decode($rawData, true);

以前很多新手踩坑:前端传 JSON,后端用 $_POST 拿,结果空的。因为 $_POST 只解析 application/x-www-form-urlencodedmultipart/form-data。要拿 JSON,就得从 php://input 读。

有个细节需要注意:当 enctype="multipart/form-data" 时,php://input 是无效的,读不到东西。在前后端分离已成标配的今天,这个协议的使用频率比十年前高太多了。

data://:把字符串当文件用

这个协议可以把一个字符串当成文件内容来处理,常用于绕过文件上传限制或在 CTF 中快速执行代码。

// 直接把字符串当文件包含
include('data://text/plain,<?php phpinfo(); ?>');

// base64 版本(绕过特殊字符过滤)
include('data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=');

在实际开发中也有正经用途——比如测试的时候,不想真的写文件,直接用 data 协议构造测试数据传入函数,验证逻辑是否正确。

zip:// 和 phar://:压缩包里的秘密

这两个协议用来读取压缩包内的文件,无需解压。

// zip 协议:注意#要编码成%23
$content = file_get_contents('zip:///path/to/archive.zip%23file.txt');

// phar 协议:路径格式不同
$content = file_get_contents('phar:///path/to/archive.phar/file.txt');

两者区别在于分隔符:zip://#(URL 编码为 %23),phar:///

更危险的是,PHP 解析 Phar 文件时会触发反序列化操作。所以如果程序有文件包含漏洞,结合 Phar 协议和反序列化漏洞,能打出组合拳,直接导致远程代码执行。

深度进阶:过滤器链与自定义流

链式过滤器的艺术

php://filter 最骚的地方是支持多个过滤器组合,像流水线一样处理数据。

// 先 base64 解码,再 rot13,再转小写
$content = file_get_contents(
    'php://filter/read=convert.base64-decode|string.rot13|string.tolower/resource=encrypted.txt'
);

这在处理特定格式的数据时非常有用。比如有些老系统会把配置文件用简单算法混淆,你可以用 filter 链直接在读取时还原,省去了写额外解密脚本的麻烦。

自定义协议:用类实现你的流

如果你觉得内置协议不够用,PHP 还允许你自己注册协议——这就是 stream_wrapper_register()

比如你想实现一个 var:// 协议,读写全局变量:

class VariableStream {
    private $position = 0;
    private $varname;

    public function stream_open($path, $mode, $options, &$opened_path) {
        $url = parse_url($path);
        $this->varname = $url["host"];
        $this->position = 0;
        return true;
    }

    public function stream_read($count) {
        $ret = substr($GLOBALS[$this->varname], $this->position, $count);
        $this->position += strlen($ret);
        return $ret;
    }
    
    // 还需实现 stream_write, stream_close 等方法...
}

// 注册协议
stream_wrapper_register("var", "VariableStream");

// 使用
$GLOBALS['foo'] = "Hello World";
echo file_get_contents("var://foo"); // 输出了 Hello World

这个例子虽然像个玩具,但思路打开了:你可以实现数据库流、Redis 流、甚至是 API 流——用统一的文件操作接口访问任何数据源,让代码架构更加优雅。

安全:伪协议的 AB 面

伪协议是双刃剑,用好了是神器,用坏了是灾难。在文件包含漏洞中,它往往是攻击者的放大器。

典型攻击场景

假设你有这样一段漏洞代码:

include($_GET['file']);

攻击者可以利用伪协议做很多坏事:

  • 读源码?file=php://filter/convert.base64-encode/resource=index.php
  • 执行任意代码?file=data://text/plain,<?php system('id'); ?>
  • 读系统文件?file=file:///etc/passwd

哪怕你限制了后缀名,攻击者依然可以通过 php://filter 绕过,因为最终包含的不是 .php 文件,而是一个经过过滤处理的流。

防护措施:三道防线

第一道防线:配置加固

php.ini 中,必须严格管控以下选项:

; 必须关!控制 include/require 能否访问远程文件或 php://input
allow_url_include = Off

; 可以开,但要配合其他措施,控制 file_get_contents 等函数
allow_url_fopen = On

生产环境中,allow_url_include 必须设为 Off,这是底线。

第二道防线:代码层面的白名单

永远不要信任用户输入的路径。使用白名单校验是最稳妥的办法:

$allowed_pages = ['home', 'about', 'contact'];
$page = $_GET['page'] ?? 'home';

if (in_array($page, $allowed_pages)) {
    include('pages/' . $page . '.php');
} else {
    http_response_code(404);
    echo 'Page not found';
}

如果必须动态加载,务必使用绝对路径并校验前缀:

$base = '/var/www/templates/';
$input = $_GET['tpl'];

// 防止路径穿越
$file = realpath($base . $input);

if ($file && strpos($file, $base) === 0 && file_exists($file)) {
    include($file);
} else {
    die('Invalid file path');
}

第三道防线:输入过滤与清洗

除了 $_GET$_POST,很多地方也藏着危险:$_SERVER['HTTP_REFERER']$_SERVER['QUERY_STRING']$_FILES['file']['name'] 等。这些字段用之前都要过一遍 filter_var() 或正则表达式,剔除非法字符。

特别是当你要使用 php://input 读取 JSON 时,也要做校验:

$raw = file_get_contents('php://input');
$data = json_decode($raw, true);

if (json_last_error() !== JSON_ERROR_NONE) {
    http_response_code(400);
    die('Invalid JSON');
}
// 再对 $data 做字段校验...

2026 年新视野:伪协议的“正经”用途

说了这么多漏洞,但伪协议本身是中性的。在 2026 年的现代 PHP 开发中,它们在不少正经场景里发挥着重要作用。

流式处理大文件

php://tempphp://memory 处理临时数据,可以避免内存爆炸:

// 把大文件内容写到临时流
$fp = fopen('php://temp', 'r+');
fwrite($fp, $hugeData);
rewind($fp);

// 处理流中的数据...
while (!feof($fp)) {
    $chunk = fread($fp, 8192);
    // process $chunk
}
fclose($fp);

php://temp 会在数据超过 2MB 时自动转成临时文件,比纯内存的 php://memory 更安全,适合处理不确定大小的数据流。

API 响应的动态压缩

利用 compress.zlib:// 可以直接读取压缩过的远程资源,无需落地磁盘:

// 读取压缩包里的文件,不解压到磁盘
$content = file_get_contents('compress.zlib://http://example.com/data.gz');

这在微服务架构中非常有用,可以减少网络传输体积,提升系统整体性能。

伪协议就像一把精密的手术刀,在资深开发者手中能切除病灶、优化架构,但在缺乏安全意识的新手手里,却可能变成自残的利器。理解它们的原理,掌握防御的技巧,才能在 2026 年及以后的 PHP 开发之路上走得更稳、更远。

更多推荐