PHP伪协议在CTF文件包含漏洞中的实战利用与绕过技巧

📅 2026/7/27 22:10:35
PHP伪协议在CTF文件包含漏洞中的实战利用与绕过技巧
1. 项目概述从“包含”到“掌控”的思维跃迁刚接触CTFCapture The Flag夺旗赛Web安全方向的新手常常会在“文件包含漏洞”这类题目面前卡壳。题目描述可能很简单比如一个形如?pageabout.php的链接提示你“找到藏在服务器上的flag”。你试了试?page../../etc/passwd发现不行又试了试绝对路径还是被拦截。这时候一股挫败感就上来了。其实很多情况下突破口并不在于你能否猜到flag文件的绝对路径而在于你是否理解服务器处理“包含”这个动作时的底层逻辑。PHP伪协议正是打开这扇逻辑之门的万能钥匙。它允许你将一个“文件路径”的请求转化为对数据流、压缩包、甚至PHP自身输入输出的操作。这篇文章我就以一个老CTF玩家的视角带你彻底搞懂如何利用PHP伪协议来绕过文件包含漏洞的各种限制最终拿到Flag。我会结合具体的实战场景和截图让你不仅知道怎么用更明白为什么能用以及在不同限制条件下该如何变通。2. 核心原理PHP伪协议为何能成为“漏洞放大器”在深入实操之前我们必须先建立正确的认知文件包含漏洞本身只是提供了一个“将外部数据作为代码执行或内容读取”的可能性。而PHP伪协议则极大地扩展了“外部数据”的来源和形式。你可以把它想象成一个多功能转换插头服务器本来只想让你插U盘包含本地文件但这个插头却能让你接上移动硬盘、网络存储、甚至把一段文本直接变成虚拟文件。2.1 文件包含漏洞的本质PHP中include、require、include_once、require_once这些函数其设计初衷是为了代码复用。比如你把数据库连接代码写在config.php里然后在其他页面通过include(‘config.php’)来引入。漏洞的产生是因为这个“被包含的文件名”是一个变量而这个变量来自于用户可控的输入如GET参数?file。如果程序没有对这个输入进行严格的过滤比如检查是否包含../ 是否以.php结尾等攻击者就可以操纵这个变量去包含服务器上的任意文件。关键点包含函数会将被包含文件的内容在当前作用域内“执行”如果是PHP代码或“输出”如果是文本。这意味着如果你能包含一个存储了PHP代码的日志文件或者一个你上传的图片马图片中包含PHP代码那么这些代码就会被执行。2.2 PHP伪协议的角色PHP内置了多种封装协议用于访问不同的输入/输出流。在文件包含的语境下我们关注的是那些能“读取数据”的协议。它们改变了“包含”的源传统包含include(‘/var/www/html/secret.txt’)- 从文件系统读取secret.txt的内容。伪协议包含include(‘php://filter/readconvert.base64-encode/resourcesecret.txt’)- 通过php://filter这个“过滤器”流读取secret.txt的内容并先进行base64编码。伪协议的核心优势在于绕过路径限制和进行数据转换。很多CTF题目会禁止目录穿越../或者要求被包含的文件必须以.php结尾。这时直接的文件路径包含就失效了。但伪协议提供了一种“间接”访问文件的方式它描述的“资源”依然是一个文件路径但协议处理器可能会以不同的方式解析它从而绕过一些简单的黑名单过滤。注意伪协议的使用需要对应的PHP配置支持。例如allow_url_fopen和allow_url_include这两个配置会影响php://input和http://等协议的使用。在CTF环境中出于考察目的这些配置常常是开启的但在实际渗透测试中需要先进行探测。3. 常用PHP伪协议详解与实战场景下面我们逐一拆解在CTF文件包含题中最常遇到的几种伪协议我会用具体的代码片段和假设的题目场景来说明。3.1 php://filter —— 读取文件内容的“瑞士军刀”这是最常用、最强大的伪协议主要用于读取任意文件的源代码特别是在无法直接输出文件内容比如服务器配置了不显示PHP代码或需要绕过某些过滤时。基本语法php://filter/read过滤器链/resource要读取的文件php://filter/write过滤器链/resource要写入的文件核心过滤器convert.base64-encodeconvert.base64-decode: 进行base64编解码。这是最重要的过滤器用于将文件内容以base64格式输出避免内容被直接执行或特殊字符被浏览器解释导致显示不全。string.rot13: 进行ROT13编码。string.toupper/string.tolower: 转换大小写。zlib.deflate/zlib.inflate: 进行压缩/解压。实战场景1读取本地系统文件如/etc/passwd假设题目URL为http://ctf.example.com/?filewelcome.php我们尝试读取/etc/passwd。直接包含?file../../../../etc/passwd可能被过滤使用filter协议?filephp://filter/readconvert.base64-encode/resource/etc/passwd提交后页面可能会显示一串base64编码的字符串如cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaAo...我们将这串字符复制下来使用在线工具或命令行echo “cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaAo...” | base64 -d进行解码即可看到/etc/passwd的明文内容。(示意图页面显示为一长串杂乱字符实为base64)实战场景2读取网站源码获取数据库密码或flag路径这是CTF中的高频考点。题目可能包含了一个index.php但flag藏在config.php或flag.php里。直接访问config.php可能返回空白或错误因为PHP代码被执行了而非显示。利用filter读取?filephp://filter/readconvert.base64-encode/resourceconfig.php解码后你就能看到config.php的完整源代码其中可能包含类似$flag “flag{this_is_real_flag}”;或数据库连接信息$db_pass “secret123”;的关键内容。实操心得为什么一定要用base64编码因为如果直接包含resourceconfig.php其中的PHP代码如?php $flag... ?会被服务器执行执行结果可能什么都没有会输出到页面你看不到源代码。而base64编码后PHP引擎将其视为一段文本数据而非代码原样输出编码后的字符串我们解码即可得源码。多层过滤器链你可以组合多个过滤器例如先rot13再base64readstring.rot13|convert.base64-encode/resourceconfig.php。这在某些简单的过滤器绕过题中可能会用到。3.2 php://input —— 执行任意代码的“后门”这个协议允许你访问请求的原始数据即POST Body。当allow_url_include设置为On时你可以通过它来包含POST数据中的PHP代码并执行。基本语法php://input实战场景绕过文件后缀限制题目代码可能如下?php $file $_GET[‘file’]; if (strpos($file, ‘.php’) ! false) { // 检查参数是否包含.php include($file . ‘.php’); // 安全地添加后缀不 } else { include($file); } ?看起来它强制了.php后缀。但我们可以利用php://input。使用GET参数传入协议?filephp://input同时将HTTP请求方法改为POST并在POST Body中写入要执行的PHP代码?php system(‘ls /’); ?发送请求后服务器会执行include(‘php://input’)而php://input流的内容就是我们POST的system(‘ls /’)代码这段代码会被执行并将根目录列表返回给我们。(示意图Burp Repeater标签页GET参数filephp://input下方Request Body为?php system(‘ls’);?)注意事项这个方法极其危险因为它相当于一个远程代码执行RCE漏洞。在CTF中一旦发现allow_url_includeOn且存在文件包含点应优先尝试此方法。请求的Content-Type无需特殊指定但务必确保是POST请求。代码执行后的输出会直接嵌入到包含点的页面中。3.3 data:// —— 将文本直接变为可执行文件data://协议类似于php://input但它允许直接将一段编码后的数据作为文件内容进行包含。它通常用于需要一次性提供数据和协议的场景无需发起两次请求如GETPOST。基本语法data://MIME类型;base64,base64编码的数据或data://MIME类型,URL编码的数据实战场景在GET请求中直接执行代码题目可能过滤了php://或不允许POST请求。构造Payload?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgL2ZsYWcnKTs/Pg其中PD9waHAgc3lzdGVtKCdjYXQgL2ZsYWcnKTs/Pg是?php system(‘cat /flag’);?的base64编码。服务器包含这个“文件”时实际上包含的是我们提供的base64解码后的PHP代码从而执行cat /flag命令。与php://input的对比data://更便捷一个GET请求即可完成适合在浏览器地址栏直接操作或用于某些限制POST的场合。php://input更灵活可以发送更大量、更复杂的POST数据。两者都需要allow_url_includeOn。在实际CTF中如果php://input被禁用可以尝试data://反之亦然。3.4 zip:// 与 phar:// —— 压缩包内的文件包含这两个协议用于访问压缩包ZIP或PHAR内的特定文件。这在“上传图片马但无法直接访问”或“需要包含特定路径下文件”的场景中非常有用。基本语法zip://压缩包文件绝对路径#压缩包内文件路径phar://压缩包文件绝对路径/压缩包内文件路径注意zip://中的路径需要使用绝对路径且#需要URL编码为%23。phar://则相对宽松。实战场景绕过文件上传限制题目允许上传图片.jpg后端会检查文件头。你制作了一个包含一句话PHP代码的shell.php然后将其压缩为shell.zip再将shell.zip重命名为shell.jpg上传。服务器保存路径为/uploads/abc123.jpg。找到文件包含点?filexxx构造Payload?filezip:///var/www/html/uploads/abc123.jpg%23shell.php/var/www/html/uploads/abc123.jpg是上传文件的绝对路径需要猜测或通过报错等信息获取。%23是#的URL编码。shell.php是压缩包内的文件名。服务器会解压这个“图片”读取其中的shell.php并执行。实操心得绝对路径的获取这是使用zip://和phar://的最大难点。可以通过报错信息、读取/proc/self/cwd符号链接、或利用其他漏洞如目录遍历来获取Web根目录的绝对路径。phar的通用性phar://协议功能更强不仅可以包含ZIP还能包含PHAR、TAR等格式且对路径格式要求更友好。在不确定的情况下可以优先尝试phar://。利用姿势扩展甚至可以不需要文件上传如果存在一个已知的ZIP文件在服务器上比如website.zip备份也可以尝试包含其中的文件。4. 实战解题流程与技巧汇编掌握了协议还需要一套系统的解题流程。下面我以一个虚构的、综合性的CTF题目为例展示完整的解题思路。题目描述访问http://靶机IP/显示一个页面有一个链接 “View some pages”点击后URL变为http://靶机IP/?pageabout.html。尝试发现?page../../etc/passwd被拦截页面返回 “Hacker!”。4.1 第一步信息收集与漏洞确认探测包含点尝试?pageindex.php、?page./index.php观察页面是否正常包含确认page参数确实用于文件包含。探测过滤规则尝试?page....//....//etc/passwd(双写绕过)失败。尝试?page/etc/passwd(绝对路径)失败。尝试?pagephp://filter/readconvert.base64-encode/resourceindex.php成功页面返回base64编码后的index.php源码。这说明服务器没有过滤php://协议且allow_url_fopen是开启的。解码index.php源码分析其逻辑。4.2 第二步源码审计与寻找flag线索解码得到的index.php关键部分可能如下?php $page $_GET[‘page’] ?? ‘home.html’; $safe_page str_replace(‘../’, ‘’, $page); // 简单过滤目录穿越 if (strpos($safe_page, ‘flag’) ! false) { // 禁止路径中出现flag字符串 die(‘Not allowed!’); } include(‘./pages/’ . $safe_page); ?分析过滤了../但只过滤一次双写绕过可能有效但之前试过失败了可能过滤了多次或用了其他方法。过滤了路径中的flag字符串。包含路径被限定在./pages/目录下。4.3 第三步利用伪协议构造Payload目标读取可能存在的flag.php或flag.txt。尝试直接读取?pagephp://filter/readconvert.base64-encode/resourceflag.php- 返回 “Not allowed!”因为路径中包含flag。利用过滤器链绕过关键字过滤php://filter的resource参数是最终要读取的文件。我们可以尝试对resource的值进行编码看服务器是否在解码后才进行关键字检查。但通常检查发生在include函数处理路径之前伪协议本身可能无法绕过这个检查。需要换个思路。读取pages目录下的其他文件先看看pages目录有什么。?pagephp://filter/readconvert.base64-encode/resource./pages/.(尝试读目录) - 失败resource必须是一个文件。读取可能存在的配置文件?pagephp://filter/readconvert.base64-encode/resourceconfig.php- 成功解码后发现定义了$flag_path ‘/secret_abc123/flag.txt’;。最终读取flag现在知道了flag的绝对路径且该路径不经过page参数的检查。直接使用filter协议读取?pagephp://filter/readconvert.base64-encode/resource/secret_abc123/flag.txt。解码后得到flagflag{y0u_4r3_m4st3r_0f_php_f1lt3r}。(示意图浏览器或Burp中请求上述Payload响应为一段base64解码后为flag{…})4.4 第四步进阶利用如果上述失败如果config.php里没有线索或者php://filter被禁用我们需要考虑其他协议。尝试php://input将请求改为POST?pagephp://inputBody里写?php system(‘find / -name “*flag*“ 2/dev/null’);?。如果成功会列出服务器上所有包含“flag”的文件路径。尝试data://?pagedata://text/plain;base64,PD9waHAgc3lzdGVtKCJmaW5kIC8gLW5hbWUgXCJmbGFnKlwiIDIL2Rldi9udWxsIik7Pz4(即上述find命令的base64编码)。尝试日志文件包含如果服务器开放了错误日志且我们知道其路径如/var/log/apache2/access.log可以尝试包含它。首先我们需要将PHP代码注入到日志中。通过User-Agent头携带PHP代码访问网站User-Agent: ?php phpinfo(); ?。然后包含日志文件?pagephp://filter/readconvert.base64-encode/resource/var/log/apache2/access.log在解码后的日志中搜索我们的请求如果代码被原样记录则可能被执行。这是一个非常经典的“日志投毒”技巧。5. 常见问题、绕过技巧与防御浅谈5.1 常见过滤与绕过方法CTF题目和WAFWeb应用防火墙会设置各种过滤下面是一些常见情况过滤规则可能绕过方式原理与示例过滤../双写绕过....//一次过滤后剩下../str_replace(‘../’, ‘’, ‘....//’) - ‘../‘过滤php://大小写混淆PHP://、Php://PHP协议处理可能不区分大小写过滤data://使用data://的另一种格式data:text/plain,?php…?(不常用)强制添加后缀.php利用?或#截断?filephp://input%00(需要PHP5.3.4且magic_quotes_gpcOff) 或?filephp://filter/…/resource/etc/passwd%00%00空字节会截断后面的.php。注意高版本PHP已修复。检查文件是否存在使用php://filter和php://input这些是协议流不是真实文件路径file_exists()检查可能返回false但include仍能工作。目录限制结合伪协议使用绝对路径即使包含函数前加了目录前缀如include(‘./pages/’. $file)使用php://filter/…/resource/etc/passwd时resource参数指定的绝对路径会覆盖前面的相对路径。重要提示空字节截断 (%00) 在PHP 5.3.4及以上版本已失效现代CTF环境很少能用但老题目或知识体系中仍会提及。5.2 实操中容易踩的坑Base64解码错误从网页复制base64字符串时注意是否包含换行、空格或HTML标签。最好使用Burp Suite的Decoder模块或浏览器的开发者工具直接查看响应体的原始内容Raw Response。绝对路径未知这是最大的障碍。除了通过报错、读取/proc/self/environ(环境变量)、/proc/self/cwd(当前工作目录) 等文件获取还可以尝试利用PHP的realpath函数特性或暴力猜解常见路径如/var/www/html/,/home/www/,/usr/local/nginx/html/。协议被禁用如果所有伪协议都返回错误或空白可能是allow_url_fopen和allow_url_include被关闭。这时需要回归到传统的本地文件包含思路比如寻找上传点、包含日志、包含/proc/self/fd/下的文件描述符等。代码不执行使用php://input或data://时确保POST的代码是完整的?php … ?标签。有时需要尝试短标签? … ?或script language”php”…/script取决于PHP配置。5.3 从攻击者视角看防御理解了攻击才能更好地防御。作为开发者避免文件包含漏洞需做到白名单制度不要使用用户输入直接作为包含的文件名。如果必须动态包含应预先定义好允许的文件列表白名单只包含列表内的文件。路径固定将被包含的文件限制在某个特定目录下并使用硬编码或白名单方式拼接完整路径确保用户输入无法跳出该目录。严格过滤如果无法使用白名单必须对输入进行严格过滤。不仅要过滤../还要过滤php://,data://,zip://,phar://等协议字符串以及空字节等特殊字符。注意过滤函数的顺序和逻辑避免被绕过。关闭危险配置在生产环境中务必在php.ini中设置allow_url_fopen Off和allow_url_include Off。这是最有效的一劳永逸的方法。使用安全的函数考虑使用file_get_contents()读取非PHP文件内容或者使用模板引擎彻底避免include/require的动态调用。文件包含漏洞配合PHP伪协议其威力远超简单的目录遍历。它模糊了“数据”与“代码”的边界将一次本地的文件读取升级为可能的信息泄露、代码执行甚至远程命令执行。对于CTF选手而言熟练掌握这套组合拳是攻克Web类题目的基本功。而对于开发者认识到这些协议的潜在风险则是编写安全代码的第一步。真正的安全始于对攻击技术的深刻理解。在实战中往往需要将多种协议、多种技巧组合使用并耐心地进行信息收集和测试才能从那个看似无害的?file参数中找到通往Flag的隐秘通道。