DVWA文件包含漏洞实战:从LFI到日志投毒完整解析 做Web安全测试绕不开DVWA。它不是靠背题就能吃透的靶机尤其文件包含这道题很多新手三分钟看完源码、三小时解不出一道题然后开始怀疑自己适不适合干安全。我当年也在这个页面上磨过很久后来反复对比低中高三关的源码才想明白文件包含的核心其实只有一句话include就是“把另一个文件的内容塞进来执行”而所有攻击与绕过方式都是围绕“谁能决定塞哪个文件”展开的。这里我就以DVWA靶机上的文件包含漏洞为例把低级、中级、高级测试完整走一遍并把每一步背后的绕过逻辑讲清楚。零基础可以照着顺序做老手也可以用来查漏补缺。先收藏再对照着做问题是真能解决的。1. 为什么选DVWA练文件包含从零搭建靶场环境1.1 DVWA是什么文件包含页面长什么样DVWA是一套用PHP编写的开源漏洞练习靶机专门把常见Web漏洞做成一个个可交互的实验模块。File Inclusion模块就是我们要打的那个页面它模拟了一个典型的动态包含场景页面通过page参数切换要显示的文件默认提供include.php、file1.php、file2.php三个文件。当你把page参数从include.php换成file1.php页面内容会随之变化看起来就是一个正常的文件切换功能。新手很难意识到问题出在哪因为“参数可控”这件事太隐蔽了。直到你点开页面右下角的View Source看到$_GET[page]被直接交给include函数才会发现用户传什么路径服务器就包含什么文件没有做任何校验。DVWA每个漏洞模块都分了四个等级Low、Medium、High、Impossible。Low到High是三种不同强度的缺陷写法Impossible是官方实现的安全修复版用来做对比。这也是我推荐用DVWA练手的原因你能在同一个漏洞里看到“从最脆弱到逐步加固”的变化过程这才是真实业务修复需要的思路。1.2 本地靶场搭建的三种常用方式第一种Docker一键起靶机。本机有Docker的话拉一个社区维护的DVWA镜像把80或8080端口映射出来访问首页就能进入操作界面。这种方式最省事不到五分钟就能跑起来缺点是日志路径和镜像内部结构跟手工搭建的环境略有差异后面做高级测试时要注意。第二种本地集成环境部署。我最早用的是XAMPP这类集成环境把DVWA源码解压到网站根目录启动Apache和MySQL访问/setup.php完成数据库初始化然后用默认账号admin、默认密码password登录。这种方式更接近真实服务器结构日志路径清晰适合想深入理解文件包含逻辑的人。第三种手动部署到已有PHP环境。本质上跟第二种一样只是需要自己改数据库配置、调整目录权限。对零基础来说建议直接用第二种可控性最强出问题也好排查。需要提醒的是老版本DVWA在高版本PHP下有时会弹出兼容性警告但只要主页面能正常加载基本不影响漏洞测试。遇到奇怪现象先看PHP错误提示再怀疑代码缺陷。1.3 测试前必须知道的三条边界这里必须先说清楚三件事。第一这套靶机只能在你自己的部署环境或获得书面授权的测试环境里操作用同样的手法去测未经授权的线上系统性质完全不同搞安全的人要有这条底线。第二每次测试前先到DVWA的Security页面把安全等级切到对应难度。文件包含这道题不会自动跟随你的预期变化很多人在Low难度下试了半天没结果最后发现当前等级还停在Medium纯属浪费时间。第三准备好Burp Suite或者浏览器开发者工具。文件包含测试很多时候需要在发往服务器的原始请求里改参数依赖Burp这类工具才能准确控制编码后的内容只靠地址栏改参会被浏览器自动处理结果有偏差。2. 文件包含漏洞原理把include机制讲透2.1 include在PHP里到底做了什么PHP里的include、include_once、require、require_once核心动作都是同一个读取指定文件的内容并按PHP语法解析执行。说白了就是把另一个文件“拼”进来一起运行。打个比方你写文档时插入一个子文档子文档里的内容会直接进入主文档PHP的include就是干这件事的而且它更彻底不仅“插入”内容还会“执行”内容。所以漏洞能不能打取决于一个关键问题include后面跟着的文件路径是开发者写死的还是用户传进来的。如果是用户可控的路径那用户就可以让服务器去包含任意文件读源码、读系统文件、执行代码全都有可能。一个常见的危险写法长这样?php $page $_GET[page]; include($page); ?用户请求page/etc/passwd服务器就会把这个路径交给include把系统用户文件读出来。DVWA的Low级别就是这个玩法简直是把漏洞摆在你面前。2.2 LFI与RFI的分界线文件包含漏洞通常分两类。本地文件包含也就是LFI指的是只包含服务器本机已有的文件远程文件包含也就是RFI指的是可以包含远程服务器上的文件比如让目标服务器去加载你控制的URL。两者的分界线在于PHP的配置项allow_url_include。PHP 5.2以后默认是关闭的如果没开远程URL包含会直接报错。本地包含不受这个限制所以LFI在实际场景里出现频率远高于RFI。另一个容易混淆的点是伪协议比如php://filter它属于PHP内置的流包装器即使allow_url_include关闭也能用这也是为什么读源码常常优先选它而不选远程包含。2.3 为什么审计源码比盲打更重要DVWA每个难度都提供了源码查看入口这是最大的学习便利。做题的第一步永远是点开View Source看服务端逻辑而不是直接开始试各种Payload。Low级别的源码可能只有一行includeMedium级别出现了字符串替换High级别换成了白名单校验。看懂每层过滤做了什么才能精准设计绕过方案。盲打不是不可以但效率太低而且你看不到失败背后的原因。先审计源码等于先拿到地图再走迷宫路会清楚很多。3. Low难度实测无过滤下的任意文件读取3.1 看源码确认漏洞触发点先把DVWA Security里的等级切到Low再进入File Inclusion页面点击View Source。低级别源码非常直接过滤代码基本就是没有page参数从请求里取得后原封不动传给include。我每次带人做这一步都会让他停下来多看一分钟。不是让你欣赏这个漏洞而是让你记住漏洞的“原始形态”没有任何防护时攻击面就是参数本身。这也解释了为什么后面的修复都围绕参数做文章。3.2 三个必测Payload与预期结果第一个必测绝对路径文件读取。在page参数里传Linux的系统用户文件page/etc/passwd页面底部会直接回显文件内容。看到passwd内容就证明任意文件读取已经成立。第二个必测目录遍历。如果当前页面位于/vulnerabilities/fi/这样的子目录可以用../往上跳page../../../etc/passwd本质上跟绝对路径一样能读到内容但这个思路在现实环境里更常见因为很多应用是基于相对路径做包含的。注意层级要够不够会出现路径解析到不存在的目录页面空白。第三个必测PHP伪协议读源码。因为include会执行PHP文件直接包含一个正常PHP文件时页面只会显示执行结果看不到源代码。要想读源码得让文件内容先以base64形式输出pagephp://filter/readconvert.base64-encode/resourceconfig.php页面会返回一段base64编码拿来解码就能看到config.php的完整内容。这个技巧在真实代码审计里非常实用很多敏感配置就是这么泄露的。预期结果汇总一下绝对路径读取确认“能不能读”目录遍历确认“路径拼接规则”伪协议读源码确认“能不能绕过include的执行限制”。这三步走完Low级别就算通关了。3.3 从读取到执行文件包含配合日志投毒任意文件读取已经很严重但文件包含的真正威力在于能变成代码执行。Low级别下最经典的路径是包含日志文件。测试思路是先确认日志路径可读。Linux下Apache的访问日志通常在这个位置page/var/log/apache2/access.log如果能看到日志内容说明服务器日志对Web服务用户可读。然后你构造一个请求在User-Agent里写入一段PHP代码发送给服务器。因为Apache会把User-Agent原样记录到access.log这段PHP代码就被写进了日志文件。下一步再通过文件包含去读这个日志include会执行其中的PHP代码代码执行就成立了。DVWA在Low级别下把读取和执行的链条完整演示了出来。搞懂这个链条再去看高级难度会轻松很多。3.4 Low难度修复思路Low级别修复其实一句话不要直接用用户参数当文件路径。最省事的方法是白名单固定允许包含几个文件?php $allowed array(include.php, file1.php, file2.php); if (in_array($_GET[page], $allowed)) { include($_GET[page]); } else { exit(非法输入); } ?黑名单过滤在这里基本没有意义因为黑名单永远有绕过空间。看到这里你也应该理解为什么DVWA会把Low定义成“零防护”它存在的意义就是让你直观地看到问题的根因。4. Medium难度实测路径过滤的三种绕过思路4.1 先看源码再谈绕过切到Medium等级后再次打开View Source会发现代码里加了两行字符串替换一类是把http://、https://替换掉另一类是把../和..\替换为空。目的很明确既要阻止远程包含也要阻止目录穿越。按我之前说的流程先不要急着传Payload仔细想一下这两个替换到底挡了什么。挡了远程URL开头挡了相对路径里的上级目录记号。接下来所有绕过动作都是围绕“让过滤条件失效”来设计。4.2 绕过思路一双重编码让过滤落空既然../会被替换为空那能不能在替换一次之后剩下的路径仍然被服务器识别成../这里就是经典的双重编码思路。假设你传....//代码执行第一次替换把中间的../删掉剩下../。过滤器只执行一次不会再去检查替换后的结果于是剩下的../被原样交给include。服务器解析路径时它看到的就是两个点加斜杠目录穿越照样成功。用参数表示就是page....//....//....//etc/passwd替换完就变成../../../etc/passwd。这个方法对中等难度的字符串替换特别有效原理就是“你过滤一次我就藏一层”。这里有个实操细节浏览器地址栏有时会自动对特殊字符做编码或解码直接输入可能得不到预期效果。建议用Burp Suite的Repeater模块发原始请求确保到达服务端的确实是....//而不是被提前处理过的内容。4.3 绕过思路二绝对路径甩开相对路径过滤换一个角度想代码过滤的是../而我只是想去读一个绝对路径那路径里根本没有../过滤规则自然就失效了。传这个参数即可page/etc/passwd这个思路看起来很笨但实用程度超出很多人的预期。真实业务里开发者往往只盯着相对路径穿越做防护忽略了绝对路径包含同样危险。做安全测试时不要被“绕过过滤”四个字框住思维有时候直接换个上下文就解决问题了。同理Windows环境下的绝对路径也有效比如C:\Windows\system.ini。中等难度的过滤只针对..\不针对盘符开头的绝对路径。4.4 绕过思路三大小写与伪协议混用如果源码用的是str_replace做字符串替换它默认区分大小写。那么传HTTP://或者HtTp://只要原始代码只精确匹配了小写的http://大小写变形就能绕过。不过这种绕过方式依赖具体实现有些版本已经换成不区分大小写的正则替换那就无效了。判断方法很简单传一个变形后的URL看服务器有没有尝试去请求它如果报错信息里出现了远程请求痕迹说明绕过成功。另外php://filter伪协议也要顺手试一下。它不依赖http://开头也不涉及../中等难度下经常可以直接用pagephp://filter/readconvert.base64-encode/resourceconfig.php我在本地测试中中等难度用伪协议读源码的成功率很高原理跟Low差不多因为这类过滤往往没有覆盖本地流包装器。4.5 Medium修复要点中等难度的修复不能再靠黑名单了。正确做法是白名单加路径规范化。拿到参数后先做realpath把相对路径和符号链接都解析成真实路径再检查这个真实路径是否在允许目录内。?php $base realpath(dirname(__FILE__)); $path realpath($_GET[page]); if ($path strpos($path, $base) 0) { include($path); } else { exit(非法路径); } ?strpos判断前缀的方式存在一点小坑比如目录名重叠问题更严谨的做法是统一加目录分隔符再比较。但思路是对的从“堵几个特征”变成“限制允许范围”。5. High难度实测白名单校验与日志投毒5.1 高级过滤到底做了什么High级别切过来再看源码思路完全变了。代码不再是黑名单替换而是直接做白名单判断page参数只能是include.php其他任何文件都会返回错误。少了那些眼花缭乱的替换规则反而让很多新手无从下手因为常规的../、绝对路径、伪协议全部失效。这个设计很值得品味。前两个等级还在教你绕过到了高级等级它开始教你“强硬限制”的效果只允许一个文件名攻击面被压缩到极小。但安全测试要做的不只是防守视角还得思考这个白名单能不能被绕过。5.2 绕过白名单的两条真实路径白名单限制的是“include函数最终加载的文件名”但它限制不了“那个文件的内容来自哪里”。如果服务器上存在一个内容可控的文件而它恰好不在白名单限制范围外换个说法白名单只允许include.php那我能不能找另一个“内容由我控制但路径合法”的文件让它被include时执行我要的代码第一条路径是日志投毒。Apache访问日志会记录请求的User-Agent如果我把它写成PHP代码日志文件本身不叫include.php不能直接包含。但DVWA的高等级并没有把日志文件加入白名单问题就出在这实际的过滤只有文件名对比并没有限制包含文件必须位于应用目录内。只要找到一个内容可控的文件白名单就形同虚设。第二条路径是上传文件马。如果应用里有文件上传功能且上传后的文件保存在Web目录那上传一个内容包含PHP代码的图片再用文件包含去包含这个图片就能触发执行。DVWA的File Inclusion模块本身不带上传点但思路要记着这套组合拳在真实业务里非常常见。5.3 日志投毒的几步实操只在自己的DVWA靶机里操作。先确认目标环境是Linux加Apache然后尝试包含访问日志看是否能读到日志内容page/var/log/apache2/access.log能读到日志就可以进行下一步。用Burp Suite构造一个请求在User-Agent里写入PHP代码GET /dvwa/vulnerabilities/fi/?page/var/log/apache2/access.log HTTP/1.1 Host: 127.0.0.1 User-Agent: ?php phpinfo(); ?这里有个实操细节如果你把包含日志的请求本身也带上恶意User-Agent那么日志里会同时记录多条带PHP代码的请求包含时PHP代码会被多次执行输出非常杂乱。更干净的做法是分两步走。第一步用Burp发送一个访问不存在路径的请求比如GET /hacktest.phpUser-Agent写入那串PHP代码。这个请求会被Apache记录日志里就多了一行带PHP代码的记录。第二步用正常的浏览器或Burp重新打开文件包含页面传参page/var/log/apache2/access.log。include读到日志日志里的User-Agent内容被当成PHP执行页面出现phpinfo的输出。到这里文件包含已经从“读文件”升级为“执行代码”。如果你想继续验证命令执行可以把User-Agent里的PHP代码换成命令执行语句在后续请求中通过参数传命令。这个测试我只建议在靶机里做别动真实系统。日志路径在不同环境差别很大Windows下XAMPP常见路径是C:\xampp\apache\logs\access.logDocker里可能是/var/log/apache2/access.log。判断方法很简单先用绝对路径读取逐个试能读出日志内容就停下。5.4 High级别修复建议White名单做得更彻底一点不只比较文件名还要做路径校验。把所有允许包含的文件放到一个固定目录参数只接收文件名不允许带路径分隔符。include之前先拼上固定目录前缀?php $file basename($_GET[page]); $path __DIR__ . /pages/ . $file; if (is_file($path)) { include($path); } else { exit(文件不存在); } ?这样即使日志可读因为日志不在pages/目录下也无法被包含。更进一步生产环境直接不要用动态include做页面切换改用框架的路由分发或模板渲染机制从设计上消灭这个问题。危险配置面也要缩小allow_url_include保持关闭php://这类不需要的伪协议能限制就限制。6. 测试过程中最常见的坑与排查顺序6.1 包含之后白屏、下载与乱码文件包含测试最常见的表现是白屏。白屏通常意味着include执行了但目标文件路径不对或者目标文件内容被PHP解析后没有任何输出。先检查路径层级够不够再多试几个绝对路径排除路径问题。第二个表现是下载。服务器把目标文件当成普通文件直接返回下载这往往说明中间层配置有问题比如把动态请求交给了静态处理不是DVWA默认场景。第三种表现是乱码。读取二进制文件或非文本内容时出现乱码是正常的比如包含图片文件时页面会出现二进制内容如果包含的是UTF-8文本反而显示正常。乱码本身说明可读但要进一步判断是否能执行还是需要回到内容可控这个前提上。6.2 浏览器自动解码导致的绕过失败用地址栏直接测试编码后的路径是Medium难度最常踩的坑。浏览器看到%2e%2e%2f会自动解码成../然后这个../先被服务器端的过滤规则命中替换为空导致绕过失败。解决方法是绕开浏览器这道工序。打开Burp Suite的Repeater手动构造原始请求把参数值设置成不经过URL解码的原始字符串。这样到达服务端的才是你真正想发送的内容结果才可信。这个小问题在Low级别不明显到Medium级别经常会让人怀疑人生排查时先确认你看到的请求和到达服务端的请求是否一致。6.3 日志路径因环境而异High级别的日志投毒很多人卡在“日志到底在哪”。同一个DVWAWindows集成环境、Linux原生Apache、Docker容器日志路径各不一样甚至Apache和Nginx的路径也完全不同。常见的几个位置环境常见路径Linux Apache/var/log/apache2/access.log 或 /var/log/httpd/access_logWindows XAMPPC:\xampp\apache\logs\access.logLinux Nginx/var/log/nginx/access.log如果都不对可以先利用LFI去读配置文件比如Apache的/etc/apache2/apache2.conf或Nginx的/etc/nginx/nginx.conf从配置里找日志路径。靶机环境下允许这种“用漏洞打漏洞”的调研方式真实环境中也要先摸清目标类型。6.4 一套固定的排错顺序我自己的排查顺序是固定的先看源码确认过滤逻辑再判断服务器操作系统和Web中间件然后测绝对路径与伪协议确认基础可用性最后才进入日志投毒等复杂利用。每失败一次不要急着换Payload先回退到源码层面想一想“这个过滤规则为什么能拦住我”往往绕过的答案就藏在问题里。7. 从靶场走向防护文件包含的防御与检测7.1 代码层修复三件套代码修复按照优先级排第一是白名单加精确匹配第二是路径规范化和目录锁定第三是关闭不必要的危险配置。这三层同时做才能基本堵住文件包含的常见利用路径。白名单不要只比对文件名因为文件名可以被伪造更可靠的是通过realpath拿到文件的真实物理路径再判断它是否位于允许目录内。危险配置方面allow_url_include和allow_url_fopen在不必要时保持关闭能挡住大批RFI和伪协议尝试。7.2 产品层检测与审计建议只剩代码修复还不够线上环境还需要检测手段。WAF可以对page参数里的目录穿越特征、php://伪协议、对/etc/passwd和日志路径的敏感引用做拦截但WAF只是兜底不能替代修复。日志审计要重点盯两类请求一类包含读取系统文件的路径特征另一类是请求参数里出现php://、data://等伪协议关键字。看到这种请求不要只当攻击拦截要顺藤摸瓜排查是不是已经有文件被成功包含过确认攻击是否已经发生了代码执行。渗透测试报告里文件包含漏洞不要只写“任意文件读取”一定要验证是否可复用为日志投毒或上传文件组合升级成RCE后再定风险等级危害完全不同。7.3 三个难度与防护方案速查表难度过滤方式主要绕过思路对应防护Low无过滤绝对路径读取、伪协议、日志包含白名单文件列表Medium替换http(s)和../双重编码、绝对路径、大小写变形白名单加路径规范化High白名单限文件名日志投毒、可控文件执行固定目录加路径校验关闭伪协议Impossiblerealpath加白名单常规方式无效参考官方源码实现8. 写在最后我的实操体会与后续练习建议8.1 先读源码再做测试的顺序感回头看这套DVWA三关测试我踩过最值得说的坑有两处。第一处是Medium难度在浏览器地址栏直接测编码路径被浏览器自动解码后导致过滤命中白白浪费时间直到改用Burp才发现问题根本不在服务端。第二处是High难度一开始觉得白名单无解后来意识到“include只能包含服务器能读的文件那为什么不找一个内容可控的文件来包含”思路一下子打开了。这两个坑其实指向同一件事带着顺序去测试比背Payload重要得多。8.2 下一步可以拓展的方向做完这份测试建议再去源码里看一眼Impossible级别的实现它会把白名单和路径校验结合在一起那种简洁感比任何绕过技巧都更能说明问题。想继续加深理解可以搭一个Nginx日志版本的靶机再打一遍换掉中间件往往就得换绕过思路。还能在自建靶机里配合一个文件上传点把“上传图片马加文件包含执行”的完整攻击链走通。文件包含这个漏洞真正考验的不是记忆而是理解“include到底做了什么”以及“谁能控制路径”这两件事。把这两点吃透无论过滤规则怎么换绕过的路总是能找到的。