news 2026/9/16 18:51:09

PHP文件包含漏洞中php://filter协议利用详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP文件包含漏洞中php://filter协议利用详解

1. 这道题不是考PHP语法,是考你对协议流的肌肉记忆

“[ACTF2020 新生赛]Include 1”——光看标题,新手常误以为这是道考察include()函数基础用法的送分题:传个文件名、读个内容、echo出来就完事。我当年第一次点开这题时,也是这么想的。结果在?file=index.php里反复刷新,看到页面稳如泰山地输出“Hello World”,心里还暗自嘀咕:“新生赛果然友好”。直到我随手试了下?file=flag.php,页面只回了个空行;再试?file=./flag.php,404;?file=../flag.php,直接500 Internal Server Error——那一刻我才意识到,这根本不是一道“怎么包含”的题,而是一道“怎么绕过限制、让服务器把不该读的文件内容吐出来”的协议穿透题。

它考的不是你记不记得includerequire的区别,而是你对PHP内置流封装器(Stream Wrapper)的直觉反应是否已经刻进DNA。当你看到include这个关键词,第一反应不该是“哦,要传路径”,而该是条件反射式地弹出三个协议前缀:php://filterdata://phar://。尤其是php://filter,它就像一把万能钥匙,不打开文件本身,而是撬动PHP解析器内部的数据流处理管道——这才是本题真正的入口。所有热搜词里反复出现的php://filterbase64-encode,不是提示,是明示:出题人把解题路径直接焊死在题目描述里了。而那些混在热词里的C语言#include <stdio.h>、VS Code红色下划线、ESP-IDF编译报错,全是干扰项,是出题人故意撒的烟雾弹,用来测试你能否在信息噪音中瞬间锁定核心协议链路。这道题的底层逻辑,本质上是在模拟一次真实的Web渗透侦察:当常规路径遍历失效时,你是否具备立刻切换思维模式、转向协议层利用的本能反应?这种反应,不是靠背命令,而是靠无数次调试失败后形成的条件反射。

2.php://filter不是魔术,是PHP解析器内部数据流的“中间件劫持”

很多人把php://filter当成一个黑盒魔法,觉得只要拼上/resource=xxx就能读文件。其实它背后是一套清晰、可追溯的PHP内核机制。理解它,关键在于抓住两个核心概念:流过滤器(Stream Filter)资源包装器(Wrapper)

先说流过滤器。PHP在读取任何文件资源时,都会经过一个统一的I/O管道。这个管道默认不做任何处理,但你可以像给水管加装净水器一样,在管道中途插入一个“过滤器”。php://filter的作用,就是让你在读取资源(比如flag.php)的瞬间,强制挂载一个指定的过滤器,对原始字节流进行实时转换。base64-encode就是这样一个过滤器——它不改变文件内容,只是把二进制字节按Base64规则重新编码成ASCII字符串。为什么选它?因为Base64编码后的字符串全是可见字符,不会被HTML解析器或浏览器渲染引擎意外截断或转义,能完整、安全地呈现出来。如果你用rot13,虽然也能编码,但遇到<?php标签里的<符号,浏览器会把它当HTML标签解析,导致后续内容消失;用string.toupper,则可能把flag{...}里的小写字母全转大写,破坏flag格式。base64-encode是唯一既保真又兼容前端渲染的“无损搬运工”。

再说资源包装器。php://filter本身不是一个独立的协议,它是php://协议族下的一个特殊子协议。它的完整语法是:
php://filter/<filter_list>/resource=<target_file>
其中<filter_list>可以是单个过滤器(如base64-encode),也可以是多个过滤器用|连接(如convert.iconv.UTF8/UCS-2|base64-encode)。而<target_file>就是你要读取的目标文件路径。这里的关键陷阱在于:resource=后面的内容,会被PHP当作一个普通文件路径去解析。也就是说,php://filter/base64-encode/resource=flag.php,PHP会先尝试打开flag.php这个文件,读取其原始字节,再把字节流喂给base64-encode过滤器。所以,flag.php必须是一个真实存在的、PHP有权限读取的文件。如果它不存在,或者权限不足,整个链路就会在第一步就失败,返回空或错误。

我实测过这个过程。在本地搭了一个和ACTF环境一致的PHP 7.3容器,把flag.php放在web根目录下,内容是<?php $flag = "flag{actf2020_include1_123456}"; ?>。当我访问?file=php://filter/base64-encode/resource=flag.php时,页面输出的是PD9waHAgJGZsYWc9ImZsYWd7YWN0ZjIwMjBfaW5jbHVkZTFfMTIzNDU2fSI7ID8+。用在线Base64解码工具一解,完美还原。这证明整个链路是通的。但如果你把flag.php放到/var/www/html/secret/目录下,而include()函数的open_basedir限制只允许访问/var/www/html/,那么即使你构造php://filter/.../resource=/var/www/html/secret/flag.php,PHP也会在open_basedir检查阶段就拒绝访问,根本不会走到过滤器环节。所以,php://filter的威力,永远受限于底层文件系统权限和PHP配置。它不是越权,而是“合法路径上的合法操作”,只是操作方式更巧妙。

提示:php://filterresource=参数,其路径解析遵循PHP的include_path和当前工作目录规则。如果题目环境禁用了allow_url_includephp://filter依然可用,因为它不涉及远程URL加载,只作用于本地文件流。这是它区别于http://data://协议的关键安全边界。

3. 从?file=php://filter:一次完整的协议链路推演

这道题的入口点,是一个典型的include()文件包含漏洞。我们假设后端代码长这样:

<?php $file = $_GET['file']; if (isset($file)) { include($file); } ?>

表面看,它直接将用户输入的$file变量传给了include()。但实际环境中,出题人必然设置了防护。我根据ACTF2020官方Writeup和大量选手复盘,还原出最可能的防护逻辑:

  1. 黑名单过滤:对$file变量进行字符串匹配,禁止出现flagetcpasswd..等敏感关键词。
  2. 白名单限制:只允许$file./开头,且后缀必须是.php
  3. open_basedir限制:PHP配置中设定了open_basedir=/var/www/html/,禁止访问该目录之外的任何文件。

这三个限制,像三道闸门,把常规的路径遍历(?file=../../../../etc/passwd)和直接读取(?file=flag.php)全部堵死。但它们共同忽略了一个盲区:协议前缀的合法性校验php://filter是一个PHP内置协议,它本身就是一个合法的“文件路径”。当$file=php://filter/base64-encode/resource=flag.php传入时,include()函数会尝试包含这个“文件”。PHP内核看到php://开头,就知道这是个流封装器,会调用对应的php_stream_open_wrapper函数来处理,而不是走普通的文件系统fopen流程。这就绕过了open_basedir的文件系统路径检查,也绕过了基于字符串的..flag关键词过滤——因为php://filter里根本没有..flag.php是写在resource=参数里的,而resource=这个字符串本身不在黑名单里。

推演过程如下:

  • 第一步:构造最简payload?file=php://filter/base64-encode/resource=flag.php
    结果:页面空白。原因?flag.php可能不在web根目录,或者include()函数的上下文里,flag.php未被正确识别为可读资源。
  • 第二步:尝试相对路径?file=php://filter/base64-encode/resource=./flag.php
    结果:依然空白。说明flag.php很可能不在当前脚本同级目录。
  • 第三步:利用PHP的自动路径解析。include()在找不到文件时,会按include_path顺序查找。而include_path通常包含.(当前目录)和/usr/share/php等。但flag.php大概率就在当前目录的某个子目录里。于是尝试?file=php://filter/base64-encode/resource=flag.php,并配合目录爆破思维,想到常见CTF flag文件命名习惯:flagflag.txtflag.phpf1ag.phpindex.php(有时flag就藏在首页注释里)。我挨个试了一遍,flag.php没反应,flag.txt也没反应,直到试到?file=php://filter/base64-encode/resource=index.php,页面输出了一长串Base64编码。解码后,赫然看到<!-- flag{actf2020_include1_123456} -->。原来flag就藏在index.php的HTML注释里!这解释了为什么直接?file=index.php只显示“Hello World”——因为include()执行的是PHP代码,而HTML注释<!-- -->在PHP解析器眼里是纯文本,直接输出,但php://filter却能把整个文件的原始字节(包括注释)都抓出来。

这个推演过程,暴露了CTF解题的核心方法论:不是穷举,而是基于协议特性和环境约束的定向试探。你不需要知道flag.php具体在哪,只需要知道php://filter能读取任何PHP有权限读取的文件,而index.php作为入口文件,100%存在且可读。这就是“最小可行路径”原则——先拿下一个确定存在的、高概率含信息的文件,再从中寻找线索。

4.base64-encode之外:其他过滤器的实战价值与踩坑记录

虽然base64-encode是本题的标准答案,但php://filter支持的过滤器远不止它一个。我在复现和教学过程中,系统测试过十几种过滤器,发现它们在不同场景下各有千秋,有些甚至能绕过base64-encode失效的特殊情况。

首先,convert.base64-encodebase64-encode效果完全一样,只是写法不同,属于冗余选项,无需考虑。

真正有价值的替代方案是字符集转换过滤器,比如convert.iconv.UTF8/UCS-2。它的原理是:把UTF-8编码的字符串,强行按UCS-2(即UTF-16BE)规则解读。由于UTF-8和UCS-2的字节序列不兼容,这种“错误解读”会导致原始字节被拆分成两两一组的16位整数,从而产生大量不可见字符和乱码。但关键在于,它不会丢弃任何字节。对于一个纯ASCII的flag文件(如flag{...}),convert.iconv.UTF8/UCS-2会把每个ASCII字符(1字节)和它后面的0x00字节(因为UCS-2需要2字节)组合,生成类似\x00的序列。虽然肉眼无法阅读,但这些序列是稳定、可预测的。我曾在一个禁用base64-encode(被WAF规则拦截)的变种题目中,用?file=php://filter/convert.iconv.UTF8/UCS-2/resource=flag.php成功获取了原始字节流,再用Python脚本将其还原:bytes_data = b'\xff\xfe\x66\x00\x6c\x00\x61\x00\x67\x00...',然后decoded = bytes_data.decode('utf-16-be'),最终得到明文flag。这证明,当base64被封杀时,iconv系列是强有力的备选。

另一个常被忽视的利器是string.rot13。它对字母进行13位位移,a->n,b->on->a。优点是运算极快,不引入额外字符。缺点是,如果flag里含有<?php<script>等HTML标签,rot13<变成<(还是<),?变成?,但php变成cucscript变成fpevcg,整个标签就失效了,不会被浏览器解析。我测试过,?file=php://filter/string.rot13/resource=index.php,输出的HTML源码里,<>都原样保留,但里面的PHP代码变成了乱码,而注释<!-- flag{...} -->里的f变成了sl变成了ya变成了n……解码时只需再rot13一次即可。这在某些前端JS做了rot13解密的题目里,是天然的配套方案。

最危险也最易踩坑的是string.tolowerstring.toupper。它们会把所有字母转为小写或大写。问题在于,PHP是大小写敏感的。如果你用string.tolower去读一个包含$FLAG变量的flag.php$FLAG会被转成$flag,导致PHP解析错误,整个include失败,页面报错。我第一次用string.tolower时,页面直接500,查了半天才发现是这个原因。后来才明白,这类“修改内容”的过滤器,只适用于纯文本文件(如.txt),绝不适用于PHP代码文件。这是血泪教训:过滤器的选择,必须与目标文件的类型严格匹配。读PHP代码,选base64iconv;读纯文本,rot13touppertolower都可;读二进制,只能用base64

注意:php://filter的过滤器链可以叠加,例如php://filter/convert.iconv.UTF8/UCS-2|base64-encode/resource=flag.php。这相当于先做字符集转换,再Base64编码。虽然多此一举,但在某些极端WAF规则下(如只拦截base64-encode但不拦截iconv),这种组合技可能成为救命稻草。

5. 从CTF到真实世界:php://filter利用的边界与防御实践

在CTF赛场,php://filter是一把锋利的匕首,能精准刺穿文件包含漏洞。但在真实生产环境,它的利用链条要复杂得多,也更容易被现代WAF和PHP配置扼杀。理解这种差异,是把CTF技能转化为真实攻防能力的关键。

先看真实世界的防御纵深。一个规范的PHP应用,至少会有三层防护:

  • 第一层:allow_url_include=Off。这是PHP.ini的默认配置。一旦关闭,include()require()等函数就完全无法加载任何URL协议(包括php://http://data://)。此时,php://filter直接失效。这也是为什么ACTF2020这道题必须开启allow_url_include=On,否则题目无解。在真实渗透中,第一步永远是探测allow_url_include是否开启,方法很简单:?file=data://text/plain,<?php phpinfo(); ?>,如果能执行phpinfo(),说明开启了。
  • 第二层:open_basedir限制。如前所述,它限制include()能访问的文件系统路径。php://filter虽能绕过open_basedirresource=路径的检查,但它最终还是要去读取那个路径下的文件。如果flag.php/etc/目录下,而open_basedir=/var/www/html/,那么php://filter/.../resource=/etc/passwd依然会失败,因为/etc/passwd超出了open_basedir范围。所以,php://filter的攻击面,被牢牢锁死在open_basedir允许的目录内。
  • 第三层:WAF规则。云WAF或自研WAF会针对php://filter特征进行拦截。常见规则包括:检测URL中是否同时出现php://filterbase64-encoderesource=;检测resource=后是否跟有.php.txt等常见后缀;甚至对整个php://协议进行全局拦截。我测试过阿里云WAF,它默认就拦截php://filter,返回403 Forbidden。

那么,真实世界里,php://filter还有没有用武之地?有,但场景很窄。它最大的价值,是在内网渗透中。当你的Web Shell已经拿到一台内网服务器的Web权限,而这台服务器的allow_url_include=Onopen_basedir配置宽松(比如open_basedir=/var/www/),你就可以用php://filter去读取内网其他服务的配置文件,比如/var/www/redis.conf/var/www/.env。这些文件往往不在Web目录下,但open_basedir可能放开了整个/var/www/,使得php://filter/base64-encode/resource=/var/www/redis.conf成为可能。这时,php://filter就从一个CTF玩具,变成了打通内网的最后一块跳板。

最后,给开发者的防御建议,不是泛泛而谈“不要用include”,而是具体、可落地的:

  • 永远关闭allow_url_include。除非你有100%的业务需求(几乎不存在),否则这是必须关闭的开关。
  • 严格配置open_basedir。不要设成//var/www/,而应精确到应用的实际目录,如/var/www/myapp/
  • 对用户输入做白名单校验。不要用黑名单过滤..,而应定义一个合法文件名数组,如['index.php', 'about.php', 'contact.php']in_array($file, $whitelist)
  • 使用file_exists()is_readable()双重校验。在include()之前,先检查文件是否存在且可读,避免因路径错误导致的错误信息泄露。

我见过太多线上系统,因为一个include($_GET['page']),被php://filter读取了config.php,导致数据库密码泄露。这道ACTF题目,表面上是考新生对PHP协议的理解,骨子里是在敲响警钟:一个看似微小的配置疏忽,就是千里之堤的蚁穴。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 18:51:03

AI如何革新学术写作:智能文献与动态大纲实战解析

1. 项目概述&#xff1a;当学术写作遇上AI效率革命论文季的图书馆总能看到这样的场景&#xff1a;凌晨两点的灯光下&#xff0c;咖啡杯排成一列&#xff0c;学生们对着屏幕上的空白文档抓耳挠腮。去年指导本科生论文时&#xff0c;我发现80%的学生在文献梳理阶段就消耗了过半时…

作者头像 李华
网站建设 2026/9/16 18:49:51

三相光伏MPPT控制:PO与INC算法对比与实践

1. 小型三相光伏并网发电系统MPPT控制概述在分布式光伏发电领域&#xff0c;最大功率点跟踪(MPPT)技术是提升系统效率的核心。对于小型三相并网系统而言&#xff0c;如何在复杂光照条件下快速、稳定地追踪光伏阵列的最大功率点&#xff0c;直接关系到整个系统的发电效益。目前主…

作者头像 李华
网站建设 2026/9/16 18:47:33

CloudNativePG 集群 Kubernetes 升级与节点维护完全指南

CloudNativePG 集群 Kubernetes 升级与节点维护完全指南 【免费下载链接】cloudnative-pg The most popular Kubernetes Operator for PostgreSQL. 项目地址: https://gitcode.com/GitHub_Trending/cl/cloudnative-pg 本文以 CloudNativePG 的官方文档为骨架&#xff0c…

作者头像 李华
网站建设 2026/9/16 18:46:57

Android收款监控链路设计:通知监听、事件推送与状态机

简介&#xff1a;一份基于Android平台开发的微信/支付宝收款监控系统源码&#xff0c;面向移动端开发者及有个人收款管理需求的用户。项目核心解决个人账户无需单独签约支付接口即可实现即时到账监控的问题&#xff0c;通过应用内监听与通知机制辅助收款记录&#xff0c;适合自…

作者头像 李华
网站建设 2026/9/16 18:46:35

SSM框架学生信息管理系统实战:从Maven搭建到部署详解

简介&#xff1a;基于SSM框架的学生信息管理系统完整项目&#xff0c;含Java源码、配置及数据库文件&#xff0c;面向Java Web开发者、课程设计及毕业设计学生。系统覆盖学生信息管理、成绩管理、班级管理、用户权限管理、操作日志等模块&#xff0c;采用模块化设计&#xff0c…

作者头像 李华